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.
19 r
"^native_client_sdk[\\\/].*",
20 r
"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
29 _TEST_ONLY_WARNING
= (
30 'You might be calling functions intended only for testing from\n'
31 'production code. It is OK to ignore this warning if you know what\n'
32 'you are doing, as the heuristics used to detect the situation are\n'
33 'not perfect. The commit queue will not block on this warning.\n'
34 'Email joi@chromium.org if you have questions.')
37 _BANNED_OBJC_FUNCTIONS
= (
41 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
42 'prohibited. Please use CrTrackingArea instead.',
43 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
50 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
52 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
57 'convertPointFromBase:',
59 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
60 'Please use |convertPoint:(point) fromView:nil| instead.',
61 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
66 'convertPointToBase:',
68 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
69 'Please use |convertPoint:(point) toView:nil| instead.',
70 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
75 'convertRectFromBase:',
77 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
78 'Please use |convertRect:(point) fromView:nil| instead.',
79 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
86 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
87 'Please use |convertRect:(point) toView:nil| instead.',
88 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
93 'convertSizeFromBase:',
95 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
96 'Please use |convertSize:(point) fromView:nil| instead.',
97 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
102 'convertSizeToBase:',
104 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
105 'Please use |convertSize:(point) toView:nil| instead.',
106 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
113 _BANNED_CPP_FUNCTIONS
= (
114 # Make sure that gtest's FRIEND_TEST() macro is not used; the
115 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
116 # used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes.
120 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
121 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
128 'New code should not use ScopedAllowIO. Post a task to the blocking',
129 'pool or the FILE thread instead.',
134 'FilePathWatcher::Delegate',
136 'New code should not use FilePathWatcher::Delegate. Use the callback',
137 'interface instead.',
142 'browser::FindLastActiveWithProfile',
144 'This function is deprecated and we\'re working on removing it. Pass',
145 'more context to get a Browser*, like a WebContents, window, or session',
146 'id. Talk to ben@ or jam@ for more information.',
151 'browser::FindAnyBrowser',
153 'This function is deprecated and we\'re working on removing it. Pass',
154 'more context to get a Browser*, like a WebContents, window, or session',
155 'id. Talk to ben@ or jam@ for more information.',
160 'browser::FindOrCreateTabbedBrowser',
162 'This function is deprecated and we\'re working on removing it. Pass',
163 'more context to get a Browser*, like a WebContents, window, or session',
164 'id. Talk to ben@ or jam@ for more information.',
169 'browser::FindTabbedBrowser',
171 'This function is deprecated and we\'re working on removing it. Pass',
172 'more context to get a Browser*, like a WebContents, window, or session',
173 'id. Talk to ben@ or jam@ for more information.',
181 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
):
182 """Attempts to prevent use of functions intended only for testing in
183 non-testing code. For now this is just a best-effort implementation
184 that ignores header files and may have some false positives. A
185 better implementation would probably need a proper C++ parser.
187 # We only scan .cc files and the like, as the declaration of
188 # for-testing functions in header files are hard to distinguish from
189 # calls to such functions without a proper C++ parser.
190 platform_specifiers
= r
'(_(android|chromeos|gtk|mac|posix|win))?'
191 source_extensions
= r
'\.(cc|cpp|cxx|mm)$'
192 file_inclusion_pattern
= r
'.+%s' % source_extensions
193 file_exclusion_patterns
= (
194 r
'.*[/\\](test_|mock_).+%s' % source_extensions
,
195 r
'.+_test_(base|support|util)%s' % source_extensions
,
196 r
'.+_(api|browser|perf|unit|ui)?test%s%s' % (platform_specifiers
,
198 r
'.+profile_sync_service_harness%s' % source_extensions
,
200 path_exclusion_patterns
= (
201 r
'.*[/\\](test|tool(s)?)[/\\].*',
202 # At request of folks maintaining this folder.
203 r
'chrome[/\\]browser[/\\]automation[/\\].*',
206 base_function_pattern
= r
'ForTest(ing)?|for_test(ing)?'
207 inclusion_pattern
= input_api
.re
.compile(r
'(%s)\s*\(' % base_function_pattern
)
208 exclusion_pattern
= input_api
.re
.compile(
209 r
'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
210 base_function_pattern
, base_function_pattern
))
212 def FilterFile(affected_file
):
213 black_list
= (file_exclusion_patterns
+ path_exclusion_patterns
+
214 _EXCLUDED_PATHS
+ input_api
.DEFAULT_BLACK_LIST
)
215 return input_api
.FilterSourceFile(
217 white_list
=(file_inclusion_pattern
, ),
218 black_list
=black_list
)
221 for f
in input_api
.AffectedSourceFiles(FilterFile
):
222 local_path
= f
.LocalPath()
223 lines
= input_api
.ReadFile(f
).splitlines()
226 if (inclusion_pattern
.search(line
) and
227 not exclusion_pattern
.search(line
)):
229 '%s:%d\n %s' % (local_path
, line_number
, line
.strip()))
233 if not input_api
.is_committing
:
234 return [output_api
.PresubmitPromptWarning(_TEST_ONLY_WARNING
, problems
)]
236 # We don't warn on commit, to avoid stopping commits going through CQ.
237 return [output_api
.PresubmitNotifyResult(_TEST_ONLY_WARNING
, problems
)]
242 def _CheckNoIOStreamInHeaders(input_api
, output_api
):
243 """Checks to make sure no .h files include <iostream>."""
245 pattern
= input_api
.re
.compile(r
'^#include\s*<iostream>',
246 input_api
.re
.MULTILINE
)
247 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
248 if not f
.LocalPath().endswith('.h'):
250 contents
= input_api
.ReadFile(f
)
251 if pattern
.search(contents
):
255 return [ output_api
.PresubmitError(
256 'Do not #include <iostream> in header files, since it inserts static '
257 'initialization into every file including the header. Instead, '
258 '#include <ostream>. See http://crbug.com/94794',
263 def _CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
):
264 """Checks to make sure no source files use UNIT_TEST"""
266 for f
in input_api
.AffectedFiles():
267 if (not f
.LocalPath().endswith(('.cc', '.mm'))):
270 for line_num
, line
in f
.ChangedContents():
271 if 'UNIT_TEST' in line
:
272 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
276 return [output_api
.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
277 '\n'.join(problems
))]
280 def _CheckNoNewWStrings(input_api
, output_api
):
281 """Checks to make sure we don't introduce use of wstrings."""
283 for f
in input_api
.AffectedFiles():
284 if (not f
.LocalPath().endswith(('.cc', '.h')) or
285 f
.LocalPath().endswith('test.cc')):
289 for line_num
, line
in f
.ChangedContents():
290 if 'presubmit: allow wstring' in line
:
292 elif not allowWString
and 'wstring' in line
:
293 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
300 return [output_api
.PresubmitPromptWarning('New code should not use wstrings.'
301 ' If you are calling a cross-platform API that accepts a wstring, '
303 '\n'.join(problems
))]
306 def _CheckNoDEPSGIT(input_api
, output_api
):
307 """Make sure .DEPS.git is never modified manually."""
308 if any(f
.LocalPath().endswith('.DEPS.git') for f
in
309 input_api
.AffectedFiles()):
310 return [output_api
.PresubmitError(
311 'Never commit changes to .DEPS.git. This file is maintained by an\n'
312 'automated system based on what\'s in DEPS and your changes will be\n'
314 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
315 'for more information')]
319 def _CheckNoBannedFunctions(input_api
, output_api
):
320 """Make sure that banned functions are not used."""
324 file_filter
= lambda f
: f
.LocalPath().endswith(('.mm', '.m', '.h'))
325 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
326 for line_num
, line
in f
.ChangedContents():
327 for func_name
, message
, error
in _BANNED_OBJC_FUNCTIONS
:
328 if func_name
in line
:
332 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
333 for message_line
in message
:
334 problems
.append(' %s' % message_line
)
336 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.mm', '.h'))
337 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
338 for line_num
, line
in f
.ChangedContents():
339 for func_name
, message
, error
in _BANNED_CPP_FUNCTIONS
:
340 if func_name
in line
:
344 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
345 for message_line
in message
:
346 problems
.append(' %s' % message_line
)
350 result
.append(output_api
.PresubmitPromptWarning(
351 'Banned functions were used.\n' + '\n'.join(warnings
)))
353 result
.append(output_api
.PresubmitError(
354 'Banned functions were used.\n' + '\n'.join(errors
)))
358 def _CheckNoPragmaOnce(input_api
, output_api
):
359 """Make sure that banned functions are not used."""
361 pattern
= input_api
.re
.compile(r
'^#pragma\s+once',
362 input_api
.re
.MULTILINE
)
363 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
364 if not f
.LocalPath().endswith('.h'):
366 contents
= input_api
.ReadFile(f
)
367 if pattern
.search(contents
):
371 return [output_api
.PresubmitError(
372 'Do not use #pragma once in header files.\n'
373 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
378 def _CheckUnwantedDependencies(input_api
, output_api
):
379 """Runs checkdeps on #include statements added in this
380 change. Breaking - rules is an error, breaking ! rules is a
383 # We need to wait until we have an input_api object and use this
384 # roundabout construct to import checkdeps because this file is
385 # eval-ed and thus doesn't have __file__.
386 original_sys_path
= sys
.path
388 sys
.path
= sys
.path
+ [input_api
.os_path
.join(
389 input_api
.PresubmitLocalPath(), 'tools', 'checkdeps')]
391 from cpp_checker
import CppChecker
392 from rules
import Rule
394 # Restore sys.path to what it was before.
395 sys
.path
= original_sys_path
398 for f
in input_api
.AffectedFiles():
399 if not CppChecker
.IsCppFile(f
.LocalPath()):
402 changed_lines
= [line
for line_num
, line
in f
.ChangedContents()]
403 added_includes
.append([f
.LocalPath(), changed_lines
])
405 deps_checker
= checkdeps
.DepsChecker()
407 error_descriptions
= []
408 warning_descriptions
= []
409 for path
, rule_type
, rule_description
in deps_checker
.CheckAddedCppIncludes(
411 description_with_path
= '%s\n %s' % (path
, rule_description
)
412 if rule_type
== Rule
.DISALLOW
:
413 error_descriptions
.append(description_with_path
)
415 warning_descriptions
.append(description_with_path
)
418 if error_descriptions
:
419 results
.append(output_api
.PresubmitError(
420 'You added one or more #includes that violate checkdeps rules.',
422 if warning_descriptions
:
423 if not input_api
.is_committing
:
424 warning_factory
= output_api
.PresubmitPromptWarning
426 # We don't want to block use of the CQ when there is a warning
427 # of this kind, so we only show a message when committing.
428 warning_factory
= output_api
.PresubmitNotifyResult
429 results
.append(warning_factory(
430 'You added one or more #includes of files that are temporarily\n'
431 'allowed but being removed. Can you avoid introducing the\n'
432 '#include? See relevant DEPS file(s) for details and contacts.',
433 warning_descriptions
))
437 def _CheckFilePermissions(input_api
, output_api
):
438 """Check that all files have their permissions properly set."""
439 args
= [sys
.executable
, 'tools/checkperms/checkperms.py', '--root',
440 input_api
.change
.RepositoryRoot()]
441 for f
in input_api
.AffectedFiles():
442 args
+= ['--file', f
.LocalPath()]
444 (errors
, stderrdata
) = subprocess
.Popen(args
).communicate()
448 results
.append(output_api
.PreSubmitError('checkperms.py failed.',
453 def _CommonChecks(input_api
, output_api
):
454 """Checks common to both upload and commit."""
456 results
.extend(input_api
.canned_checks
.PanProjectChecks(
457 input_api
, output_api
, excluded_paths
=_EXCLUDED_PATHS
))
458 results
.extend(_CheckAuthorizedAuthor(input_api
, output_api
))
460 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
))
461 results
.extend(_CheckNoIOStreamInHeaders(input_api
, output_api
))
462 results
.extend(_CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
))
463 results
.extend(_CheckNoNewWStrings(input_api
, output_api
))
464 results
.extend(_CheckNoDEPSGIT(input_api
, output_api
))
465 results
.extend(_CheckNoBannedFunctions(input_api
, output_api
))
466 results
.extend(_CheckNoPragmaOnce(input_api
, output_api
))
467 results
.extend(_CheckUnwantedDependencies(input_api
, output_api
))
468 results
.extend(_CheckFilePermissions(input_api
, output_api
))
472 def _CheckSubversionConfig(input_api
, output_api
):
473 """Verifies the subversion config file is correctly setup.
475 Checks that autoprops are enabled, returns an error otherwise.
477 join
= input_api
.os_path
.join
478 if input_api
.platform
== 'win32':
479 appdata
= input_api
.environ
.get('APPDATA', '')
481 return [output_api
.PresubmitError('%APPDATA% is not configured.')]
482 path
= join(appdata
, 'Subversion', 'config')
484 home
= input_api
.environ
.get('HOME', '')
486 return [output_api
.PresubmitError('$HOME is not configured.')]
487 path
= join(home
, '.subversion', 'config')
490 'Please look at http://dev.chromium.org/developers/coding-style to\n'
491 'configure your subversion configuration file. This enables automatic\n'
492 'properties to simplify the project maintenance.\n'
493 'Pro-tip: just download and install\n'
494 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
497 lines
= open(path
, 'r').read().splitlines()
498 # Make sure auto-props is enabled and check for 2 Chromium standard
500 if (not '*.cc = svn:eol-style=LF' in lines
or
501 not '*.pdf = svn:mime-type=application/pdf' in lines
or
502 not 'enable-auto-props = yes' in lines
):
504 output_api
.PresubmitNotifyResult(
505 'It looks like you have not configured your subversion config '
506 'file or it is not up-to-date.\n' + error_msg
)
508 except (OSError, IOError):
510 output_api
.PresubmitNotifyResult(
511 'Can\'t find your subversion config file.\n' + error_msg
)
516 def _CheckAuthorizedAuthor(input_api
, output_api
):
517 """For non-googler/chromites committers, verify the author's email address is
520 # TODO(maruel): Add it to input_api?
523 author
= input_api
.change
.author_email
525 input_api
.logging
.info('No author, skipping AUTHOR check')
527 authors_path
= input_api
.os_path
.join(
528 input_api
.PresubmitLocalPath(), 'AUTHORS')
530 input_api
.re
.match(r
'[^#]+\s+\<(.+?)\>\s*$', line
)
531 for line
in open(authors_path
))
532 valid_authors
= [item
.group(1).lower() for item
in valid_authors
if item
]
533 if input_api
.verbose
:
534 print 'Valid authors are %s' % ', '.join(valid_authors
)
535 if not any(fnmatch
.fnmatch(author
.lower(), valid
) for valid
in valid_authors
):
536 return [output_api
.PresubmitPromptWarning(
537 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
539 'http://www.chromium.org/developers/contributing-code and read the '
541 'If you are a chromite, verify the contributor signed the CLA.') %
546 def CheckChangeOnUpload(input_api
, output_api
):
548 results
.extend(_CommonChecks(input_api
, output_api
))
552 def CheckChangeOnCommit(input_api
, output_api
):
554 results
.extend(_CommonChecks(input_api
, output_api
))
555 # TODO(thestig) temporarily disabled, doesn't work in third_party/
556 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
557 # input_api, output_api, sources))
558 # Make sure the tree is 'open'.
559 results
.extend(input_api
.canned_checks
.CheckTreeIsOpen(
562 json_url
='http://chromium-status.appspot.com/current?format=json'))
563 results
.extend(input_api
.canned_checks
.CheckRietveldTryJobExecution(input_api
,
564 output_api
, 'http://codereview.chromium.org',
565 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
566 'tryserver@chromium.org'))
568 results
.extend(input_api
.canned_checks
.CheckChangeHasBugField(
569 input_api
, output_api
))
570 results
.extend(input_api
.canned_checks
.CheckChangeHasDescription(
571 input_api
, output_api
))
572 results
.extend(_CheckSubversionConfig(input_api
, output_api
))
576 def GetPreferredTrySlaves(project
, change
):
577 files
= change
.LocalPaths()
582 if all(re
.search('\.(m|mm)$|[/_]mac[/_.]', f
) for f
in files
):
584 if all(re
.search('[/_]win[/_.]', f
) for f
in files
):
586 if all(re
.search('[/_]android[/_.]', f
) for f
in files
):
589 trybots
= ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile',
592 # Match things like path/aura/file.cc and path/file_aura.cc.
593 # Same for ash and chromeos.
594 if any(re
.search('[/_](ash|aura)', f
) for f
in files
):
595 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile', 'win_aura']
597 if any(re
.search('[/_]chromeos', f
) for f
in files
):
598 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile']