• 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
252def _CheckTestDuplicates(input_api, output_api):
253  """Checks that pixel and javascript tests don't contain duplicates.
254  We use .in and .pdf files, having both can cause race conditions on the bots,
255  which run the tests in parallel.
256  """
257  tests_added = []
258  results = []
259  for f in input_api.AffectedFiles():
260    if not f.LocalPath().startswith(('testing/resources/pixel/',
261        'testing/resources/javascript/')):
262      continue
263    end_len = 0
264    if f.LocalPath().endswith('.in'):
265      end_len = 3
266    elif f.LocalPath().endswith('.pdf'):
267      end_len = 4
268    else:
269      continue
270    path = f.LocalPath()[:-end_len]
271    if path in tests_added:
272      results.append(output_api.PresubmitError(
273          'Remove %s to prevent shadowing %s' % (path + '.pdf',
274            path + '.in')))
275    else:
276      tests_added.append(path)
277  return results
278
279def _CheckPNGFormat(input_api, output_api):
280  """Checks that .png files have a format that will be considered valid by our
281  test runners. If a file ends with .png, then it must be of the form
282  NAME_expected(_(win|mac|linux))?.pdf.#.png"""
283  expected_pattern = input_api.re.compile(
284      r'.+_expected(_(win|mac|linux))?\.pdf\.\d+.png')
285  results = []
286  for f in input_api.AffectedFiles(include_deletes=False):
287    if not f.LocalPath().endswith('.png'):
288      continue
289    if expected_pattern.match(f.LocalPath()):
290      continue
291    results.append(output_api.PresubmitError(
292        'PNG file %s does not have the correct format' % f.LocalPath()))
293  return results
294
295def CheckChangeOnUpload(input_api, output_api):
296  results = []
297  results += _CheckUnwantedDependencies(input_api, output_api)
298  results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
299  results += input_api.canned_checks.CheckChangeLintsClean(
300      input_api, output_api, None, LINT_FILTERS)
301  results += _CheckIncludeOrder(input_api, output_api)
302  results += _CheckTestDuplicates(input_api, output_api)
303  results += _CheckPNGFormat(input_api, output_api)
304
305  return results
306