WebKit roll 108512:108599.
[chromium-blink-merge.git] / PRESUBMIT.py
blobd0305eea199bf2f8dc333f80b3f85b4424a9e4fb
1 # Copyright (c) 2012 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.
5 """Top-level presubmit script for Chromium.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into gcl.
9 """
12 import re
15 _EXCLUDED_PATHS = (
16 r"^breakpad[\\\/].*",
17 r"^native_client_sdk[\\\/].*",
18 r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
19 r"^skia[\\\/].*",
20 r"^v8[\\\/].*",
21 r".*MakeFile$",
25 _TEST_ONLY_WARNING = (
26 'You might be calling functions intended only for testing from\n'
27 'production code. It is OK to ignore this warning if you know what\n'
28 'you are doing, as the heuristics used to detect the situation are\n'
29 'not perfect. The commit queue will not block on this warning.\n'
30 'Email joi@chromium.org if you have questions.')
34 def _CheckNoInterfacesInBase(input_api, output_api):
35 """Checks to make sure no files in libbase.a have |@interface|."""
36 pattern = input_api.re.compile(r'^\s*@interface', input_api.re.MULTILINE)
37 files = []
38 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
39 if (f.LocalPath().startswith('base/') and
40 not f.LocalPath().endswith('_unittest.mm')):
41 contents = input_api.ReadFile(f)
42 if pattern.search(contents):
43 files.append(f)
45 if len(files):
46 return [ output_api.PresubmitError(
47 'Objective-C interfaces or categories are forbidden in libbase. ' +
48 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' +
49 'browse_thread/thread/efb28c10435987fd',
50 files) ]
51 return []
54 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
55 """Attempts to prevent use of functions intended only for testing in
56 non-testing code. For now this is just a best-effort implementation
57 that ignores header files and may have some false positives. A
58 better implementation would probably need a proper C++ parser.
59 """
60 # We only scan .cc files and the like, as the declaration of
61 # for-testing functions in header files are hard to distinguish from
62 # calls to such functions without a proper C++ parser.
63 source_extensions = r'\.(cc|cpp|cxx|mm)$'
64 file_inclusion_pattern = r'.+%s' % source_extensions
65 file_exclusion_patterns = (
66 r'.*[/\\](test_|mock_).+%s' % source_extensions,
67 r'.+_test_(support|base)%s' % source_extensions,
68 r'.+_(api|browser|perf|unit|ui)?test%s' % source_extensions,
69 r'.+profile_sync_service_harness%s' % source_extensions,
71 path_exclusion_patterns = (
72 r'.*[/\\](test|tool(s)?)[/\\].*',
73 # At request of folks maintaining this folder.
74 r'chrome[/\\]browser[/\\]automation[/\\].*',
77 base_function_pattern = r'ForTest(ing)?|for_test(ing)?'
78 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
79 exclusion_pattern = input_api.re.compile(
80 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
81 base_function_pattern, base_function_pattern))
83 def FilterFile(affected_file):
84 black_list = (file_exclusion_patterns + path_exclusion_patterns +
85 _EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
86 return input_api.FilterSourceFile(
87 affected_file,
88 white_list=(file_inclusion_pattern, ),
89 black_list=black_list)
91 problems = []
92 for f in input_api.AffectedSourceFiles(FilterFile):
93 local_path = f.LocalPath()
94 lines = input_api.ReadFile(f).splitlines()
95 line_number = 0
96 for line in lines:
97 if (inclusion_pattern.search(line) and
98 not exclusion_pattern.search(line)):
99 problems.append(
100 '%s:%d\n %s' % (local_path, line_number, line.strip()))
101 line_number += 1
103 if problems:
104 if not input_api.is_committing:
105 return [output_api.PresubmitPromptWarning(_TEST_ONLY_WARNING, problems)]
106 else:
107 # We don't warn on commit, to avoid stopping commits going through CQ.
108 return [output_api.PresubmitNotifyResult(_TEST_ONLY_WARNING, problems)]
109 else:
110 return []
113 def _CheckNoIOStreamInHeaders(input_api, output_api):
114 """Checks to make sure no .h files include <iostream>."""
115 files = []
116 pattern = input_api.re.compile(r'^#include\s*<iostream>',
117 input_api.re.MULTILINE)
118 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
119 if not f.LocalPath().endswith('.h'):
120 continue
121 contents = input_api.ReadFile(f)
122 if pattern.search(contents):
123 files.append(f)
125 if len(files):
126 return [ output_api.PresubmitError(
127 'Do not #include <iostream> in header files, since it inserts static ' +
128 'initialization into every file including the header. Instead, ' +
129 '#include <ostream>. See http://crbug.com/94794',
130 files) ]
131 return []
134 def _CheckNoNewWStrings(input_api, output_api):
135 """Checks to make sure we don't introduce use of wstrings."""
136 problems = []
137 for f in input_api.AffectedFiles():
138 if (not f.LocalPath().endswith(('.cc', '.h')) or
139 f.LocalPath().endswith('test.cc')):
140 continue
142 for line_num, line in f.ChangedContents():
143 if 'wstring' in line:
144 problems.append(' %s:%d' % (f.LocalPath(), line_num))
146 if not problems:
147 return []
148 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
149 ' If you are calling an API that accepts a wstring, fix the API.\n' +
150 '\n'.join(problems))]
153 def _CheckNoDEPSGIT(input_api, output_api):
154 """Make sure .DEPS.git is never modified manually."""
155 if any(f.LocalPath().endswith('.DEPS.git') for f in
156 input_api.AffectedFiles()):
157 return [output_api.PresubmitError(
158 'Never commit changes to .DEPS.git. This file is maintained by an\n'
159 'automated system based on what\'s in DEPS and your changes will be\n'
160 'overwritten.\n'
161 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
162 'for more information')]
163 return []
166 def _CheckNoFRIEND_TEST(input_api, output_api):
167 """Make sure that gtest's FRIEND_TEST() macro is not used, the
168 FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be used
169 instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
170 problems = []
172 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
173 for f in input_api.AffectedFiles(file_filter=file_filter):
174 for line_num, line in f.ChangedContents():
175 if 'FRIEND_TEST(' in line:
176 problems.append(' %s:%d' % (f.LocalPath(), line_num))
178 if not problems:
179 return []
180 return [output_api.PresubmitPromptWarning('Chromium code should not use '
181 'gtest\'s FRIEND_TEST() macro. Include base/gtest_prod_util.h and use '
182 'FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
185 def _CommonChecks(input_api, output_api):
186 """Checks common to both upload and commit."""
187 results = []
188 results.extend(input_api.canned_checks.PanProjectChecks(
189 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
190 results.extend(_CheckNoInterfacesInBase(input_api, output_api))
191 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
192 results.extend(
193 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
194 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
195 results.extend(_CheckNoNewWStrings(input_api, output_api))
196 results.extend(_CheckNoDEPSGIT(input_api, output_api))
197 results.extend(_CheckNoFRIEND_TEST(input_api, output_api))
198 return results
201 def _CheckSubversionConfig(input_api, output_api):
202 """Verifies the subversion config file is correctly setup.
204 Checks that autoprops are enabled, returns an error otherwise.
206 join = input_api.os_path.join
207 if input_api.platform == 'win32':
208 appdata = input_api.environ.get('APPDATA', '')
209 if not appdata:
210 return [output_api.PresubmitError('%APPDATA% is not configured.')]
211 path = join(appdata, 'Subversion', 'config')
212 else:
213 home = input_api.environ.get('HOME', '')
214 if not home:
215 return [output_api.PresubmitError('$HOME is not configured.')]
216 path = join(home, '.subversion', 'config')
218 error_msg = (
219 'Please look at http://dev.chromium.org/developers/coding-style to\n'
220 'configure your subversion configuration file. This enables automatic\n'
221 'properties to simplify the project maintenance.\n'
222 'Pro-tip: just download and install\n'
223 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
225 try:
226 lines = open(path, 'r').read().splitlines()
227 # Make sure auto-props is enabled and check for 2 Chromium standard
228 # auto-prop.
229 if (not '*.cc = svn:eol-style=LF' in lines or
230 not '*.pdf = svn:mime-type=application/pdf' in lines or
231 not 'enable-auto-props = yes' in lines):
232 return [
233 output_api.PresubmitNotifyResult(
234 'It looks like you have not configured your subversion config '
235 'file or it is not up-to-date.\n' + error_msg)
237 except (OSError, IOError):
238 return [
239 output_api.PresubmitNotifyResult(
240 'Can\'t find your subversion config file.\n' + error_msg)
242 return []
245 def _CheckAuthorizedAuthor(input_api, output_api):
246 """For non-googler/chromites committers, verify the author's email address is
247 in AUTHORS.
249 # TODO(maruel): Add it to input_api?
250 import fnmatch
252 author = input_api.change.author_email
253 if not author:
254 input_api.logging.info('No author, skipping AUTHOR check')
255 return []
256 authors_path = input_api.os_path.join(
257 input_api.PresubmitLocalPath(), 'AUTHORS')
258 valid_authors = (
259 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
260 for line in open(authors_path))
261 valid_authors = [item.group(1).lower() for item in valid_authors if item]
262 if input_api.verbose:
263 print 'Valid authors are %s' % ', '.join(valid_authors)
264 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
265 return [output_api.PresubmitPromptWarning(
266 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
267 '\n'
268 'http://www.chromium.org/developers/contributing-code and read the '
269 '"Legal" section\n'
270 'If you are a chromite, verify the contributor signed the CLA.') %
271 author)]
272 return []
275 def CheckChangeOnUpload(input_api, output_api):
276 results = []
277 results.extend(_CommonChecks(input_api, output_api))
278 return results
281 def CheckChangeOnCommit(input_api, output_api):
282 results = []
283 results.extend(_CommonChecks(input_api, output_api))
284 # TODO(thestig) temporarily disabled, doesn't work in third_party/
285 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
286 # input_api, output_api, sources))
287 # Make sure the tree is 'open'.
288 results.extend(input_api.canned_checks.CheckTreeIsOpen(
289 input_api,
290 output_api,
291 json_url='http://chromium-status.appspot.com/current?format=json'))
292 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
293 output_api, 'http://codereview.chromium.org',
294 ('win_rel', 'linux_rel', 'mac_rel'), 'tryserver@chromium.org'))
296 results.extend(input_api.canned_checks.CheckChangeHasBugField(
297 input_api, output_api))
298 results.extend(input_api.canned_checks.CheckChangeHasTestField(
299 input_api, output_api))
300 results.extend(input_api.canned_checks.CheckChangeHasDescription(
301 input_api, output_api))
302 results.extend(_CheckSubversionConfig(input_api, output_api))
303 return results
306 def GetPreferredTrySlaves(project, change):
307 affected_files = change.LocalPaths()
308 only_objc_files = all(f.endswith(('.mm', '.m')) for f in affected_files)
309 if only_objc_files:
310 return ['mac_rel']
311 preferred = ['win_rel', 'linux_rel', 'mac_rel']
312 if any(f.endswith(('.h', '.cc', '.cpp', '.cxx')) for f in affected_files):
313 preferred.append('linux_clang')
314 aura_re = '_aura[^/]*[.][^/]*'
315 if any(re.search(aura_re, f) for f in affected_files):
316 preferred.append('linux_chromeos')
317 # For bringup (staging of upstream work) we must be careful to not
318 # overload Android infrastructure. Keeping Android try decisions in a
319 # single location (instead of adding conditionals in base/, net/, ...)
320 # will help us avoid doing so. For example, we are starting off with
321 # 2 trybots (compared against ~45 for Mac and Linux).
322 # If any file matches something compiled on the main waterfall
323 # android builder, use the android try server.
324 android_re_list = ('^base/', '^ipc/', '^net/', '^sql/', '^jingle/',
325 '^build/common.gypi$')
326 for f in affected_files:
327 if any(re.search(r, f) for r in android_re_list):
328 preferred.append('android')
329 break
330 return preferred