Updating trunk VERSION from 1004.0 to 1005.0
[chromium-blink-merge.git] / PRESUBMIT.py
blob06c92efa89f1cdef939cd6818d8f0ee615b9042e
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 _CheckNoNewOldCallback(input_api, output_api):
186 """Checks to make sure we don't introduce new uses of old callbacks."""
188 def HasOldCallbackKeywords(line):
189 """Returns True if a line of text contains keywords that indicate the use
190 of the old callback system.
192 return ('NewRunnableMethod' in line or
193 'NewCallback' in line or
194 input_api.re.search(r'\bCallback\d<', line) or
195 input_api.re.search(r'\bpublic Task\b', line) or
196 'public CancelableTask' in line)
198 problems = []
199 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
200 for f in input_api.AffectedFiles(file_filter=file_filter):
201 if not any(HasOldCallbackKeywords(line) for line in f.NewContents()):
202 continue
203 for line_num, line in f.ChangedContents():
204 if HasOldCallbackKeywords(line):
205 problems.append(' %s:%d' % (f.LocalPath(), line_num))
207 if not problems:
208 return []
209 return [output_api.PresubmitPromptWarning('The old callback system is '
210 'deprecated. If possible, use base::Bind and base::Callback instead.\n' +
211 '\n'.join(problems))]
214 def _CommonChecks(input_api, output_api):
215 """Checks common to both upload and commit."""
216 results = []
217 results.extend(input_api.canned_checks.PanProjectChecks(
218 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
219 results.extend(_CheckNoInterfacesInBase(input_api, output_api))
220 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
221 results.extend(
222 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
223 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
224 results.extend(_CheckNoNewWStrings(input_api, output_api))
225 results.extend(_CheckNoDEPSGIT(input_api, output_api))
226 results.extend(_CheckNoFRIEND_TEST(input_api, output_api))
227 results.extend(_CheckNoNewOldCallback(input_api, output_api))
228 return results
231 def _CheckSubversionConfig(input_api, output_api):
232 """Verifies the subversion config file is correctly setup.
234 Checks that autoprops are enabled, returns an error otherwise.
236 join = input_api.os_path.join
237 if input_api.platform == 'win32':
238 appdata = input_api.environ.get('APPDATA', '')
239 if not appdata:
240 return [output_api.PresubmitError('%APPDATA% is not configured.')]
241 path = join(appdata, 'Subversion', 'config')
242 else:
243 home = input_api.environ.get('HOME', '')
244 if not home:
245 return [output_api.PresubmitError('$HOME is not configured.')]
246 path = join(home, '.subversion', 'config')
248 error_msg = (
249 'Please look at http://dev.chromium.org/developers/coding-style to\n'
250 'configure your subversion configuration file. This enables automatic\n'
251 'properties to simplify the project maintenance.\n'
252 'Pro-tip: just download and install\n'
253 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
255 try:
256 lines = open(path, 'r').read().splitlines()
257 # Make sure auto-props is enabled and check for 2 Chromium standard
258 # auto-prop.
259 if (not '*.cc = svn:eol-style=LF' in lines or
260 not '*.pdf = svn:mime-type=application/pdf' in lines or
261 not 'enable-auto-props = yes' in lines):
262 return [
263 output_api.PresubmitNotifyResult(
264 'It looks like you have not configured your subversion config '
265 'file or it is not up-to-date.\n' + error_msg)
267 except (OSError, IOError):
268 return [
269 output_api.PresubmitNotifyResult(
270 'Can\'t find your subversion config file.\n' + error_msg)
272 return []
275 def _CheckAuthorizedAuthor(input_api, output_api):
276 """For non-googler/chromites committers, verify the author's email address is
277 in AUTHORS.
279 # TODO(maruel): Add it to input_api?
280 import fnmatch
282 author = input_api.change.author_email
283 if not author:
284 input_api.logging.info('No author, skipping AUTHOR check')
285 return []
286 authors_path = input_api.os_path.join(
287 input_api.PresubmitLocalPath(), 'AUTHORS')
288 valid_authors = (
289 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
290 for line in open(authors_path))
291 valid_authors = [item.group(1).lower() for item in valid_authors if item]
292 if input_api.verbose:
293 print 'Valid authors are %s' % ', '.join(valid_authors)
294 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
295 return [output_api.PresubmitPromptWarning(
296 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
297 '\n'
298 'http://www.chromium.org/developers/contributing-code and read the '
299 '"Legal" section\n'
300 'If you are a chromite, verify the contributor signed the CLA.') %
301 author)]
302 return []
305 def CheckChangeOnUpload(input_api, output_api):
306 results = []
307 results.extend(_CommonChecks(input_api, output_api))
308 return results
311 def CheckChangeOnCommit(input_api, output_api):
312 results = []
313 results.extend(_CommonChecks(input_api, output_api))
314 # TODO(thestig) temporarily disabled, doesn't work in third_party/
315 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
316 # input_api, output_api, sources))
317 # Make sure the tree is 'open'.
318 results.extend(input_api.canned_checks.CheckTreeIsOpen(
319 input_api,
320 output_api,
321 json_url='http://chromium-status.appspot.com/current?format=json'))
322 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
323 output_api, 'http://codereview.chromium.org',
324 ('win_rel', 'linux_rel', 'mac_rel'), 'tryserver@chromium.org'))
326 results.extend(input_api.canned_checks.CheckChangeHasBugField(
327 input_api, output_api))
328 results.extend(input_api.canned_checks.CheckChangeHasTestField(
329 input_api, output_api))
330 results.extend(input_api.canned_checks.CheckChangeHasDescription(
331 input_api, output_api))
332 results.extend(_CheckSubversionConfig(input_api, output_api))
333 return results
336 def GetPreferredTrySlaves(project, change):
337 only_objc_files = all(
338 f.LocalPath().endswith(('.mm', '.m')) for f in change.AffectedFiles())
339 if only_objc_files:
340 return ['mac_rel']
341 preferred = ['win_rel', 'linux_rel', 'mac_rel']
342 aura_re = '_aura[^/]*[.][^/]*'
343 if any(re.search(aura_re, f.LocalPath()) for f in change.AffectedFiles()):
344 preferred.append('linux_chromeos_aura:compile')
345 return preferred