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.
17 r
"^native_client_sdk[\\\/].*",
18 r
"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
26 _TEST_ONLY_WARNING
= (
27 'You might be calling functions intended only for testing from\n'
28 'production code. It is OK to ignore this warning if you know what\n'
29 'you are doing, as the heuristics used to detect the situation are\n'
30 'not perfect. The commit queue will not block on this warning.\n'
31 'Email joi@chromium.org if you have questions.')
35 def _CheckNoInterfacesInBase(input_api
, output_api
):
36 """Checks to make sure no files in libbase.a have |@interface|."""
37 pattern
= input_api
.re
.compile(r
'^\s*@interface', input_api
.re
.MULTILINE
)
39 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
40 if (f
.LocalPath().startswith('base/') and
41 not f
.LocalPath().endswith('_unittest.mm')):
42 contents
= input_api
.ReadFile(f
)
43 if pattern
.search(contents
):
47 return [ output_api
.PresubmitError(
48 'Objective-C interfaces or categories are forbidden in libbase. ' +
49 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' +
50 'browse_thread/thread/efb28c10435987fd',
55 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
):
56 """Attempts to prevent use of functions intended only for testing in
57 non-testing code. For now this is just a best-effort implementation
58 that ignores header files and may have some false positives. A
59 better implementation would probably need a proper C++ parser.
61 # We only scan .cc files and the like, as the declaration of
62 # for-testing functions in header files are hard to distinguish from
63 # calls to such functions without a proper C++ parser.
64 source_extensions
= r
'\.(cc|cpp|cxx|mm)$'
65 file_inclusion_pattern
= r
'.+%s' % source_extensions
66 file_exclusion_patterns
= (
67 r
'.*[/\\](test_|mock_).+%s' % source_extensions
,
68 r
'.+_test_(base|support|util)%s' % source_extensions
,
69 r
'.+_(api|browser|perf|unit|ui)?test%s' % source_extensions
,
70 r
'.+profile_sync_service_harness%s' % source_extensions
,
72 path_exclusion_patterns
= (
73 r
'.*[/\\](test|tool(s)?)[/\\].*',
74 # At request of folks maintaining this folder.
75 r
'chrome[/\\]browser[/\\]automation[/\\].*',
78 base_function_pattern
= r
'ForTest(ing)?|for_test(ing)?'
79 inclusion_pattern
= input_api
.re
.compile(r
'(%s)\s*\(' % base_function_pattern
)
80 exclusion_pattern
= input_api
.re
.compile(
81 r
'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
82 base_function_pattern
, base_function_pattern
))
84 def FilterFile(affected_file
):
85 black_list
= (file_exclusion_patterns
+ path_exclusion_patterns
+
86 _EXCLUDED_PATHS
+ input_api
.DEFAULT_BLACK_LIST
)
87 return input_api
.FilterSourceFile(
89 white_list
=(file_inclusion_pattern
, ),
90 black_list
=black_list
)
93 for f
in input_api
.AffectedSourceFiles(FilterFile
):
94 local_path
= f
.LocalPath()
95 lines
= input_api
.ReadFile(f
).splitlines()
98 if (inclusion_pattern
.search(line
) and
99 not exclusion_pattern
.search(line
)):
101 '%s:%d\n %s' % (local_path
, line_number
, line
.strip()))
105 if not input_api
.is_committing
:
106 return [output_api
.PresubmitPromptWarning(_TEST_ONLY_WARNING
, problems
)]
108 # We don't warn on commit, to avoid stopping commits going through CQ.
109 return [output_api
.PresubmitNotifyResult(_TEST_ONLY_WARNING
, problems
)]
114 def _CheckNoIOStreamInHeaders(input_api
, output_api
):
115 """Checks to make sure no .h files include <iostream>."""
117 pattern
= input_api
.re
.compile(r
'^#include\s*<iostream>',
118 input_api
.re
.MULTILINE
)
119 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
120 if not f
.LocalPath().endswith('.h'):
122 contents
= input_api
.ReadFile(f
)
123 if pattern
.search(contents
):
127 return [ output_api
.PresubmitError(
128 'Do not #include <iostream> in header files, since it inserts static ' +
129 'initialization into every file including the header. Instead, ' +
130 '#include <ostream>. See http://crbug.com/94794',
135 def _CheckNoNewWStrings(input_api
, output_api
):
136 """Checks to make sure we don't introduce use of wstrings."""
138 for f
in input_api
.AffectedFiles():
139 if (not f
.LocalPath().endswith(('.cc', '.h')) or
140 f
.LocalPath().endswith('test.cc')):
143 for line_num
, line
in f
.ChangedContents():
144 if 'wstring' in line
:
145 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
149 return [output_api
.PresubmitPromptWarning('New code should not use wstrings.'
150 ' If you are calling an API that accepts a wstring, fix the API.\n' +
151 '\n'.join(problems
))]
154 def _CheckNoDEPSGIT(input_api
, output_api
):
155 """Make sure .DEPS.git is never modified manually."""
156 if any(f
.LocalPath().endswith('.DEPS.git') for f
in
157 input_api
.AffectedFiles()):
158 return [output_api
.PresubmitError(
159 'Never commit changes to .DEPS.git. This file is maintained by an\n'
160 'automated system based on what\'s in DEPS and your changes will be\n'
162 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
163 'for more information')]
167 def _CheckNoFRIEND_TEST(input_api
, output_api
):
168 """Make sure that gtest's FRIEND_TEST() macro is not used, the
169 FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be used
170 instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
173 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.h'))
174 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
175 for line_num
, line
in f
.ChangedContents():
176 if 'FRIEND_TEST(' in line
:
177 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
181 return [output_api
.PresubmitPromptWarning('Chromium code should not use '
182 'gtest\'s FRIEND_TEST() macro. Include base/gtest_prod_util.h and use '
183 'FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems
))]
186 def _CheckNoScopedAllowIO(input_api
, output_api
):
187 """Make sure that ScopedAllowIO is not used."""
190 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.h'))
191 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
192 for line_num
, line
in f
.ChangedContents():
193 if 'ScopedAllowIO' in line
:
194 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
198 return [output_api
.PresubmitPromptWarning('New code should not use '
199 'ScopedAllowIO. Post a task to the blocking pool or the FILE thread '
200 'instead.\n' + '\n'.join(problems
))]
203 def _CheckNoFilePathWatcherDelegate(input_api
, output_api
):
204 """Make sure that FilePathWatcher::Delegate is not used."""
207 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.h'))
208 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
209 for line_num
, line
in f
.ChangedContents():
210 if 'FilePathWatcher::Delegate' in line
:
211 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
215 return [output_api
.PresubmitPromptWarning('New code should not use '
216 'FilePathWatcher::Delegate. Use the callback interface instead.\n' +
217 '\n'.join(problems
))]
220 def _CommonChecks(input_api
, output_api
):
221 """Checks common to both upload and commit."""
223 results
.extend(input_api
.canned_checks
.PanProjectChecks(
224 input_api
, output_api
, excluded_paths
=_EXCLUDED_PATHS
))
225 results
.extend(_CheckNoInterfacesInBase(input_api
, output_api
))
226 results
.extend(_CheckAuthorizedAuthor(input_api
, output_api
))
228 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
))
229 results
.extend(_CheckNoIOStreamInHeaders(input_api
, output_api
))
230 results
.extend(_CheckNoNewWStrings(input_api
, output_api
))
231 results
.extend(_CheckNoDEPSGIT(input_api
, output_api
))
232 results
.extend(_CheckNoFRIEND_TEST(input_api
, output_api
))
233 results
.extend(_CheckNoScopedAllowIO(input_api
, output_api
))
234 results
.extend(_CheckNoFilePathWatcherDelegate(input_api
, output_api
))
238 def _CheckSubversionConfig(input_api
, output_api
):
239 """Verifies the subversion config file is correctly setup.
241 Checks that autoprops are enabled, returns an error otherwise.
243 join
= input_api
.os_path
.join
244 if input_api
.platform
== 'win32':
245 appdata
= input_api
.environ
.get('APPDATA', '')
247 return [output_api
.PresubmitError('%APPDATA% is not configured.')]
248 path
= join(appdata
, 'Subversion', 'config')
250 home
= input_api
.environ
.get('HOME', '')
252 return [output_api
.PresubmitError('$HOME is not configured.')]
253 path
= join(home
, '.subversion', 'config')
256 'Please look at http://dev.chromium.org/developers/coding-style to\n'
257 'configure your subversion configuration file. This enables automatic\n'
258 'properties to simplify the project maintenance.\n'
259 'Pro-tip: just download and install\n'
260 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
263 lines
= open(path
, 'r').read().splitlines()
264 # Make sure auto-props is enabled and check for 2 Chromium standard
266 if (not '*.cc = svn:eol-style=LF' in lines
or
267 not '*.pdf = svn:mime-type=application/pdf' in lines
or
268 not 'enable-auto-props = yes' in lines
):
270 output_api
.PresubmitNotifyResult(
271 'It looks like you have not configured your subversion config '
272 'file or it is not up-to-date.\n' + error_msg
)
274 except (OSError, IOError):
276 output_api
.PresubmitNotifyResult(
277 'Can\'t find your subversion config file.\n' + error_msg
)
282 def _CheckAuthorizedAuthor(input_api
, output_api
):
283 """For non-googler/chromites committers, verify the author's email address is
286 # TODO(maruel): Add it to input_api?
289 author
= input_api
.change
.author_email
291 input_api
.logging
.info('No author, skipping AUTHOR check')
293 authors_path
= input_api
.os_path
.join(
294 input_api
.PresubmitLocalPath(), 'AUTHORS')
296 input_api
.re
.match(r
'[^#]+\s+\<(.+?)\>\s*$', line
)
297 for line
in open(authors_path
))
298 valid_authors
= [item
.group(1).lower() for item
in valid_authors
if item
]
299 if input_api
.verbose
:
300 print 'Valid authors are %s' % ', '.join(valid_authors
)
301 if not any(fnmatch
.fnmatch(author
.lower(), valid
) for valid
in valid_authors
):
302 return [output_api
.PresubmitPromptWarning(
303 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
305 'http://www.chromium.org/developers/contributing-code and read the '
307 'If you are a chromite, verify the contributor signed the CLA.') %
312 def CheckChangeOnUpload(input_api
, output_api
):
314 results
.extend(_CommonChecks(input_api
, output_api
))
318 def CheckChangeOnCommit(input_api
, output_api
):
320 results
.extend(_CommonChecks(input_api
, output_api
))
321 # TODO(thestig) temporarily disabled, doesn't work in third_party/
322 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
323 # input_api, output_api, sources))
324 # Make sure the tree is 'open'.
325 results
.extend(input_api
.canned_checks
.CheckTreeIsOpen(
328 json_url
='http://chromium-status.appspot.com/current?format=json'))
329 results
.extend(input_api
.canned_checks
.CheckRietveldTryJobExecution(input_api
,
330 output_api
, 'http://codereview.chromium.org',
331 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
332 'tryserver@chromium.org'))
334 results
.extend(input_api
.canned_checks
.CheckChangeHasBugField(
335 input_api
, output_api
))
336 results
.extend(input_api
.canned_checks
.CheckChangeHasTestField(
337 input_api
, output_api
))
338 results
.extend(input_api
.canned_checks
.CheckChangeHasDescription(
339 input_api
, output_api
))
340 results
.extend(_CheckSubversionConfig(input_api
, output_api
))
344 def GetPreferredTrySlaves(project
, change
):
345 affected_files
= change
.LocalPaths()
346 only_objc_files
= all(f
.endswith(('.mm', '.m')) for f
in affected_files
)
349 preferred
= ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile']
350 aura_re
= '_aura[^/]*[.][^/]*'
351 if any(re
.search(aura_re
, f
) for f
in affected_files
):
352 preferred
.append('linux_chromeos')
354 android_re_list
= ('^base/',
355 '^build/common.gypi$',
362 # Nothing that looks like win-only or aura-only
363 win_re
= '_win\.(cc|h)$'
364 possibly_android
= True
365 for non_android_re
in (aura_re
, win_re
):
366 if all(re
.search(non_android_re
, f
) for f
in affected_files
):
367 possibly_android
= False
370 for f
in change
.AffectedFiles():
371 if any(re
.search(r
, f
.LocalPath()) for r
in android_re_list
):
372 preferred
.append('android')