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
".+[\\\/]pnacl_shim\.c$",
30 _TEST_ONLY_WARNING
= (
31 'You might be calling functions intended only for testing from\n'
32 'production code. It is OK to ignore this warning if you know what\n'
33 'you are doing, as the heuristics used to detect the situation are\n'
34 'not perfect. The commit queue will not block on this warning.\n'
35 'Email joi@chromium.org if you have questions.')
38 _BANNED_OBJC_FUNCTIONS
= (
42 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
43 'prohibited. Please use CrTrackingArea instead.',
44 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
51 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
53 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
58 'convertPointFromBase:',
60 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
61 'Please use |convertPoint:(point) fromView:nil| instead.',
62 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
67 'convertPointToBase:',
69 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
70 'Please use |convertPoint:(point) toView:nil| instead.',
71 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
76 'convertRectFromBase:',
78 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
79 'Please use |convertRect:(point) fromView:nil| instead.',
80 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
87 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
88 'Please use |convertRect:(point) toView:nil| instead.',
89 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
94 'convertSizeFromBase:',
96 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
97 'Please use |convertSize:(point) fromView:nil| instead.',
98 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
103 'convertSizeToBase:',
105 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
106 'Please use |convertSize:(point) toView:nil| instead.',
107 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
114 _BANNED_CPP_FUNCTIONS
= (
115 # Make sure that gtest's FRIEND_TEST() macro is not used; the
116 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
117 # used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes.
121 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
122 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
129 'New code should not use ScopedAllowIO. Post a task to the blocking',
130 'pool or the FILE thread instead.',
135 'FilePathWatcher::Delegate',
137 'New code should not use FilePathWatcher::Delegate. Use the callback',
138 'interface instead.',
143 'browser::FindLastActiveWithProfile',
145 'This function is deprecated and we\'re working on removing it. Pass',
146 'more context to get a Browser*, like a WebContents, window, or session',
147 'id. Talk to ben@ or jam@ for more information.',
152 'browser::FindAnyBrowser',
154 'This function is deprecated and we\'re working on removing it. Pass',
155 'more context to get a Browser*, like a WebContents, window, or session',
156 'id. Talk to ben@ or jam@ for more information.',
161 'browser::FindOrCreateTabbedBrowser',
163 'This function is deprecated and we\'re working on removing it. Pass',
164 'more context to get a Browser*, like a WebContents, window, or session',
165 'id. Talk to ben@ or jam@ for more information.',
170 'browser::FindTabbedBrowser',
172 'This function is deprecated and we\'re working on removing it. Pass',
173 'more context to get a Browser*, like a WebContents, window, or session',
174 'id. Talk to ben@ or jam@ for more information.',
182 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
):
183 """Attempts to prevent use of functions intended only for testing in
184 non-testing code. For now this is just a best-effort implementation
185 that ignores header files and may have some false positives. A
186 better implementation would probably need a proper C++ parser.
188 # We only scan .cc files and the like, as the declaration of
189 # for-testing functions in header files are hard to distinguish from
190 # calls to such functions without a proper C++ parser.
191 platform_specifiers
= r
'(_(android|chromeos|gtk|mac|posix|win))?'
192 source_extensions
= r
'\.(cc|cpp|cxx|mm)$'
193 file_inclusion_pattern
= r
'.+%s' % source_extensions
194 file_exclusion_patterns
= (
195 r
'.*[/\\](test_|mock_).+%s' % source_extensions
,
196 r
'.+_test_(base|support|util)%s' % source_extensions
,
197 r
'.+_(api|browser|perf|unit|ui)?test%s%s' % (platform_specifiers
,
199 r
'.+profile_sync_service_harness%s' % source_extensions
,
201 path_exclusion_patterns
= (
202 r
'.*[/\\](test|tool(s)?)[/\\].*',
203 # At request of folks maintaining this folder.
204 r
'chrome[/\\]browser[/\\]automation[/\\].*',
207 base_function_pattern
= r
'ForTest(ing)?|for_test(ing)?'
208 inclusion_pattern
= input_api
.re
.compile(r
'(%s)\s*\(' % base_function_pattern
)
209 exclusion_pattern
= input_api
.re
.compile(
210 r
'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
211 base_function_pattern
, base_function_pattern
))
213 def FilterFile(affected_file
):
214 black_list
= (file_exclusion_patterns
+ path_exclusion_patterns
+
215 _EXCLUDED_PATHS
+ input_api
.DEFAULT_BLACK_LIST
)
216 return input_api
.FilterSourceFile(
218 white_list
=(file_inclusion_pattern
, ),
219 black_list
=black_list
)
222 for f
in input_api
.AffectedSourceFiles(FilterFile
):
223 local_path
= f
.LocalPath()
224 lines
= input_api
.ReadFile(f
).splitlines()
227 if (inclusion_pattern
.search(line
) and
228 not exclusion_pattern
.search(line
)):
230 '%s:%d\n %s' % (local_path
, line_number
, line
.strip()))
234 if not input_api
.is_committing
:
235 return [output_api
.PresubmitPromptWarning(_TEST_ONLY_WARNING
, problems
)]
237 # We don't warn on commit, to avoid stopping commits going through CQ.
238 return [output_api
.PresubmitNotifyResult(_TEST_ONLY_WARNING
, problems
)]
243 def _CheckNoIOStreamInHeaders(input_api
, output_api
):
244 """Checks to make sure no .h files include <iostream>."""
246 pattern
= input_api
.re
.compile(r
'^#include\s*<iostream>',
247 input_api
.re
.MULTILINE
)
248 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
249 if not f
.LocalPath().endswith('.h'):
251 contents
= input_api
.ReadFile(f
)
252 if pattern
.search(contents
):
256 return [ output_api
.PresubmitError(
257 'Do not #include <iostream> in header files, since it inserts static '
258 'initialization into every file including the header. Instead, '
259 '#include <ostream>. See http://crbug.com/94794',
264 def _CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
):
265 """Checks to make sure no source files use UNIT_TEST"""
267 for f
in input_api
.AffectedFiles():
268 if (not f
.LocalPath().endswith(('.cc', '.mm'))):
271 for line_num
, line
in f
.ChangedContents():
272 if 'UNIT_TEST' in line
:
273 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
277 return [output_api
.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
278 '\n'.join(problems
))]
281 def _CheckNoNewWStrings(input_api
, output_api
):
282 """Checks to make sure we don't introduce use of wstrings."""
284 for f
in input_api
.AffectedFiles():
285 if (not f
.LocalPath().endswith(('.cc', '.h')) or
286 f
.LocalPath().endswith('test.cc')):
290 for line_num
, line
in f
.ChangedContents():
291 if 'presubmit: allow wstring' in line
:
293 elif not allowWString
and 'wstring' in line
:
294 problems
.append(' %s:%d' % (f
.LocalPath(), line_num
))
301 return [output_api
.PresubmitPromptWarning('New code should not use wstrings.'
302 ' If you are calling a cross-platform API that accepts a wstring, '
304 '\n'.join(problems
))]
307 def _CheckNoDEPSGIT(input_api
, output_api
):
308 """Make sure .DEPS.git is never modified manually."""
309 if any(f
.LocalPath().endswith('.DEPS.git') for f
in
310 input_api
.AffectedFiles()):
311 return [output_api
.PresubmitError(
312 'Never commit changes to .DEPS.git. This file is maintained by an\n'
313 'automated system based on what\'s in DEPS and your changes will be\n'
315 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
316 'for more information')]
320 def _CheckNoBannedFunctions(input_api
, output_api
):
321 """Make sure that banned functions are not used."""
325 file_filter
= lambda f
: f
.LocalPath().endswith(('.mm', '.m', '.h'))
326 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
327 for line_num
, line
in f
.ChangedContents():
328 for func_name
, message
, error
in _BANNED_OBJC_FUNCTIONS
:
329 if func_name
in line
:
333 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
334 for message_line
in message
:
335 problems
.append(' %s' % message_line
)
337 file_filter
= lambda f
: f
.LocalPath().endswith(('.cc', '.mm', '.h'))
338 for f
in input_api
.AffectedFiles(file_filter
=file_filter
):
339 for line_num
, line
in f
.ChangedContents():
340 for func_name
, message
, error
in _BANNED_CPP_FUNCTIONS
:
341 if func_name
in line
:
345 problems
.append(' %s:%d:' % (f
.LocalPath(), line_num
))
346 for message_line
in message
:
347 problems
.append(' %s' % message_line
)
351 result
.append(output_api
.PresubmitPromptWarning(
352 'Banned functions were used.\n' + '\n'.join(warnings
)))
354 result
.append(output_api
.PresubmitError(
355 'Banned functions were used.\n' + '\n'.join(errors
)))
359 def _CheckNoPragmaOnce(input_api
, output_api
):
360 """Make sure that banned functions are not used."""
362 pattern
= input_api
.re
.compile(r
'^#pragma\s+once',
363 input_api
.re
.MULTILINE
)
364 for f
in input_api
.AffectedSourceFiles(input_api
.FilterSourceFile
):
365 if not f
.LocalPath().endswith('.h'):
367 contents
= input_api
.ReadFile(f
)
368 if pattern
.search(contents
):
372 return [output_api
.PresubmitError(
373 'Do not use #pragma once in header files.\n'
374 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
379 def _CheckUnwantedDependencies(input_api
, output_api
):
380 """Runs checkdeps on #include statements added in this
381 change. Breaking - rules is an error, breaking ! rules is a
384 # We need to wait until we have an input_api object and use this
385 # roundabout construct to import checkdeps because this file is
386 # eval-ed and thus doesn't have __file__.
387 original_sys_path
= sys
.path
389 sys
.path
= sys
.path
+ [input_api
.os_path
.join(
390 input_api
.PresubmitLocalPath(), 'tools', 'checkdeps')]
392 from cpp_checker
import CppChecker
393 from rules
import Rule
395 # Restore sys.path to what it was before.
396 sys
.path
= original_sys_path
399 for f
in input_api
.AffectedFiles():
400 if not CppChecker
.IsCppFile(f
.LocalPath()):
403 changed_lines
= [line
for line_num
, line
in f
.ChangedContents()]
404 added_includes
.append([f
.LocalPath(), changed_lines
])
406 deps_checker
= checkdeps
.DepsChecker()
408 error_descriptions
= []
409 warning_descriptions
= []
410 for path
, rule_type
, rule_description
in deps_checker
.CheckAddedCppIncludes(
412 description_with_path
= '%s\n %s' % (path
, rule_description
)
413 if rule_type
== Rule
.DISALLOW
:
414 error_descriptions
.append(description_with_path
)
416 warning_descriptions
.append(description_with_path
)
419 if error_descriptions
:
420 results
.append(output_api
.PresubmitError(
421 'You added one or more #includes that violate checkdeps rules.',
423 if warning_descriptions
:
424 if not input_api
.is_committing
:
425 warning_factory
= output_api
.PresubmitPromptWarning
427 # We don't want to block use of the CQ when there is a warning
428 # of this kind, so we only show a message when committing.
429 warning_factory
= output_api
.PresubmitNotifyResult
430 results
.append(warning_factory(
431 'You added one or more #includes of files that are temporarily\n'
432 'allowed but being removed. Can you avoid introducing the\n'
433 '#include? See relevant DEPS file(s) for details and contacts.',
434 warning_descriptions
))
438 def _CheckFilePermissions(input_api
, output_api
):
439 """Check that all files have their permissions properly set."""
440 args
= [sys
.executable
, 'tools/checkperms/checkperms.py', '--root',
441 input_api
.change
.RepositoryRoot()]
442 for f
in input_api
.AffectedFiles():
443 args
+= ['--file', f
.LocalPath()]
445 (errors
, stderrdata
) = subprocess
.Popen(args
).communicate()
449 results
.append(output_api
.PreSubmitError('checkperms.py failed.',
454 def _CommonChecks(input_api
, output_api
):
455 """Checks common to both upload and commit."""
457 results
.extend(input_api
.canned_checks
.PanProjectChecks(
458 input_api
, output_api
, excluded_paths
=_EXCLUDED_PATHS
))
459 results
.extend(_CheckAuthorizedAuthor(input_api
, output_api
))
461 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api
, output_api
))
462 results
.extend(_CheckNoIOStreamInHeaders(input_api
, output_api
))
463 results
.extend(_CheckNoUNIT_TESTInSourceFiles(input_api
, output_api
))
464 results
.extend(_CheckNoNewWStrings(input_api
, output_api
))
465 results
.extend(_CheckNoDEPSGIT(input_api
, output_api
))
466 results
.extend(_CheckNoBannedFunctions(input_api
, output_api
))
467 results
.extend(_CheckNoPragmaOnce(input_api
, output_api
))
468 results
.extend(_CheckUnwantedDependencies(input_api
, output_api
))
469 results
.extend(_CheckFilePermissions(input_api
, output_api
))
473 def _CheckSubversionConfig(input_api
, output_api
):
474 """Verifies the subversion config file is correctly setup.
476 Checks that autoprops are enabled, returns an error otherwise.
478 join
= input_api
.os_path
.join
479 if input_api
.platform
== 'win32':
480 appdata
= input_api
.environ
.get('APPDATA', '')
482 return [output_api
.PresubmitError('%APPDATA% is not configured.')]
483 path
= join(appdata
, 'Subversion', 'config')
485 home
= input_api
.environ
.get('HOME', '')
487 return [output_api
.PresubmitError('$HOME is not configured.')]
488 path
= join(home
, '.subversion', 'config')
491 'Please look at http://dev.chromium.org/developers/coding-style to\n'
492 'configure your subversion configuration file. This enables automatic\n'
493 'properties to simplify the project maintenance.\n'
494 'Pro-tip: just download and install\n'
495 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
498 lines
= open(path
, 'r').read().splitlines()
499 # Make sure auto-props is enabled and check for 2 Chromium standard
501 if (not '*.cc = svn:eol-style=LF' in lines
or
502 not '*.pdf = svn:mime-type=application/pdf' in lines
or
503 not 'enable-auto-props = yes' in lines
):
505 output_api
.PresubmitNotifyResult(
506 'It looks like you have not configured your subversion config '
507 'file or it is not up-to-date.\n' + error_msg
)
509 except (OSError, IOError):
511 output_api
.PresubmitNotifyResult(
512 'Can\'t find your subversion config file.\n' + error_msg
)
517 def _CheckAuthorizedAuthor(input_api
, output_api
):
518 """For non-googler/chromites committers, verify the author's email address is
521 # TODO(maruel): Add it to input_api?
524 author
= input_api
.change
.author_email
526 input_api
.logging
.info('No author, skipping AUTHOR check')
528 authors_path
= input_api
.os_path
.join(
529 input_api
.PresubmitLocalPath(), 'AUTHORS')
531 input_api
.re
.match(r
'[^#]+\s+\<(.+?)\>\s*$', line
)
532 for line
in open(authors_path
))
533 valid_authors
= [item
.group(1).lower() for item
in valid_authors
if item
]
534 if input_api
.verbose
:
535 print 'Valid authors are %s' % ', '.join(valid_authors
)
536 if not any(fnmatch
.fnmatch(author
.lower(), valid
) for valid
in valid_authors
):
537 return [output_api
.PresubmitPromptWarning(
538 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
540 'http://www.chromium.org/developers/contributing-code and read the '
542 'If you are a chromite, verify the contributor signed the CLA.') %
547 def CheckChangeOnUpload(input_api
, output_api
):
549 results
.extend(_CommonChecks(input_api
, output_api
))
553 def CheckChangeOnCommit(input_api
, output_api
):
555 results
.extend(_CommonChecks(input_api
, output_api
))
556 # TODO(thestig) temporarily disabled, doesn't work in third_party/
557 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
558 # input_api, output_api, sources))
559 # Make sure the tree is 'open'.
560 results
.extend(input_api
.canned_checks
.CheckTreeIsOpen(
563 json_url
='http://chromium-status.appspot.com/current?format=json'))
564 results
.extend(input_api
.canned_checks
.CheckRietveldTryJobExecution(input_api
,
565 output_api
, 'http://codereview.chromium.org',
566 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
567 'tryserver@chromium.org'))
569 results
.extend(input_api
.canned_checks
.CheckChangeHasBugField(
570 input_api
, output_api
))
571 results
.extend(input_api
.canned_checks
.CheckChangeHasDescription(
572 input_api
, output_api
))
573 results
.extend(_CheckSubversionConfig(input_api
, output_api
))
577 def GetPreferredTrySlaves(project
, change
):
578 files
= change
.LocalPaths()
583 if all(re
.search('\.(m|mm)$|(^|[/_])mac[/_.]', f
) for f
in files
):
584 return ['mac_rel', 'mac_asan']
585 if all(re
.search('(^|[/_])win[/_.]', f
) for f
in files
):
587 if all(re
.search('(^|[/_])android[/_.]', f
) for f
in files
):
590 trybots
= ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile',
591 'linux_chromeos', 'android', 'mac_asan']
593 # Match things like path/aura/file.cc and path/file_aura.cc.
594 # Same for ash and chromeos.
595 if any(re
.search('[/_](ash|aura)', f
) for f
in files
):
596 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile', 'win_aura',
597 'linux_chromeos_asan']
599 if any(re
.search('[/_]chromeos', f
) for f
in files
):
600 trybots
+= ['linux_chromeos', 'linux_chromeos_clang:compile',
601 'linux_chromeos_asan']