Make caps lock disabled on shift release.
[chromium-blink-merge.git] / PRESUBMIT.py
blobac8a719eed171b4c969b46bbf162dff4b28ea556
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.
9 """
12 import re
13 import subprocess
14 import sys
17 _EXCLUDED_PATHS = (
18 r"^breakpad[\\\/].*",
19 r"^native_client_sdk[\\\/].*",
20 r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
21 r"^skia[\\\/].*",
22 r"^v8[\\\/].*",
23 r".*MakeFile$",
24 r".+_autogen\.h$",
28 _TEST_ONLY_WARNING = (
29 'You might be calling functions intended only for testing from\n'
30 'production code. It is OK to ignore this warning if you know what\n'
31 'you are doing, as the heuristics used to detect the situation are\n'
32 'not perfect. The commit queue will not block on this warning.\n'
33 'Email joi@chromium.org if you have questions.')
36 _BANNED_OBJC_FUNCTIONS = (
38 'addTrackingRect:',
40 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
41 'prohibited. Please use CrTrackingArea instead.',
42 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
44 False,
47 'NSTrackingArea',
49 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
50 'instead.',
51 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
53 False,
56 'convertPointFromBase:',
58 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
59 'Please use |convertPoint:(point) fromView:nil| instead.',
60 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
62 True,
65 'convertPointToBase:',
67 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
68 'Please use |convertPoint:(point) toView:nil| instead.',
69 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
71 True,
74 'convertRectFromBase:',
76 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
77 'Please use |convertRect:(point) fromView:nil| instead.',
78 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
80 True,
83 'convertRectToBase:',
85 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
86 'Please use |convertRect:(point) toView:nil| instead.',
87 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
89 True,
92 'convertSizeFromBase:',
94 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
95 'Please use |convertSize:(point) fromView:nil| instead.',
96 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
98 True,
101 'convertSizeToBase:',
103 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
104 'Please use |convertSize:(point) toView:nil| instead.',
105 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
107 True,
112 _BANNED_CPP_FUNCTIONS = (
113 # Make sure that gtest's FRIEND_TEST() macro is not used; the
114 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
115 # used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes.
117 'FRIEND_TEST(',
119 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
120 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
122 False,
125 'ScopedAllowIO',
127 'New code should not use ScopedAllowIO. Post a task to the blocking',
128 'pool or the FILE thread instead.',
130 True,
133 'FilePathWatcher::Delegate',
135 'New code should not use FilePathWatcher::Delegate. Use the callback',
136 'interface instead.',
138 False,
141 'browser::FindLastActiveWithProfile',
143 'This function is deprecated and we\'re working on removing it. Pass',
144 'more context to get a Browser*, like a WebContents, window, or session',
145 'id. Talk to ben@ or jam@ for more information.',
147 True,
150 'browser::FindBrowserWithProfile',
152 'This function is deprecated and we\'re working on removing it. Pass',
153 'more context to get a Browser*, like a WebContents, window, or session',
154 'id. Talk to ben@ or jam@ for more information.',
156 True,
159 'browser::FindAnyBrowser',
161 'This function is deprecated and we\'re working on removing it. Pass',
162 'more context to get a Browser*, like a WebContents, window, or session',
163 'id. Talk to ben@ or jam@ for more information.',
165 True,
168 'browser::FindOrCreateTabbedBrowser',
170 'This function is deprecated and we\'re working on removing it. Pass',
171 'more context to get a Browser*, like a WebContents, window, or session',
172 'id. Talk to ben@ or jam@ for more information.',
174 True,
177 'browser::FindTabbedBrowser',
179 'This function is deprecated and we\'re working on removing it. Pass',
180 'more context to get a Browser*, like a WebContents, window, or session',
181 'id. Talk to ben@ or jam@ for more information.',
183 True,
189 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
190 """Attempts to prevent use of functions intended only for testing in
191 non-testing code. For now this is just a best-effort implementation
192 that ignores header files and may have some false positives. A
193 better implementation would probably need a proper C++ parser.
195 # We only scan .cc files and the like, as the declaration of
196 # for-testing functions in header files are hard to distinguish from
197 # calls to such functions without a proper C++ parser.
198 platform_specifiers = r'(_(android|chromeos|gtk|mac|posix|win))?'
199 source_extensions = r'\.(cc|cpp|cxx|mm)$'
200 file_inclusion_pattern = r'.+%s' % source_extensions
201 file_exclusion_patterns = (
202 r'.*[/\\](test_|mock_).+%s' % source_extensions,
203 r'.+_test_(base|support|util)%s' % source_extensions,
204 r'.+_(api|browser|perf|unit|ui)?test%s%s' % (platform_specifiers,
205 source_extensions),
206 r'.+profile_sync_service_harness%s' % source_extensions,
208 path_exclusion_patterns = (
209 r'.*[/\\](test|tool(s)?)[/\\].*',
210 # At request of folks maintaining this folder.
211 r'chrome[/\\]browser[/\\]automation[/\\].*',
214 base_function_pattern = r'ForTest(ing)?|for_test(ing)?'
215 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
216 exclusion_pattern = input_api.re.compile(
217 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
218 base_function_pattern, base_function_pattern))
220 def FilterFile(affected_file):
221 black_list = (file_exclusion_patterns + path_exclusion_patterns +
222 _EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
223 return input_api.FilterSourceFile(
224 affected_file,
225 white_list=(file_inclusion_pattern, ),
226 black_list=black_list)
228 problems = []
229 for f in input_api.AffectedSourceFiles(FilterFile):
230 local_path = f.LocalPath()
231 lines = input_api.ReadFile(f).splitlines()
232 line_number = 0
233 for line in lines:
234 if (inclusion_pattern.search(line) and
235 not exclusion_pattern.search(line)):
236 problems.append(
237 '%s:%d\n %s' % (local_path, line_number, line.strip()))
238 line_number += 1
240 if problems:
241 if not input_api.is_committing:
242 return [output_api.PresubmitPromptWarning(_TEST_ONLY_WARNING, problems)]
243 else:
244 # We don't warn on commit, to avoid stopping commits going through CQ.
245 return [output_api.PresubmitNotifyResult(_TEST_ONLY_WARNING, problems)]
246 else:
247 return []
250 def _CheckNoIOStreamInHeaders(input_api, output_api):
251 """Checks to make sure no .h files include <iostream>."""
252 files = []
253 pattern = input_api.re.compile(r'^#include\s*<iostream>',
254 input_api.re.MULTILINE)
255 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
256 if not f.LocalPath().endswith('.h'):
257 continue
258 contents = input_api.ReadFile(f)
259 if pattern.search(contents):
260 files.append(f)
262 if len(files):
263 return [ output_api.PresubmitError(
264 'Do not #include <iostream> in header files, since it inserts static '
265 'initialization into every file including the header. Instead, '
266 '#include <ostream>. See http://crbug.com/94794',
267 files) ]
268 return []
271 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
272 """Checks to make sure no source files use UNIT_TEST"""
273 problems = []
274 for f in input_api.AffectedFiles():
275 if (not f.LocalPath().endswith(('.cc', '.mm'))):
276 continue
278 for line_num, line in f.ChangedContents():
279 if 'UNIT_TEST' in line:
280 problems.append(' %s:%d' % (f.LocalPath(), line_num))
282 if not problems:
283 return []
284 return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
285 '\n'.join(problems))]
288 def _CheckNoNewWStrings(input_api, output_api):
289 """Checks to make sure we don't introduce use of wstrings."""
290 problems = []
291 for f in input_api.AffectedFiles():
292 if (not f.LocalPath().endswith(('.cc', '.h')) or
293 f.LocalPath().endswith('test.cc')):
294 continue
296 allowWString = False
297 for line_num, line in f.ChangedContents():
298 if 'presubmit: allow wstring' in line:
299 allowWString = True
300 elif not allowWString and 'wstring' in line:
301 problems.append(' %s:%d' % (f.LocalPath(), line_num))
302 allowWString = False
303 else:
304 allowWString = False
306 if not problems:
307 return []
308 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
309 ' If you are calling a cross-platform API that accepts a wstring, '
310 'fix the API.\n' +
311 '\n'.join(problems))]
314 def _CheckNoDEPSGIT(input_api, output_api):
315 """Make sure .DEPS.git is never modified manually."""
316 if any(f.LocalPath().endswith('.DEPS.git') for f in
317 input_api.AffectedFiles()):
318 return [output_api.PresubmitError(
319 'Never commit changes to .DEPS.git. This file is maintained by an\n'
320 'automated system based on what\'s in DEPS and your changes will be\n'
321 'overwritten.\n'
322 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
323 'for more information')]
324 return []
327 def _CheckNoBannedFunctions(input_api, output_api):
328 """Make sure that banned functions are not used."""
329 warnings = []
330 errors = []
332 file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
333 for f in input_api.AffectedFiles(file_filter=file_filter):
334 for line_num, line in f.ChangedContents():
335 for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
336 if func_name in line:
337 problems = warnings;
338 if error:
339 problems = errors;
340 problems.append(' %s:%d:' % (f.LocalPath(), line_num))
341 for message_line in message:
342 problems.append(' %s' % message_line)
344 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
345 for f in input_api.AffectedFiles(file_filter=file_filter):
346 for line_num, line in f.ChangedContents():
347 for func_name, message, error in _BANNED_CPP_FUNCTIONS:
348 if func_name in line:
349 problems = warnings;
350 if error:
351 problems = errors;
352 problems.append(' %s:%d:' % (f.LocalPath(), line_num))
353 for message_line in message:
354 problems.append(' %s' % message_line)
356 result = []
357 if (warnings):
358 result.append(output_api.PresubmitPromptWarning(
359 'Banned functions were used.\n' + '\n'.join(warnings)))
360 if (errors):
361 result.append(output_api.PresubmitError(
362 'Banned functions were used.\n' + '\n'.join(errors)))
363 return result
366 def _CheckNoPragmaOnce(input_api, output_api):
367 """Make sure that banned functions are not used."""
368 files = []
369 pattern = input_api.re.compile(r'^#pragma\s+once',
370 input_api.re.MULTILINE)
371 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
372 if not f.LocalPath().endswith('.h'):
373 continue
374 contents = input_api.ReadFile(f)
375 if pattern.search(contents):
376 files.append(f)
378 if files:
379 return [output_api.PresubmitError(
380 'Do not use #pragma once in header files.\n'
381 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
382 files)]
383 return []
386 def _CheckUnwantedDependencies(input_api, output_api):
387 """Runs checkdeps on #include statements added in this
388 change. Breaking - rules is an error, breaking ! rules is a
389 warning.
391 # We need to wait until we have an input_api object and use this
392 # roundabout construct to import checkdeps because this file is
393 # eval-ed and thus doesn't have __file__.
394 original_sys_path = sys.path
395 try:
396 sys.path = sys.path + [input_api.os_path.join(
397 input_api.PresubmitLocalPath(), 'tools', 'checkdeps')]
398 import checkdeps
399 from cpp_checker import CppChecker
400 from rules import Rule
401 finally:
402 # Restore sys.path to what it was before.
403 sys.path = original_sys_path
405 added_includes = []
406 for f in input_api.AffectedFiles():
407 if not CppChecker.IsCppFile(f.LocalPath()):
408 continue
410 changed_lines = [line for line_num, line in f.ChangedContents()]
411 added_includes.append([f.LocalPath(), changed_lines])
413 deps_checker = checkdeps.DepsChecker()
415 error_descriptions = []
416 warning_descriptions = []
417 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
418 added_includes):
419 description_with_path = '%s\n %s' % (path, rule_description)
420 if rule_type == Rule.DISALLOW:
421 error_descriptions.append(description_with_path)
422 else:
423 warning_descriptions.append(description_with_path)
425 results = []
426 if error_descriptions:
427 results.append(output_api.PresubmitError(
428 'You added one or more #includes that violate checkdeps rules.',
429 error_descriptions))
430 if warning_descriptions:
431 if not input_api.is_committing:
432 warning_factory = output_api.PresubmitPromptWarning
433 else:
434 # We don't want to block use of the CQ when there is a warning
435 # of this kind, so we only show a message when committing.
436 warning_factory = output_api.PresubmitNotifyResult
437 results.append(warning_factory(
438 'You added one or more #includes of files that are temporarily\n'
439 'allowed but being removed. Can you avoid introducing the\n'
440 '#include? See relevant DEPS file(s) for details and contacts.',
441 warning_descriptions))
442 return results
445 def _CheckFilePermissions(input_api, output_api):
446 """Check that all files have their permissions properly set."""
447 args = [sys.executable, 'tools/checkperms/checkperms.py', '--root',
448 input_api.change.RepositoryRoot()]
449 for f in input_api.AffectedFiles():
450 args += ['--file', f.LocalPath()]
451 errors = []
452 (errors, stderrdata) = subprocess.Popen(args).communicate()
454 results = []
455 if errors:
456 results.append(output_api.PreSubmitError('checkperms.py failed.',
457 errors))
458 return results
461 def _CommonChecks(input_api, output_api):
462 """Checks common to both upload and commit."""
463 results = []
464 results.extend(input_api.canned_checks.PanProjectChecks(
465 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
466 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
467 results.extend(
468 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
469 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
470 results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
471 results.extend(_CheckNoNewWStrings(input_api, output_api))
472 results.extend(_CheckNoDEPSGIT(input_api, output_api))
473 results.extend(_CheckNoBannedFunctions(input_api, output_api))
474 results.extend(_CheckNoPragmaOnce(input_api, output_api))
475 results.extend(_CheckUnwantedDependencies(input_api, output_api))
476 results.extend(_CheckFilePermissions(input_api, output_api))
477 return results
480 def _CheckSubversionConfig(input_api, output_api):
481 """Verifies the subversion config file is correctly setup.
483 Checks that autoprops are enabled, returns an error otherwise.
485 join = input_api.os_path.join
486 if input_api.platform == 'win32':
487 appdata = input_api.environ.get('APPDATA', '')
488 if not appdata:
489 return [output_api.PresubmitError('%APPDATA% is not configured.')]
490 path = join(appdata, 'Subversion', 'config')
491 else:
492 home = input_api.environ.get('HOME', '')
493 if not home:
494 return [output_api.PresubmitError('$HOME is not configured.')]
495 path = join(home, '.subversion', 'config')
497 error_msg = (
498 'Please look at http://dev.chromium.org/developers/coding-style to\n'
499 'configure your subversion configuration file. This enables automatic\n'
500 'properties to simplify the project maintenance.\n'
501 'Pro-tip: just download and install\n'
502 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
504 try:
505 lines = open(path, 'r').read().splitlines()
506 # Make sure auto-props is enabled and check for 2 Chromium standard
507 # auto-prop.
508 if (not '*.cc = svn:eol-style=LF' in lines or
509 not '*.pdf = svn:mime-type=application/pdf' in lines or
510 not 'enable-auto-props = yes' in lines):
511 return [
512 output_api.PresubmitNotifyResult(
513 'It looks like you have not configured your subversion config '
514 'file or it is not up-to-date.\n' + error_msg)
516 except (OSError, IOError):
517 return [
518 output_api.PresubmitNotifyResult(
519 'Can\'t find your subversion config file.\n' + error_msg)
521 return []
524 def _CheckAuthorizedAuthor(input_api, output_api):
525 """For non-googler/chromites committers, verify the author's email address is
526 in AUTHORS.
528 # TODO(maruel): Add it to input_api?
529 import fnmatch
531 author = input_api.change.author_email
532 if not author:
533 input_api.logging.info('No author, skipping AUTHOR check')
534 return []
535 authors_path = input_api.os_path.join(
536 input_api.PresubmitLocalPath(), 'AUTHORS')
537 valid_authors = (
538 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
539 for line in open(authors_path))
540 valid_authors = [item.group(1).lower() for item in valid_authors if item]
541 if input_api.verbose:
542 print 'Valid authors are %s' % ', '.join(valid_authors)
543 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
544 return [output_api.PresubmitPromptWarning(
545 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
546 '\n'
547 'http://www.chromium.org/developers/contributing-code and read the '
548 '"Legal" section\n'
549 'If you are a chromite, verify the contributor signed the CLA.') %
550 author)]
551 return []
554 def CheckChangeOnUpload(input_api, output_api):
555 results = []
556 results.extend(_CommonChecks(input_api, output_api))
557 return results
560 def CheckChangeOnCommit(input_api, output_api):
561 results = []
562 results.extend(_CommonChecks(input_api, output_api))
563 # TODO(thestig) temporarily disabled, doesn't work in third_party/
564 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
565 # input_api, output_api, sources))
566 # Make sure the tree is 'open'.
567 results.extend(input_api.canned_checks.CheckTreeIsOpen(
568 input_api,
569 output_api,
570 json_url='http://chromium-status.appspot.com/current?format=json'))
571 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
572 output_api, 'http://codereview.chromium.org',
573 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
574 'tryserver@chromium.org'))
576 results.extend(input_api.canned_checks.CheckChangeHasBugField(
577 input_api, output_api))
578 results.extend(input_api.canned_checks.CheckChangeHasDescription(
579 input_api, output_api))
580 results.extend(_CheckSubversionConfig(input_api, output_api))
581 return results
584 def GetPreferredTrySlaves(project, change):
585 files = change.LocalPaths()
587 if not files:
588 return []
590 if all(re.search('\.(m|mm)$|[/_]mac[/_.]', f) for f in files):
591 return ['mac_rel']
592 if all(re.search('[/_]win[/_.]', f) for f in files):
593 return ['win_rel']
594 if all(re.search('[/_]android[/_.]', f) for f in files):
595 return ['android']
597 trybots = ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile',
598 'android']
600 # Match things like path/aura/file.cc and path/file_aura.cc.
601 # Same for ash and chromeos.
602 if any(re.search('[/_](ash|aura)', f) for f in files):
603 trybots += ['linux_chromeos', 'linux_chromeos_clang:compile', 'win_aura']
604 else:
605 if any(re.search('[/_]chromeos', f) for f in files):
606 trybots += ['linux_chromeos', 'linux_chromeos_clang:compile']
608 return trybots