Change the metro / desktop restart strings again
[chromium-blink-merge.git] / PRESUBMIT.py
blobbf13dc6ab3b303ef4fcb0ff686c5134a956f6cdb
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$",
25 r"^cc[\\\/].*",
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 = (
41 'addTrackingRect:',
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',
47 False,
50 'NSTrackingArea',
52 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
53 'instead.',
54 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
56 False,
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',
65 True,
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',
74 True,
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',
83 True,
86 'convertRectToBase:',
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',
92 True,
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',
101 True,
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',
110 True,
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.
120 'FRIEND_TEST(',
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.',
125 False,
128 'ScopedAllowIO',
130 'New code should not use ScopedAllowIO. Post a task to the blocking',
131 'pool or the FILE thread instead.',
133 True,
136 'FilePathWatcher::Delegate',
138 'New code should not use FilePathWatcher::Delegate. Use the callback',
139 'interface instead.',
141 False,
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.',
150 True,
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.',
159 True,
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.',
168 True,
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.',
177 True,
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,
199 source_extensions),
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(
218 affected_file,
219 white_list=(file_inclusion_pattern, ),
220 black_list=black_list)
222 problems = []
223 for f in input_api.AffectedSourceFiles(FilterFile):
224 local_path = f.LocalPath()
225 lines = input_api.ReadFile(f).splitlines()
226 line_number = 0
227 for line in lines:
228 if (inclusion_pattern.search(line) and
229 not exclusion_pattern.search(line)):
230 problems.append(
231 '%s:%d\n %s' % (local_path, line_number, line.strip()))
232 line_number += 1
234 if problems:
235 if not input_api.is_committing:
236 return [output_api.PresubmitPromptWarning(_TEST_ONLY_WARNING, problems)]
237 else:
238 # We don't warn on commit, to avoid stopping commits going through CQ.
239 return [output_api.PresubmitNotifyResult(_TEST_ONLY_WARNING, problems)]
240 else:
241 return []
244 def _CheckNoIOStreamInHeaders(input_api, output_api):
245 """Checks to make sure no .h files include <iostream>."""
246 files = []
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'):
251 continue
252 contents = input_api.ReadFile(f)
253 if pattern.search(contents):
254 files.append(f)
256 if len(files):
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',
261 files) ]
262 return []
265 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
266 """Checks to make sure no source files use UNIT_TEST"""
267 problems = []
268 for f in input_api.AffectedFiles():
269 if (not f.LocalPath().endswith(('.cc', '.mm'))):
270 continue
272 for line_num, line in f.ChangedContents():
273 if 'UNIT_TEST' in line:
274 problems.append(' %s:%d' % (f.LocalPath(), line_num))
276 if not problems:
277 return []
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."""
284 problems = []
285 for f in input_api.AffectedFiles():
286 if (not f.LocalPath().endswith(('.cc', '.h')) or
287 f.LocalPath().endswith('test.cc')):
288 continue
290 allowWString = False
291 for line_num, line in f.ChangedContents():
292 if 'presubmit: allow wstring' in line:
293 allowWString = True
294 elif not allowWString and 'wstring' in line:
295 problems.append(' %s:%d' % (f.LocalPath(), line_num))
296 allowWString = False
297 else:
298 allowWString = False
300 if not problems:
301 return []
302 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
303 ' If you are calling a cross-platform API that accepts a wstring, '
304 'fix the API.\n' +
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'
315 'overwritten.\n'
316 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
317 'for more information')]
318 return []
321 def _CheckNoBannedFunctions(input_api, output_api):
322 """Make sure that banned functions are not used."""
323 warnings = []
324 errors = []
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:
331 problems = warnings;
332 if error:
333 problems = errors;
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:
343 problems = warnings;
344 if error:
345 problems = errors;
346 problems.append(' %s:%d:' % (f.LocalPath(), line_num))
347 for message_line in message:
348 problems.append(' %s' % message_line)
350 result = []
351 if (warnings):
352 result.append(output_api.PresubmitPromptWarning(
353 'Banned functions were used.\n' + '\n'.join(warnings)))
354 if (errors):
355 result.append(output_api.PresubmitError(
356 'Banned functions were used.\n' + '\n'.join(errors)))
357 return result
360 def _CheckNoPragmaOnce(input_api, output_api):
361 """Make sure that banned functions are not used."""
362 files = []
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'):
367 continue
368 contents = input_api.ReadFile(f)
369 if pattern.search(contents):
370 files.append(f)
372 if files:
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',
376 files)]
377 return []
380 def _CheckUnwantedDependencies(input_api, output_api):
381 """Runs checkdeps on #include statements added in this
382 change. Breaking - rules is an error, breaking ! rules is a
383 warning.
385 # We need to wait until we have an input_api object and use this
386 # roundabout construct to import checkdeps because this file is
387 # eval-ed and thus doesn't have __file__.
388 original_sys_path = sys.path
389 try:
390 sys.path = sys.path + [input_api.os_path.join(
391 input_api.PresubmitLocalPath(), 'tools', 'checkdeps')]
392 import checkdeps
393 from cpp_checker import CppChecker
394 from rules import Rule
395 finally:
396 # Restore sys.path to what it was before.
397 sys.path = original_sys_path
399 added_includes = []
400 for f in input_api.AffectedFiles():
401 if not CppChecker.IsCppFile(f.LocalPath()):
402 continue
404 changed_lines = [line for line_num, line in f.ChangedContents()]
405 added_includes.append([f.LocalPath(), changed_lines])
407 deps_checker = checkdeps.DepsChecker()
409 error_descriptions = []
410 warning_descriptions = []
411 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
412 added_includes):
413 description_with_path = '%s\n %s' % (path, rule_description)
414 if rule_type == Rule.DISALLOW:
415 error_descriptions.append(description_with_path)
416 else:
417 warning_descriptions.append(description_with_path)
419 results = []
420 if error_descriptions:
421 results.append(output_api.PresubmitError(
422 'You added one or more #includes that violate checkdeps rules.',
423 error_descriptions))
424 if warning_descriptions:
425 if not input_api.is_committing:
426 warning_factory = output_api.PresubmitPromptWarning
427 else:
428 # We don't want to block use of the CQ when there is a warning
429 # of this kind, so we only show a message when committing.
430 warning_factory = output_api.PresubmitNotifyResult
431 results.append(warning_factory(
432 'You added one or more #includes of files that are temporarily\n'
433 'allowed but being removed. Can you avoid introducing the\n'
434 '#include? See relevant DEPS file(s) for details and contacts.',
435 warning_descriptions))
436 return results
439 def _CheckFilePermissions(input_api, output_api):
440 """Check that all files have their permissions properly set."""
441 args = [sys.executable, 'tools/checkperms/checkperms.py', '--root',
442 input_api.change.RepositoryRoot()]
443 for f in input_api.AffectedFiles():
444 args += ['--file', f.LocalPath()]
445 errors = []
446 (errors, stderrdata) = subprocess.Popen(args).communicate()
448 results = []
449 if errors:
450 results.append(output_api.PreSubmitError('checkperms.py failed.',
451 errors))
452 return results
455 def _CommonChecks(input_api, output_api):
456 """Checks common to both upload and commit."""
457 results = []
458 results.extend(input_api.canned_checks.PanProjectChecks(
459 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
460 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
461 results.extend(
462 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
463 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
464 results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
465 results.extend(_CheckNoNewWStrings(input_api, output_api))
466 results.extend(_CheckNoDEPSGIT(input_api, output_api))
467 results.extend(_CheckNoBannedFunctions(input_api, output_api))
468 results.extend(_CheckNoPragmaOnce(input_api, output_api))
469 results.extend(_CheckUnwantedDependencies(input_api, output_api))
470 results.extend(_CheckFilePermissions(input_api, output_api))
471 return results
474 def _CheckSubversionConfig(input_api, output_api):
475 """Verifies the subversion config file is correctly setup.
477 Checks that autoprops are enabled, returns an error otherwise.
479 join = input_api.os_path.join
480 if input_api.platform == 'win32':
481 appdata = input_api.environ.get('APPDATA', '')
482 if not appdata:
483 return [output_api.PresubmitError('%APPDATA% is not configured.')]
484 path = join(appdata, 'Subversion', 'config')
485 else:
486 home = input_api.environ.get('HOME', '')
487 if not home:
488 return [output_api.PresubmitError('$HOME is not configured.')]
489 path = join(home, '.subversion', 'config')
491 error_msg = (
492 'Please look at http://dev.chromium.org/developers/coding-style to\n'
493 'configure your subversion configuration file. This enables automatic\n'
494 'properties to simplify the project maintenance.\n'
495 'Pro-tip: just download and install\n'
496 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
498 try:
499 lines = open(path, 'r').read().splitlines()
500 # Make sure auto-props is enabled and check for 2 Chromium standard
501 # auto-prop.
502 if (not '*.cc = svn:eol-style=LF' in lines or
503 not '*.pdf = svn:mime-type=application/pdf' in lines or
504 not 'enable-auto-props = yes' in lines):
505 return [
506 output_api.PresubmitNotifyResult(
507 'It looks like you have not configured your subversion config '
508 'file or it is not up-to-date.\n' + error_msg)
510 except (OSError, IOError):
511 return [
512 output_api.PresubmitNotifyResult(
513 'Can\'t find your subversion config file.\n' + error_msg)
515 return []
518 def _CheckAuthorizedAuthor(input_api, output_api):
519 """For non-googler/chromites committers, verify the author's email address is
520 in AUTHORS.
522 # TODO(maruel): Add it to input_api?
523 import fnmatch
525 author = input_api.change.author_email
526 if not author:
527 input_api.logging.info('No author, skipping AUTHOR check')
528 return []
529 authors_path = input_api.os_path.join(
530 input_api.PresubmitLocalPath(), 'AUTHORS')
531 valid_authors = (
532 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
533 for line in open(authors_path))
534 valid_authors = [item.group(1).lower() for item in valid_authors if item]
535 if input_api.verbose:
536 print 'Valid authors are %s' % ', '.join(valid_authors)
537 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
538 return [output_api.PresubmitPromptWarning(
539 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
540 '\n'
541 'http://www.chromium.org/developers/contributing-code and read the '
542 '"Legal" section\n'
543 'If you are a chromite, verify the contributor signed the CLA.') %
544 author)]
545 return []
548 def CheckChangeOnUpload(input_api, output_api):
549 results = []
550 results.extend(_CommonChecks(input_api, output_api))
551 return results
554 def CheckChangeOnCommit(input_api, output_api):
555 results = []
556 results.extend(_CommonChecks(input_api, output_api))
557 # TODO(thestig) temporarily disabled, doesn't work in third_party/
558 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
559 # input_api, output_api, sources))
560 # Make sure the tree is 'open'.
561 results.extend(input_api.canned_checks.CheckTreeIsOpen(
562 input_api,
563 output_api,
564 json_url='http://chromium-status.appspot.com/current?format=json'))
565 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
566 output_api, 'http://codereview.chromium.org',
567 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
568 'tryserver@chromium.org'))
570 results.extend(input_api.canned_checks.CheckChangeHasBugField(
571 input_api, output_api))
572 results.extend(input_api.canned_checks.CheckChangeHasDescription(
573 input_api, output_api))
574 results.extend(_CheckSubversionConfig(input_api, output_api))
575 return results
578 def GetPreferredTrySlaves(project, change):
579 files = change.LocalPaths()
581 if not files:
582 return []
584 if all(re.search('\.(m|mm)$|(^|[/_])mac[/_.]', f) for f in files):
585 return ['mac_rel', 'mac_asan']
586 if all(re.search('(^|[/_])win[/_.]', f) for f in files):
587 return ['win_rel']
588 if all(re.search('(^|[/_])android[/_.]', f) for f in files):
589 return ['android_dbg']
591 trybots = ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang:compile',
592 'linux_chromeos', 'android_dbg', 'linux_asan', 'mac_asan']
594 # Match things like path/aura/file.cc and path/file_aura.cc.
595 # Same for ash and chromeos.
596 if any(re.search('[/_](ash|aura)', f) for f in files):
597 trybots += ['linux_chromeos', 'linux_chromeos_clang:compile', 'win_aura',
598 'linux_chromeos_asan']
599 else:
600 if any(re.search('[/_]chromeos', f) for f in files):
601 trybots += ['linux_chromeos', 'linux_chromeos_clang:compile',
602 'linux_chromeos_asan']
604 return trybots