blob: 833d0925d7121d67103a15900194a3d5649d75e0 [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
Nicolas Penac4479c52017-06-26 11:45:06 -0400296def _CheckPNGFormat(input_api, output_api):
297 """Checks that .png files have a format that will be considered valid by our
298 test runners. If a file ends with .png, then it must be of the form
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000299 NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png"""
Nicolas Penac4479c52017-06-26 11:45:06 -0400300 expected_pattern = input_api.re.compile(
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000301 r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400302 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400303 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400304 if not f.LocalPath().endswith('.png'):
305 continue
306 if expected_pattern.match(f.LocalPath()):
307 continue
308 results.append(output_api.PresubmitError(
309 'PNG file %s does not have the correct format' % f.LocalPath()))
310 return results
dsinclair2ca2da52016-09-13 18:10:34 -0700311
Nico Weber077f1a32015-08-06 15:08:57 -0700312def CheckChangeOnUpload(input_api, output_api):
K Moon8de7d57d2019-12-05 19:32:33 +0000313 cpp_source_filter = lambda x: input_api.FilterSourceFile(
Lei Zhang80872892020-08-25 19:38:18 +0000314 x, files_to_check=(r'\.(?:c|cc|cpp|h)$',))
K Moon8de7d57d2019-12-05 19:32:33 +0000315
Nico Weber077f1a32015-08-06 15:08:57 -0700316 results = []
Lei Zhang804a0e32020-05-21 20:57:16 +0000317 results.extend(_CheckUnwantedDependencies(input_api, output_api))
318 results.extend(
319 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
320 results.extend(
321 input_api.canned_checks.CheckChangeLintsClean(input_api, output_api,
322 cpp_source_filter,
323 LINT_FILTERS))
324 results.extend(_CheckIncludeOrder(input_api, output_api))
325 results.extend(_CheckTestDuplicates(input_api, output_api))
326 results.extend(_CheckPNGFormat(input_api, output_api))
Dan Sinclair544bbc62016-03-14 15:07:39 -0400327
Lei Zhang71e26732020-05-21 21:03:05 +0000328 author = input_api.change.author_email
329 if author and author not in _KNOWN_ROBOTS:
330 results.extend(
331 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
332
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000333 for f in input_api.AffectedFiles():
334 path, name = input_api.os_path.split(f.LocalPath())
335 if name == 'PRESUBMIT.py':
336 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
337 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
338 if f.Action() != 'D' and input_api.os_path.exists(test_file):
339 # The PRESUBMIT.py file (and the directory containing it) might
340 # have been affected by being moved or removed, so only try to
341 # run the tests if they still exist.
342 results.extend(
343 input_api.canned_checks.RunUnitTestsInDirectory(
344 input_api,
345 output_api,
346 full_path,
Lei Zhang95c61432020-08-27 22:45:08 +0000347 files_to_check=[r'^PRESUBMIT_test\.py$']))
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000348
Nico Weber077f1a32015-08-06 15:08:57 -0700349 return results