blob: a7db1230d4c60060fa97eec2c58de61639542bd7 [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
Dan Sinclair544bbc62016-03-14 15:07:39 -040037def _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
dsinclair4cd49e12016-04-05 10:28:48 -070053 except ImportError:
54 return [output_api.PresubmitError(
55 'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
Dan Sinclair544bbc62016-03-14 15:07:39 -040056 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
dsinclair2ca2da52016-09-13 18:10:34 -070094def _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):
dsinclaird33c8e32016-11-21 13:31:16 -0800242 if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
dsinclair2ca2da52016-09-13 18:10:34 -0700243 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
Nicolas Penad824a902017-05-19 15:59:49 -0400252def _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
Nicolas Penac4479c52017-06-26 11:45:06 -0400270 path = f.LocalPath()[:-end_len]
Nicolas Penad824a902017-05-19 15:59:49 -0400271 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
Nicolas Penac4479c52017-06-26 11:45:06 -0400279def _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
Nicolas Penad261b062017-06-26 16:54:43 -0400282 NAME_expected(_(win|mac|linux))?.pdf.#.png"""
Nicolas Penac4479c52017-06-26 11:45:06 -0400283 expected_pattern = input_api.re.compile(
Nicolas Penad261b062017-06-26 16:54:43 -0400284 r'.+_expected(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400285 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400286 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400287 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
dsinclair2ca2da52016-09-13 18:10:34 -0700294
Nico Weber077f1a32015-08-06 15:08:57 -0700295def CheckChangeOnUpload(input_api, output_api):
296 results = []
Dan Sinclair544bbc62016-03-14 15:07:39 -0400297 results += _CheckUnwantedDependencies(input_api, output_api)
Nico Weber077f1a32015-08-06 15:08:57 -0700298 results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
Dan Sinclair22d66072016-02-22 11:56:05 -0500299 results += input_api.canned_checks.CheckChangeLintsClean(
300 input_api, output_api, None, LINT_FILTERS)
dsinclair2ca2da52016-09-13 18:10:34 -0700301 results += _CheckIncludeOrder(input_api, output_api)
Nicolas Penad824a902017-05-19 15:59:49 -0400302 results += _CheckTestDuplicates(input_api, output_api)
Nicolas Penac4479c52017-06-26 11:45:06 -0400303 results += _CheckPNGFormat(input_api, output_api)
Dan Sinclair544bbc62016-03-14 15:07:39 -0400304
Nico Weber077f1a32015-08-06 15:08:57 -0700305 return results