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[\\\/].*",
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
)
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
):
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',
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.
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(
88 white_list
=(file_inclusion_pattern
, ),
89 black_list
=black_list
)
92 for f
in input_api
.AffectedSourceFiles(FilterFile
):
93 local_path
= f
.LocalPath()
94 lines
= input_api
.ReadFile(f
).splitlines()
97 if (inclusion_pattern
.search(line
) and
98 not exclusion_pattern
.search(line
)):
100 '%s:%d\n %s' % (local_path
, line_number
, line
.strip()))
104 if not input_api
.is_committing
:
105 return [output_api
.PresubmitPromptWarning(_TEST_ONLY_WARNING
, problems
)]
107 # We don't warn on commit, to avoid stopping commits going through CQ.
108 return [output_api
.PresubmitNotifyResult(_TEST_ONLY_WARNING
, problems
)]
113 def _CheckNoIOStreamInHeaders(input_api
, output_api
):
114 """Checks to make sure no .h files include <iostream>."""
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'):
121 contents
= input_api
.ReadFile(f
)
122 if pattern
.search(contents
):
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',
134 def _CheckNoNewWStrings(input_api
, output_api
):
135 """Checks to make sure we don't introduce use of wstrings."""
137 for f
in input_api
.AffectedFiles():
138 if (not f
.LocalPath().endswith(('.cc', '.h')) or
139 f
.LocalPath().endswith('test.cc')):
142 for line_num
, line
in f
.ChangedContents():
143 if 'wstring' in line
:
144 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
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'
161 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
162 'for more information')]
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."""
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
))
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
)
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()):
203 for line_num
, line
in f
.ChangedContents():
204 if HasOldCallbackKeywords(line
):
205 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
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."""
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
))
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
))
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', '')
240 return [output_api
.PresubmitError('%APPDATA% is not configured.')]
241 path
= join(appdata
, 'Subversion', 'config')
243 home
= input_api
.environ
.get('HOME', '')
245 return [output_api
.PresubmitError('$HOME is not configured.')]
246 path
= join(home
, '.subversion', 'config')
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')
256 lines
= open(path
, 'r').read().splitlines()
257 # Make sure auto-props is enabled and check for 2 Chromium standard
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
):
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):
269 output_api
.PresubmitNotifyResult(
270 'Can\'t find your subversion config file.\n' + error_msg
)
275 def _CheckAuthorizedAuthor(input_api
, output_api
):
276 """For non-googler/chromites committers, verify the author's email address is
279 # TODO(maruel): Add it to input_api?
282 author
= input_api
.change
.author_email
284 input_api
.logging
.info('No author, skipping AUTHOR check')
286 authors_path
= input_api
.os_path
.join(
287 input_api
.PresubmitLocalPath(), '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'
298 'http://www.chromium.org/developers/contributing-code and read the '
300 'If you are a chromite, verify the contributor signed the CLA.') %
305 def CheckChangeOnUpload(input_api
, output_api
):
307 results
.extend(_CommonChecks(input_api
, output_api
))
311 def CheckChangeOnCommit(input_api
, output_api
):
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(
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
))
336 def GetPreferredTrySlaves(project
, change
):
337 affected_files
= change
.LocalPaths()
338 only_objc_files
= all(f
.endswith(('.mm', '.m')) for f
in affected_files
)
341 preferred
= ['win_rel', 'linux_rel', 'mac_rel']
342 if any(f
.endswith(('.h', '.cc', '.cpp', '.cxx')) for f
in affected_files
):
343 preferred
.append('linux_clang')
344 aura_re
= '_aura[^/]*[.][^/]*'
345 if any(re
.search(aura_re
, f
) for f
in affected_files
):
346 preferred
.append('linux_chromeos')
347 # For bringup (staging of upstream work) we must be careful to not
348 # overload Android infrastructure. Keeping Android try decisions in a
349 # single location (instead of adding conditionals in base/, net/, ...)
350 # will help us avoid doing so. For example, we are starting off with
351 # 2 trybots (compared against ~45 for Mac and Linux).
352 # If any file matches something compiled on the main waterfall
353 # android builder, use the android try server.
354 android_re_list
= ('^base/', '^ipc/', '^net/', '^sql/', '^jingle/',
355 '^build/common.gypi$')
356 for f
in affected_files
:
357 if any(re
.search(r
, f
) for r
in android_re_list
):
358 preferred
.append('android')