blob: f87de6dd622c8b4f2ee6acf10b3ac978a58f7e95 [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 """
254 def FileFilterIncludeOrder(affected_file):
255 black_list = (input_api.DEFAULT_BLACK_LIST)
256 return input_api.FilterSourceFile(affected_file, black_list=black_list)
257
258 warnings = []
259 for f in input_api.AffectedFiles(file_filter=FileFilterIncludeOrder):
dsinclaird33c8e32016-11-21 13:31:16 -0800260 if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
dsinclair2ca2da52016-09-13 18:10:34 -0700261 changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
262 warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
263
264 results = []
265 if warnings:
266 results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
267 warnings))
268 return results
269
Nicolas Penad824a902017-05-19 15:59:49 -0400270def _CheckTestDuplicates(input_api, output_api):
271 """Checks that pixel and javascript tests don't contain duplicates.
272 We use .in and .pdf files, having both can cause race conditions on the bots,
273 which run the tests in parallel.
274 """
275 tests_added = []
276 results = []
277 for f in input_api.AffectedFiles():
Lei Zhang567039b2019-09-17 00:45:23 +0000278 if f.Action() == 'D':
279 continue
Nicolas Penad824a902017-05-19 15:59:49 -0400280 if not f.LocalPath().startswith(('testing/resources/pixel/',
281 'testing/resources/javascript/')):
282 continue
283 end_len = 0
284 if f.LocalPath().endswith('.in'):
285 end_len = 3
286 elif f.LocalPath().endswith('.pdf'):
287 end_len = 4
288 else:
289 continue
Nicolas Penac4479c52017-06-26 11:45:06 -0400290 path = f.LocalPath()[:-end_len]
Nicolas Penad824a902017-05-19 15:59:49 -0400291 if path in tests_added:
292 results.append(output_api.PresubmitError(
293 'Remove %s to prevent shadowing %s' % (path + '.pdf',
294 path + '.in')))
295 else:
296 tests_added.append(path)
297 return results
298
Nicolas Penac4479c52017-06-26 11:45:06 -0400299def _CheckPNGFormat(input_api, output_api):
300 """Checks that .png files have a format that will be considered valid by our
301 test runners. If a file ends with .png, then it must be of the form
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000302 NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png"""
Nicolas Penac4479c52017-06-26 11:45:06 -0400303 expected_pattern = input_api.re.compile(
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000304 r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400305 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400306 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400307 if not f.LocalPath().endswith('.png'):
308 continue
309 if expected_pattern.match(f.LocalPath()):
310 continue
311 results.append(output_api.PresubmitError(
312 'PNG file %s does not have the correct format' % f.LocalPath()))
313 return results
dsinclair2ca2da52016-09-13 18:10:34 -0700314
Nico Weber077f1a32015-08-06 15:08:57 -0700315def CheckChangeOnUpload(input_api, output_api):
K Moon8de7d57d2019-12-05 19:32:33 +0000316 cpp_source_filter = lambda x: input_api.FilterSourceFile(
317 x, white_list=(r'\.(?:c|cc|cpp|h)$',))
318
Nico Weber077f1a32015-08-06 15:08:57 -0700319 results = []
Lei Zhang804a0e32020-05-21 20:57:16 +0000320 results.extend(_CheckUnwantedDependencies(input_api, output_api))
321 results.extend(
322 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
323 results.extend(
324 input_api.canned_checks.CheckChangeLintsClean(input_api, output_api,
325 cpp_source_filter,
326 LINT_FILTERS))
327 results.extend(_CheckIncludeOrder(input_api, output_api))
328 results.extend(_CheckTestDuplicates(input_api, output_api))
329 results.extend(_CheckPNGFormat(input_api, output_api))
Dan Sinclair544bbc62016-03-14 15:07:39 -0400330
Lei Zhang71e26732020-05-21 21:03:05 +0000331 author = input_api.change.author_email
332 if author and author not in _KNOWN_ROBOTS:
333 results.extend(
334 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
335
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000336 for f in input_api.AffectedFiles():
337 path, name = input_api.os_path.split(f.LocalPath())
338 if name == 'PRESUBMIT.py':
339 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
340 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
341 if f.Action() != 'D' and input_api.os_path.exists(test_file):
342 # The PRESUBMIT.py file (and the directory containing it) might
343 # have been affected by being moved or removed, so only try to
344 # run the tests if they still exist.
345 results.extend(
346 input_api.canned_checks.RunUnitTestsInDirectory(
347 input_api,
348 output_api,
349 full_path,
350 whitelist=[r'^PRESUBMIT_test\.py$']))
351
Nico Weber077f1a32015-08-06 15:08:57 -0700352 return results