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[\\\/].*",
26 r
"^webkit[\\\/]compositor_bindings[\\\/].*",
27 r
".+[\\\/]pnacl_shim\.c$",
31 _TEST_ONLY_WARNING
= (
32 'You might be calling functions intended only for testing from\n'
33 'production code. It is OK to ignore this warning if you know what\n'
34 'you are doing, as the heuristics used to detect the situation are\n'
35 'not perfect. The commit queue will not block on this warning.\n'
36 'Email joi@chromium.org if you have questions.')
39 _BANNED_OBJC_FUNCTIONS
= (
43 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
44 'prohibited. Please use CrTrackingArea instead.',
45 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
52 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
54 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
59 'convertPointFromBase:',
61 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
62 'Please use |convertPoint:(point) fromView:nil| instead.',
63 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
68 'convertPointToBase:',
70 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
71 'Please use |convertPoint:(point) toView:nil| instead.',
72 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
77 'convertRectFromBase:',
79 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
80 'Please use |convertRect:(point) fromView:nil| instead.',
81 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
88 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
89 'Please use |convertRect:(point) toView:nil| instead.',
90 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
95 'convertSizeFromBase:',
97 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
98 'Please use |convertSize:(point) fromView:nil| instead.',
99 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
104 'convertSizeToBase:',
106 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
107 'Please use |convertSize:(point) toView:nil| instead.',
108 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
115 _BANNED_CPP_FUNCTIONS
= (
116 # Make sure that gtest's FRIEND_TEST() macro is not used; the
117 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
118 # used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes.
122 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
123 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
130 'New code should not use ScopedAllowIO. Post a task to the blocking',
131 'pool or the FILE thread instead.',
136 'FilePathWatcher::Delegate',
138 'New code should not use FilePathWatcher::Delegate. Use the callback',
139 'interface instead.',
144 'browser::FindLastActiveWithProfile',
146 'This function is deprecated and we\'re working on removing it. Pass',
147 'more context to get a Browser*, like a WebContents, window, or session',
148 'id. Talk to ben@ or jam@ for more information.',
153 'browser::FindAnyBrowser',
155 'This function is deprecated and we\'re working on removing it. Pass',
156 'more context to get a Browser*, like a WebContents, window, or session',
157 'id. Talk to ben@ or jam@ for more information.',
162 'browser::FindOrCreateTabbedBrowser',
164 'This function is deprecated and we\'re working on removing it. Pass',
165 'more context to get a Browser*, like a WebContents, window, or session',
166 'id. Talk to ben@ or jam@ for more information.',
171 'browser::FindTabbedBrowser',
173 'This function is deprecated and we\'re working on removing it. Pass',
174 'more context to get a Browser*, like a WebContents, window, or session',
175 'id. Talk to ben@ or jam@ for more information.',
183 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
):
184 """Attempts to prevent use of functions intended only for testing in
185 non-testing code. For now this is just a best-effort implementation
186 that ignores header files and may have some false positives. A
187 better implementation would probably need a proper C++ parser.
189 # We only scan .cc files and the like, as the declaration of
190 # for-testing functions in header files are hard to distinguish from
191 # calls to such functions without a proper C++ parser.
192 platform_specifiers
= r
'(_(android|chromeos|gtk|mac|posix|win))?'
193 source_extensions
= r
'\.(cc|cpp|cxx|mm)$'
194 file_inclusion_pattern
= r
'.+%s' % source_extensions
195 file_exclusion_patterns
= (
196 r
'.*[/\\](test_|mock_).+%s' % source_extensions
,
197 r
'.+_test_(base|support|util)%s' % source_extensions
,
198 r
'.+_(api|browser|perf|unit|ui)?test%s%s' % (platform_specifiers
,
200 r
'.+profile_sync_service_harness%s' % source_extensions
,
202 path_exclusion_patterns
= (
203 r
'.*[/\\](test|tool(s)?)[/\\].*',
204 # At request of folks maintaining this folder.
205 r
'chrome[/\\]browser[/\\]automation[/\\].*',
208 base_function_pattern
= r
'ForTest(ing)?|for_test(ing)?'
209 inclusion_pattern
= input_api
.re
.compile(r
'(%s)\s*\(' % base_function_pattern
)
210 exclusion_pattern
= input_api
.re
.compile(
211 r
'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
212 base_function_pattern
, base_function_pattern
))
214 def FilterFile(affected_file
):
215 black_list
= (file_exclusion_patterns
+ path_exclusion_patterns
+
216 _EXCLUDED_PATHS
+ input_api
.DEFAULT_BLACK_LIST
)
217 return input_api
.FilterSourceFile(
219 white_list
=(file_inclusion_pattern
, ),
220 black_list
=black_list
)
223 for f
in input_api
.AffectedSourceFiles(FilterFile
):
224 local_path
= f
.LocalPath()
225 lines
= input_api
.ReadFile(f
).splitlines()
228 if (inclusion_pattern
.search(line
) and
229 not exclusion_pattern
.search(line
)):
231 '%s:%d\n %s' % (local_path
, line_number
, line
.strip()))
235 if not input_api
.is_committing
:
236 return [output_api
.PresubmitPromptWarning(_TEST_ONLY_WARNING
, problems
)]
238 # We don't warn on commit, to avoid stopping commits going through CQ.
239 return [output_api
.PresubmitNotifyResult(_TEST_ONLY_WARNING
, problems
)]
244 def _CheckNoIOStreamInHeaders(input_api
, output_api
):
245 """Checks to make sure no .h files include <iostream>."""
247 pattern
= input_api
.re
.compile(r
'^#include\s*<iostream>',
248 input_api
.re
.MULTILINE
)
249 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
250 if not f
.LocalPath().endswith('.h'):
252 contents
= input_api
.ReadFile(f
)
253 if pattern
.search(contents
):
257 return [ output_api
.PresubmitError(
258 'Do not #include <iostream> in header files, since it inserts static '
259 'initialization into every file including the header. Instead, '
260 '#include <ostream>. See http://crbug.com/94794',
265 def _CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
):
266 """Checks to make sure no source files use UNIT_TEST"""
268 for f
in input_api
.AffectedFiles():
269 if (not f
.LocalPath().endswith(('.cc', '.mm'))):
272 for line_num
, line
in f
.ChangedContents():
273 if 'UNIT_TEST' in line
:
274 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
278 return [output_api
.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
279 '\n'.join(problems
))]
282 def _CheckNoNewWStrings(input_api
, output_api
):
283 """Checks to make sure we don't introduce use of wstrings."""
285 for f
in input_api
.AffectedFiles():
286 if (not f
.LocalPath().endswith(('.cc', '.h')) or
287 f
.LocalPath().endswith('test.cc')):
291 for line_num
, line
in f
.ChangedContents():
292 if 'presubmit: allow wstring' in line
:
294 elif not allowWString
and 'wstring' in line
:
295 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
302 return [output_api
.PresubmitPromptWarning('New code should not use wstrings.'
303 ' If you are calling a cross-platform API that accepts a wstring, '
305 '\n'.join(problems
))]
308 def _CheckNoDEPSGIT(input_api
, output_api
):
309 """Make sure .DEPS.git is never modified manually."""
310 if any(f
.LocalPath().endswith('.DEPS.git') for f
in
311 input_api
.AffectedFiles()):
312 return [output_api
.PresubmitError(
313 'Never commit changes to .DEPS.git. This file is maintained by an\n'
314 'automated system based on what\'s in DEPS and your changes will be\n'
316 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
317 'for more information')]
321 def _CheckNoBannedFunctions(input_api
, output_api
):
322 """Make sure that banned functions are not used."""
326 file_filter
= lambda f
: f
.LocalPath().endswith(('.mm', '.m', '.h'))
327 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
328 for line_num
, line
in f
.ChangedContents():
329 for func_name
, message
, error
in _BANNED_OBJC_FUNCTIONS
:
330 if func_name
in line
:
334 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
335 for message_line
in message
:
336 problems
.append(' %s' % message_line
)
338 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.mm', '.h'))
339 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
340 for line_num
, line
in f
.ChangedContents():
341 for func_name
, message
, error
in _BANNED_CPP_FUNCTIONS
:
342 if func_name
in line
:
346 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
347 for message_line
in message
:
348 problems
.append(' %s' % message_line
)
352 result
.append(output_api
.PresubmitPromptWarning(
353 'Banned functions were used.\n' + '\n'.join(warnings
)))
355 result
.append(output_api
.PresubmitError(
356 'Banned functions were used.\n' + '\n'.join(errors
)))
360 def _CheckNoPragmaOnce(input_api
, output_api
):
361 """Make sure that banned functions are not used."""
363 pattern
= input_api
.re
.compile(r
'^#pragma\s+once',
364 input_api
.re
.MULTILINE
)
365 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
366 if not f
.LocalPath().endswith('.h'):
368 contents
= input_api
.ReadFile(f
)
369 if pattern
.search(contents
):
373 return [output_api
.PresubmitError(
374 'Do not use #pragma once in header files.\n'
375 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
380 def _CheckNoTrinaryTrueFalse(input_api
, output_api
):
381 """Checks to make sure we don't introduce use of foo ? true : false."""
383 pattern
= input_api
.re
.compile(r
'\?\s*(true|false)\s*:\s*(true|false)')
384 for f
in input_api
.AffectedFiles():
385 if not f
.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
388 for line_num
, line
in f
.ChangedContents():
389 if pattern
.match(line
):
390 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
394 return [output_api
.PresubmitPromptWarning(
395 'Please consider avoiding the "? true : false" pattern if possible.\n' +
396 '\n'.join(problems
))]
399 def _CheckUnwantedDependencies(input_api
, output_api
):
400 """Runs checkdeps on #include statements added in this
401 change. Breaking - rules is an error, breaking ! rules is a
404 # We need to wait until we have an input_api object and use this
405 # roundabout construct to import checkdeps because this file is
406 # eval-ed and thus doesn't have __file__.
407 original_sys_path
= sys
.path
409 sys
.path
= sys
.path
+ [input_api
.os_path
.join(
410 input_api
.PresubmitLocalPath(), 'tools', 'checkdeps')]
412 from cpp_checker
import CppChecker
413 from rules
import Rule
415 # Restore sys.path to what it was before.
416 sys
.path
= original_sys_path
419 for f
in input_api
.AffectedFiles():
420 if not CppChecker
.IsCppFile(f
.LocalPath()):
423 changed_lines
= [line
for line_num
, line
in f
.ChangedContents()]
424 added_includes
.append([f
.LocalPath(), changed_lines
])
426 deps_checker
= checkdeps
.DepsChecker()
428 error_descriptions
= []
429 warning_descriptions
= []
430 for path
, rule_type
, rule_description
in deps_checker
.CheckAddedCppIncludes(
432 description_with_path
= '%s\n %s' % (path
, rule_description
)
433 if rule_type
== Rule
.DISALLOW
:
434 error_descriptions
.append(description_with_path
)
436 warning_descriptions
.append(description_with_path
)
439 if error_descriptions
:
440 results
.append(output_api
.PresubmitError(
441 'You added one or more #includes that violate checkdeps rules.',
443 if warning_descriptions
:
444 if not input_api
.is_committing
:
445 warning_factory
= output_api
.PresubmitPromptWarning
447 # We don't want to block use of the CQ when there is a warning
448 # of this kind, so we only show a message when committing.
449 warning_factory
= output_api
.PresubmitNotifyResult
450 results
.append(warning_factory(
451 'You added one or more #includes of files that are temporarily\n'
452 'allowed but being removed. Can you avoid introducing the\n'
453 '#include? See relevant DEPS file(s) for details and contacts.',
454 warning_descriptions
))
458 def _CheckFilePermissions(input_api
, output_api
):
459 """Check that all files have their permissions properly set."""
460 args
= [sys
.executable
, 'tools/checkperms/checkperms.py', '--root',
461 input_api
.change
.RepositoryRoot()]
462 for f
in input_api
.AffectedFiles():
463 args
+= ['--file', f
.LocalPath()]
465 (errors
, stderrdata
) = subprocess
.Popen(args
).communicate()
469 results
.append(output_api
.PreSubmitError('checkperms.py failed.',
474 def _CommonChecks(input_api
, output_api
):
475 """Checks common to both upload and commit."""
477 results
.extend(input_api
.canned_checks
.PanProjectChecks(
478 input_api
, output_api
, excluded_paths
=_EXCLUDED_PATHS
))
479 results
.extend(_CheckAuthorizedAuthor(input_api
, output_api
))
481 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
))
482 results
.extend(_CheckNoIOStreamInHeaders(input_api
, output_api
))
483 results
.extend(_CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
))
484 results
.extend(_CheckNoNewWStrings(input_api
, output_api
))
485 results
.extend(_CheckNoDEPSGIT(input_api
, output_api
))
486 results
.extend(_CheckNoBannedFunctions(input_api
, output_api
))
487 results
.extend(_CheckNoPragmaOnce(input_api
, output_api
))
488 results
.extend(_CheckNoTrinaryTrueFalse(input_api
, output_api
))
489 results
.extend(_CheckUnwantedDependencies(input_api
, output_api
))
490 results
.extend(_CheckFilePermissions(input_api
, output_api
))
494 def _CheckSubversionConfig(input_api
, output_api
):
495 """Verifies the subversion config file is correctly setup.
497 Checks that autoprops are enabled, returns an error otherwise.
499 join
= input_api
.os_path
.join
500 if input_api
.platform
== 'win32':
501 appdata
= input_api
.environ
.get('APPDATA', '')
503 return [output_api
.PresubmitError('%APPDATA% is not configured.')]
504 path
= join(appdata
, 'Subversion', 'config')
506 home
= input_api
.environ
.get('HOME', '')
508 return [output_api
.PresubmitError('$HOME is not configured.')]
509 path
= join(home
, '.subversion', 'config')
512 'Please look at http://dev.chromium.org/developers/coding-style to\n'
513 'configure your subversion configuration file. This enables automatic\n'
514 'properties to simplify the project maintenance.\n'
515 'Pro-tip: just download and install\n'
516 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
519 lines
= open(path
, 'r').read().splitlines()
520 # Make sure auto-props is enabled and check for 2 Chromium standard
522 if (not '*.cc = svn:eol-style=LF' in lines
or
523 not '*.pdf = svn:mime-type=application/pdf' in lines
or
524 not 'enable-auto-props = yes' in lines
):
526 output_api
.PresubmitNotifyResult(
527 'It looks like you have not configured your subversion config '
528 'file or it is not up-to-date.\n' + error_msg
)
530 except (OSError, IOError):
532 output_api
.PresubmitNotifyResult(
533 'Can\'t find your subversion config file.\n' + error_msg
)
538 def _CheckAuthorizedAuthor(input_api
, output_api
):
539 """For non-googler/chromites committers, verify the author's email address is
542 # TODO(maruel): Add it to input_api?
545 author
= input_api
.change
.author_email
547 input_api
.logging
.info('No author, skipping AUTHOR check')
549 authors_path
= input_api
.os_path
.join(
550 input_api
.PresubmitLocalPath(), 'AUTHORS')
552 input_api
.re
.match(r
'[^#]+\s+\<(.+?)\>\s*$', line
)
553 for line
in open(authors_path
))
554 valid_authors
= [item
.group(1).lower() for item
in valid_authors
if item
]
555 if input_api
.verbose
:
556 print 'Valid authors are %s' % ', '.join(valid_authors
)
557 if not any(fnmatch
.fnmatch(author
.lower(), valid
) for valid
in valid_authors
):
558 return [output_api
.PresubmitPromptWarning(
559 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
561 'http://www.chromium.org/developers/contributing-code and read the '
563 'If you are a chromite, verify the contributor signed the CLA.') %
568 def CheckChangeOnUpload(input_api
, output_api
):
570 results
.extend(_CommonChecks(input_api
, output_api
))
574 def CheckChangeOnCommit(input_api
, output_api
):
576 results
.extend(_CommonChecks(input_api
, output_api
))
577 # TODO(thestig) temporarily disabled, doesn't work in third_party/
578 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
579 # input_api, output_api, sources))
580 # Make sure the tree is 'open'.
581 results
.extend(input_api
.canned_checks
.CheckTreeIsOpen(
584 json_url
='http://chromium-status.appspot.com/current?format=json'))
585 results
.extend(input_api
.canned_checks
.CheckRietveldTryJobExecution(input_api
,
586 output_api
, 'http://codereview.chromium.org',
587 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
588 'tryserver@chromium.org'))
590 results
.extend(input_api
.canned_checks
.CheckChangeHasBugField(
591 input_api
, output_api
))
592 results
.extend(input_api
.canned_checks
.CheckChangeHasDescription(
593 input_api
, output_api
))
594 results
.extend(_CheckSubversionConfig(input_api
, output_api
))
598 def GetPreferredTrySlaves(project
, change
):
599 files
= change
.LocalPaths()
604 if all(re
.search('\.(m|mm)$|(^|[/_])mac[/_.]', f
) for f
in files
):
605 return ['mac_rel', 'mac_asan']
606 if all(re
.search('(^|[/_])win[/_.]', f
) for f
in files
):
608 if all(re
.search('(^|[/_])android[/_.]', f
) for f
in files
):
609 return ['android_dbg']
610 if all(re
.search('^native_client_sdk', f
) for f
in files
):
611 return ['linux_nacl_sdk', 'win_nacl_sdk', 'mac_nacl_sdk']
612 if all(re
.search('[/_]ios[/_.]', f
) for f
in files
):
613 return ['ios_rel_device', 'ios_dbg_simulator']
615 trybots
= ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile',
616 'linux_chromeos', 'android_dbg', 'linux_asan', 'mac_asan',
617 'ios_rel_device', 'ios_dbg_simulator']
619 # Match things like path/aura/file.cc and path/file_aura.cc.
620 # Same for ash and chromeos.
621 if any(re
.search('[/_](ash|aura)', f
) for f
in files
):
622 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile', 'win_aura',
623 'linux_chromeos_asan']
625 if any(re
.search('[/_]chromeos', f
) for f
in files
):
626 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile',
627 'linux_chromeos_asan']