• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Presubmit script for pdfium.
6
7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8for more details about the presubmit API built into depot_tools.
9"""
10
11LINT_FILTERS = [
12  # Rvalue ref checks are unreliable.
13  '-build/c++11',
14  # Need to fix header names not matching cpp names.
15  '-build/include_order',
16  # Too many to fix at the moment.
17  '-readability/casting',
18  # Need to refactor large methods to fix.
19  '-readability/fn_size',
20  # Lots of usage to fix first.
21  '-runtime/int',
22  # Lots of non-const references need to be fixed
23  '-runtime/references',
24  # We are not thread safe, so this will never pass.
25  '-runtime/threadsafe_fn',
26  # Figure out how to deal with #defines that git cl format creates.
27  '-whitespace/indent',
28]
29
30
31_INCLUDE_ORDER_WARNING = (
32    'Your #include order seems to be broken. Remember to use the right '
33    'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
34    'cppguide.html#Names_and_Order_of_Includes')
35
36
37def _CheckUnwantedDependencies(input_api, output_api):
38  """Runs checkdeps on #include statements added in this
39  change. Breaking - rules is an error, breaking ! rules is a
40  warning.
41  """
42  import sys
43  # We need to wait until we have an input_api object and use this
44  # roundabout construct to import checkdeps because this file is
45  # eval-ed and thus doesn't have __file__.
46  original_sys_path = sys.path
47  try:
48    sys.path = sys.path + [input_api.os_path.join(
49        input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
50    import checkdeps
51    from cpp_checker import CppChecker
52    from rules import Rule
53  except ImportError:
54    return [output_api.PresubmitError(
55        'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
56  finally:
57    # Restore sys.path to what it was before.
58    sys.path = original_sys_path
59
60  added_includes = []
61  for f in input_api.AffectedFiles():
62    if not CppChecker.IsCppFile(f.LocalPath()):
63      continue
64
65    changed_lines = [line for line_num, line in f.ChangedContents()]
66    added_includes.append([f.LocalPath(), changed_lines])
67
68  deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
69
70  error_descriptions = []
71  warning_descriptions = []
72  for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
73      added_includes):
74    description_with_path = '%s\n    %s' % (path, rule_description)
75    if rule_type == Rule.DISALLOW:
76      error_descriptions.append(description_with_path)
77    else:
78      warning_descriptions.append(description_with_path)
79
80  results = []
81  if error_descriptions:
82    results.append(output_api.PresubmitError(
83        'You added one or more #includes that violate checkdeps rules.',
84        error_descriptions))
85  if warning_descriptions:
86    results.append(output_api.PresubmitPromptOrNotify(
87        'You added one or more #includes of files that are temporarily\n'
88        'allowed but being removed. Can you avoid introducing the\n'
89        '#include? See relevant DEPS file(s) for details and contacts.',
90        warning_descriptions))
91  return results
92
93
94def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
95  """Checks that the lines in scope occur in the right order.
96
97  1. C system files in alphabetical order
98  2. C++ system files in alphabetical order
99  3. Project's .h files
100  """
101
102  c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
103  cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
104  custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
105
106  C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
107
108  state = C_SYSTEM_INCLUDES
109
110  previous_line = ''
111  previous_line_num = 0
112  problem_linenums = []
113  out_of_order = " - line belongs before previous line"
114  for line_num, line in scope:
115    if c_system_include_pattern.match(line):
116      if state != C_SYSTEM_INCLUDES:
117        problem_linenums.append((line_num, previous_line_num,
118            " - C system include file in wrong block"))
119      elif previous_line and previous_line > line:
120        problem_linenums.append((line_num, previous_line_num,
121            out_of_order))
122    elif cpp_system_include_pattern.match(line):
123      if state == C_SYSTEM_INCLUDES:
124        state = CPP_SYSTEM_INCLUDES
125      elif state == CUSTOM_INCLUDES:
126        problem_linenums.append((line_num, previous_line_num,
127            " - c++ system include file in wrong block"))
128      elif previous_line and previous_line > line:
129        problem_linenums.append((line_num, previous_line_num, out_of_order))
130    elif custom_include_pattern.match(line):
131      if state != CUSTOM_INCLUDES:
132        state = CUSTOM_INCLUDES
133      elif previous_line and previous_line > line:
134        problem_linenums.append((line_num, previous_line_num, out_of_order))
135    else:
136      problem_linenums.append((line_num, previous_line_num,
137          "Unknown include type"))
138    previous_line = line
139    previous_line_num = line_num
140
141  warnings = []
142  for (line_num, previous_line_num, failure_type) in problem_linenums:
143    if line_num in changed_linenums or previous_line_num in changed_linenums:
144      warnings.append('    %s:%d:%s' % (file_path, line_num, failure_type))
145  return warnings
146
147
148def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
149  """Checks the #include order for the given file f."""
150
151  system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
152  # Exclude the following includes from the check:
153  # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
154  # specific order.
155  # 2) <atlbase.h>, "build/build_config.h"
156  excluded_include_pattern = input_api.re.compile(
157      r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
158  custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
159  # Match the final or penultimate token if it is xxxtest so we can ignore it
160  # when considering the special first include.
161  test_file_tag_pattern = input_api.re.compile(
162    r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
163  if_pattern = input_api.re.compile(
164      r'\s*#\s*(if|elif|else|endif|define|undef).*')
165  # Some files need specialized order of includes; exclude such files from this
166  # check.
167  uncheckable_includes_pattern = input_api.re.compile(
168      r'\s*#include '
169      '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
170
171  contents = f.NewContents()
172  warnings = []
173  line_num = 0
174
175  # Handle the special first include. If the first include file is
176  # some/path/file.h, the corresponding including file can be some/path/file.cc,
177  # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
178  # etc. It's also possible that no special first include exists.
179  # If the included file is some/path/file_platform.h the including file could
180  # also be some/path/file_xxxtest_platform.h.
181  including_file_base_name = test_file_tag_pattern.sub(
182    '', input_api.os_path.basename(f.LocalPath()))
183
184  for line in contents:
185    line_num += 1
186    if system_include_pattern.match(line):
187      # No special first include -> process the line again along with normal
188      # includes.
189      line_num -= 1
190      break
191    match = custom_include_pattern.match(line)
192    if match:
193      match_dict = match.groupdict()
194      header_basename = test_file_tag_pattern.sub(
195        '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
196
197      if header_basename not in including_file_base_name:
198        # No special first include -> process the line again along with normal
199        # includes.
200        line_num -= 1
201      break
202
203  # Split into scopes: Each region between #if and #endif is its own scope.
204  scopes = []
205  current_scope = []
206  for line in contents[line_num:]:
207    line_num += 1
208    if uncheckable_includes_pattern.match(line):
209      continue
210    if if_pattern.match(line):
211      scopes.append(current_scope)
212      current_scope = []
213    elif ((system_include_pattern.match(line) or
214           custom_include_pattern.match(line)) and
215          not excluded_include_pattern.match(line)):
216      current_scope.append((line_num, line))
217  scopes.append(current_scope)
218
219  for scope in scopes:
220    warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
221                                               changed_linenums))
222  return warnings
223
224
225def _CheckIncludeOrder(input_api, output_api):
226  """Checks that the #include order is correct.
227
228  1. The corresponding header for source files.
229  2. C system files in alphabetical order
230  3. C++ system files in alphabetical order
231  4. Project's .h files in alphabetical order
232
233  Each region separated by #if, #elif, #else, #endif, #define and #undef follows
234  these rules separately.
235  """
236  def FileFilterIncludeOrder(affected_file):
237    black_list = (input_api.DEFAULT_BLACK_LIST)
238    return input_api.FilterSourceFile(affected_file, black_list=black_list)
239
240  warnings = []
241  for f in input_api.AffectedFiles(file_filter=FileFilterIncludeOrder):
242    if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
243      changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
244      warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
245
246  results = []
247  if warnings:
248    results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
249                                                      warnings))
250  return results
251
252
253def CheckChangeOnUpload(input_api, output_api):
254  results = []
255  results += _CheckUnwantedDependencies(input_api, output_api)
256  results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
257  results += input_api.canned_checks.CheckChangeLintsClean(
258      input_api, output_api, None, LINT_FILTERS)
259  results += _CheckIncludeOrder(input_api, output_api)
260
261  return results
262