blob: 3eea4fe0b6c87abc5b12244e2eee0eac8a2ba033 [file] [log] [blame]
Nico Weber077f1a32015-08-06 15:08:57 -07001# 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
Dan Sinclair22d66072016-02-22 11:56:05 -050011LINT_FILTERS = [
Dan Sinclair3ebd1212016-03-09 09:59:23 -050012 # Rvalue ref checks are unreliable.
dan sinclaird2019df2016-02-22 22:32:03 -050013 '-build/c++11',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050014 # Need to fix header names not matching cpp names.
dan sinclaird2019df2016-02-22 22:32:03 -050015 '-build/include_order',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050016 # Too many to fix at the moment.
dan sinclaird2019df2016-02-22 22:32:03 -050017 '-readability/casting',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050018 # Need to refactor large methods to fix.
dan sinclaird2019df2016-02-22 22:32:03 -050019 '-readability/fn_size',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050020 # Lots of usage to fix first.
dan sinclaird2019df2016-02-22 22:32:03 -050021 '-runtime/int',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050022 # Lots of non-const references need to be fixed
dan sinclaird2019df2016-02-22 22:32:03 -050023 '-runtime/references',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050024 # We are not thread safe, so this will never pass.
dan sinclaird2019df2016-02-22 22:32:03 -050025 '-runtime/threadsafe_fn',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050026 # Figure out how to deal with #defines that git cl format creates.
dan sinclaird2019df2016-02-22 22:32:03 -050027 '-whitespace/indent',
Dan Sinclair22d66072016-02-22 11:56:05 -050028]
29
Dan Sinclair544bbc62016-03-14 15:07:39 -040030
dsinclair2ca2da52016-09-13 18:10:34 -070031_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
Lei Zhang71e26732020-05-21 21:03:05 +000037# Bypass the AUTHORS check for these accounts.
38_KNOWN_ROBOTS = set() | set(
39 '%s@skia-public.iam.gserviceaccount.com' % s for s in ('pdfium-autoroll',))
40
41
Dan Sinclair544bbc62016-03-14 15:07:39 -040042def _CheckUnwantedDependencies(input_api, output_api):
43 """Runs checkdeps on #include statements added in this
44 change. Breaking - rules is an error, breaking ! rules is a
45 warning.
46 """
47 import sys
48 # We need to wait until we have an input_api object and use this
49 # roundabout construct to import checkdeps because this file is
50 # eval-ed and thus doesn't have __file__.
51 original_sys_path = sys.path
52 try:
Lei Zhangb1d78722018-02-02 22:17:58 +000053 def GenerateCheckdepsPath(base_path):
54 return input_api.os_path.join(base_path, 'buildtools', 'checkdeps')
55
56 presubmit_path = input_api.PresubmitLocalPath()
57 presubmit_parent_path = input_api.os_path.dirname(presubmit_path)
58 not_standalone_pdfium = \
59 input_api.os_path.basename(presubmit_parent_path) == "third_party" and \
60 input_api.os_path.basename(presubmit_path) == "pdfium"
61
62 sys.path.append(GenerateCheckdepsPath(presubmit_path))
63 if not_standalone_pdfium:
64 presubmit_grandparent_path = input_api.os_path.dirname(
65 presubmit_parent_path)
66 sys.path.append(GenerateCheckdepsPath(presubmit_grandparent_path))
67
Dan Sinclair544bbc62016-03-14 15:07:39 -040068 import checkdeps
69 from cpp_checker import CppChecker
70 from rules import Rule
dsinclair4cd49e12016-04-05 10:28:48 -070071 except ImportError:
72 return [output_api.PresubmitError(
73 'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
Dan Sinclair544bbc62016-03-14 15:07:39 -040074 finally:
75 # Restore sys.path to what it was before.
76 sys.path = original_sys_path
77
78 added_includes = []
79 for f in input_api.AffectedFiles():
80 if not CppChecker.IsCppFile(f.LocalPath()):
81 continue
82
83 changed_lines = [line for line_num, line in f.ChangedContents()]
84 added_includes.append([f.LocalPath(), changed_lines])
85
86 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
87
88 error_descriptions = []
89 warning_descriptions = []
90 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
91 added_includes):
92 description_with_path = '%s\n %s' % (path, rule_description)
93 if rule_type == Rule.DISALLOW:
94 error_descriptions.append(description_with_path)
95 else:
96 warning_descriptions.append(description_with_path)
97
98 results = []
99 if error_descriptions:
100 results.append(output_api.PresubmitError(
101 'You added one or more #includes that violate checkdeps rules.',
102 error_descriptions))
103 if warning_descriptions:
104 results.append(output_api.PresubmitPromptOrNotify(
105 'You added one or more #includes of files that are temporarily\n'
106 'allowed but being removed. Can you avoid introducing the\n'
107 '#include? See relevant DEPS file(s) for details and contacts.',
108 warning_descriptions))
109 return results
110
111
dsinclair2ca2da52016-09-13 18:10:34 -0700112def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
113 """Checks that the lines in scope occur in the right order.
114
115 1. C system files in alphabetical order
116 2. C++ system files in alphabetical order
117 3. Project's .h files
118 """
119
120 c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
121 cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
122 custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
123
124 C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
125
126 state = C_SYSTEM_INCLUDES
127
128 previous_line = ''
129 previous_line_num = 0
130 problem_linenums = []
131 out_of_order = " - line belongs before previous line"
132 for line_num, line in scope:
133 if c_system_include_pattern.match(line):
134 if state != C_SYSTEM_INCLUDES:
135 problem_linenums.append((line_num, previous_line_num,
136 " - C system include file in wrong block"))
137 elif previous_line and previous_line > line:
138 problem_linenums.append((line_num, previous_line_num,
139 out_of_order))
140 elif cpp_system_include_pattern.match(line):
141 if state == C_SYSTEM_INCLUDES:
142 state = CPP_SYSTEM_INCLUDES
143 elif state == CUSTOM_INCLUDES:
144 problem_linenums.append((line_num, previous_line_num,
145 " - c++ system include file in wrong block"))
146 elif previous_line and previous_line > line:
147 problem_linenums.append((line_num, previous_line_num, out_of_order))
148 elif custom_include_pattern.match(line):
149 if state != CUSTOM_INCLUDES:
150 state = CUSTOM_INCLUDES
151 elif previous_line and previous_line > line:
152 problem_linenums.append((line_num, previous_line_num, out_of_order))
153 else:
154 problem_linenums.append((line_num, previous_line_num,
155 "Unknown include type"))
156 previous_line = line
157 previous_line_num = line_num
158
159 warnings = []
160 for (line_num, previous_line_num, failure_type) in problem_linenums:
161 if line_num in changed_linenums or previous_line_num in changed_linenums:
162 warnings.append(' %s:%d:%s' % (file_path, line_num, failure_type))
163 return warnings
164
165
166def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
167 """Checks the #include order for the given file f."""
168
169 system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
170 # Exclude the following includes from the check:
171 # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
172 # specific order.
173 # 2) <atlbase.h>, "build/build_config.h"
174 excluded_include_pattern = input_api.re.compile(
175 r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
176 custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
177 # Match the final or penultimate token if it is xxxtest so we can ignore it
178 # when considering the special first include.
179 test_file_tag_pattern = input_api.re.compile(
180 r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
181 if_pattern = input_api.re.compile(
182 r'\s*#\s*(if|elif|else|endif|define|undef).*')
183 # Some files need specialized order of includes; exclude such files from this
184 # check.
185 uncheckable_includes_pattern = input_api.re.compile(
186 r'\s*#include '
187 '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
188
189 contents = f.NewContents()
190 warnings = []
191 line_num = 0
192
193 # Handle the special first include. If the first include file is
194 # some/path/file.h, the corresponding including file can be some/path/file.cc,
195 # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
196 # etc. It's also possible that no special first include exists.
197 # If the included file is some/path/file_platform.h the including file could
198 # also be some/path/file_xxxtest_platform.h.
199 including_file_base_name = test_file_tag_pattern.sub(
200 '', input_api.os_path.basename(f.LocalPath()))
201
202 for line in contents:
203 line_num += 1
204 if system_include_pattern.match(line):
205 # No special first include -> process the line again along with normal
206 # includes.
207 line_num -= 1
208 break
209 match = custom_include_pattern.match(line)
210 if match:
211 match_dict = match.groupdict()
212 header_basename = test_file_tag_pattern.sub(
213 '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
214
215 if header_basename not in including_file_base_name:
216 # No special first include -> process the line again along with normal
217 # includes.
218 line_num -= 1
219 break
220
221 # Split into scopes: Each region between #if and #endif is its own scope.
222 scopes = []
223 current_scope = []
224 for line in contents[line_num:]:
225 line_num += 1
226 if uncheckable_includes_pattern.match(line):
227 continue
228 if if_pattern.match(line):
229 scopes.append(current_scope)
230 current_scope = []
231 elif ((system_include_pattern.match(line) or
232 custom_include_pattern.match(line)) and
233 not excluded_include_pattern.match(line)):
234 current_scope.append((line_num, line))
235 scopes.append(current_scope)
236
237 for scope in scopes:
238 warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
239 changed_linenums))
240 return warnings
241
242
243def _CheckIncludeOrder(input_api, output_api):
244 """Checks that the #include order is correct.
245
246 1. The corresponding header for source files.
247 2. C system files in alphabetical order
248 3. C++ system files in alphabetical order
249 4. Project's .h files in alphabetical order
250
251 Each region separated by #if, #elif, #else, #endif, #define and #undef follows
252 these rules separately.
253 """
dsinclair2ca2da52016-09-13 18:10:34 -0700254 warnings = []
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000255 for f in input_api.AffectedFiles(file_filter=input_api.FilterSourceFile):
dsinclaird33c8e32016-11-21 13:31:16 -0800256 if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
dsinclair2ca2da52016-09-13 18:10:34 -0700257 changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
258 warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
259
260 results = []
261 if warnings:
262 results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
263 warnings))
264 return results
265
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000266
Nicolas Penad824a902017-05-19 15:59:49 -0400267def _CheckTestDuplicates(input_api, output_api):
268 """Checks that pixel and javascript tests don't contain duplicates.
269 We use .in and .pdf files, having both can cause race conditions on the bots,
270 which run the tests in parallel.
271 """
272 tests_added = []
273 results = []
274 for f in input_api.AffectedFiles():
Lei Zhang567039b2019-09-17 00:45:23 +0000275 if f.Action() == 'D':
276 continue
Nicolas Penad824a902017-05-19 15:59:49 -0400277 if not f.LocalPath().startswith(('testing/resources/pixel/',
278 'testing/resources/javascript/')):
279 continue
280 end_len = 0
281 if f.LocalPath().endswith('.in'):
282 end_len = 3
283 elif f.LocalPath().endswith('.pdf'):
284 end_len = 4
285 else:
286 continue
Nicolas Penac4479c52017-06-26 11:45:06 -0400287 path = f.LocalPath()[:-end_len]
Nicolas Penad824a902017-05-19 15:59:49 -0400288 if path in tests_added:
289 results.append(output_api.PresubmitError(
290 'Remove %s to prevent shadowing %s' % (path + '.pdf',
291 path + '.in')))
292 else:
293 tests_added.append(path)
294 return results
295
Lei Zhang624fba82021-06-15 20:11:39 +0000296
Nicolas Penac4479c52017-06-26 11:45:06 -0400297def _CheckPNGFormat(input_api, output_api):
298 """Checks that .png files have a format that will be considered valid by our
299 test runners. If a file ends with .png, then it must be of the form
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000300 NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png"""
Nicolas Penac4479c52017-06-26 11:45:06 -0400301 expected_pattern = input_api.re.compile(
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000302 r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400303 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400304 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400305 if not f.LocalPath().endswith('.png'):
306 continue
307 if expected_pattern.match(f.LocalPath()):
308 continue
309 results.append(output_api.PresubmitError(
310 'PNG file %s does not have the correct format' % f.LocalPath()))
311 return results
dsinclair2ca2da52016-09-13 18:10:34 -0700312
Lei Zhang624fba82021-06-15 20:11:39 +0000313
314def _CheckUselessForwardDeclarations(input_api, output_api):
315 """Checks that added or removed lines in non third party affected
316 header files do not lead to new useless class or struct forward
317 declaration.
318 """
319 results = []
320 class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
321 input_api.re.MULTILINE)
322 struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
323 input_api.re.MULTILINE)
324 for f in input_api.AffectedFiles(include_deletes=False):
325 if f.LocalPath().startswith('third_party'):
326 continue
327
328 if not f.LocalPath().endswith('.h'):
329 continue
330
331 contents = input_api.ReadFile(f)
332 fwd_decls = input_api.re.findall(class_pattern, contents)
333 fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
334
335 useless_fwd_decls = []
336 for decl in fwd_decls:
337 count = sum(
338 1
339 for _ in input_api.re.finditer(r'\b%s\b' %
340 input_api.re.escape(decl), contents))
341 if count == 1:
342 useless_fwd_decls.append(decl)
343
344 if not useless_fwd_decls:
345 continue
346
347 for line in f.GenerateScmDiff().splitlines():
348 if (line.startswith('-') and not line.startswith('--') or
349 line.startswith('+') and not line.startswith('++')):
350 for decl in useless_fwd_decls:
351 if input_api.re.search(r'\b%s\b' % decl, line[1:]):
352 results.append(
353 output_api.PresubmitPromptWarning(
354 '%s: %s forward declaration is no longer needed' %
355 (f.LocalPath(), decl)))
356 useless_fwd_decls.remove(decl)
357
358 return results
359
360
Nico Weber077f1a32015-08-06 15:08:57 -0700361def CheckChangeOnUpload(input_api, output_api):
362 results = []
Lei Zhang804a0e32020-05-21 20:57:16 +0000363 results.extend(_CheckUnwantedDependencies(input_api, output_api))
364 results.extend(
365 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
366 results.extend(
Lei Zhang4c472552021-05-24 21:44:47 +0000367 input_api.canned_checks.CheckChangeLintsClean(
368 input_api, output_api, lint_filters=LINT_FILTERS))
Lei Zhang804a0e32020-05-21 20:57:16 +0000369 results.extend(_CheckIncludeOrder(input_api, output_api))
370 results.extend(_CheckTestDuplicates(input_api, output_api))
371 results.extend(_CheckPNGFormat(input_api, output_api))
Lei Zhang624fba82021-06-15 20:11:39 +0000372 results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
Dan Sinclair544bbc62016-03-14 15:07:39 -0400373
Lei Zhang71e26732020-05-21 21:03:05 +0000374 author = input_api.change.author_email
375 if author and author not in _KNOWN_ROBOTS:
376 results.extend(
377 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
378
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000379 for f in input_api.AffectedFiles():
380 path, name = input_api.os_path.split(f.LocalPath())
381 if name == 'PRESUBMIT.py':
382 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
383 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
384 if f.Action() != 'D' and input_api.os_path.exists(test_file):
385 # The PRESUBMIT.py file (and the directory containing it) might
386 # have been affected by being moved or removed, so only try to
387 # run the tests if they still exist.
388 results.extend(
389 input_api.canned_checks.RunUnitTestsInDirectory(
390 input_api,
391 output_api,
392 full_path,
Lei Zhang95c61432020-08-27 22:45:08 +0000393 files_to_check=[r'^PRESUBMIT_test\.py$']))
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000394
Nico Weber077f1a32015-08-06 15:08:57 -0700395 return results