These improvements were made in a separate cl (https://chromiumcodereview.appspot...
[chromium-blink-merge.git] / PRESUBMIT.py
blobeeb412a0a15576412852f598616305901d52b30f
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[\\\/]src[\\\/]build_tools[\\\/]make_rules.py",
20 r"^native_client_sdk[\\\/]src[\\\/]build_tools[\\\/]make_simple.py",
21 r"^native_client_sdk[\\\/]src[\\\/]tools[\\\/].*.mk",
22 r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
23 r"^skia[\\\/].*",
24 r"^v8[\\\/].*",
25 r".*MakeFile$",
26 r".+_autogen\.h$",
27 r"^cc[\\\/].*",
28 r".+[\\\/]pnacl_shim\.c$",
31 # Fragment of a regular expression that matches file name suffixes
32 # used to indicate different platforms.
33 _PLATFORM_SPECIFIERS = r'(_(android|chromeos|gtk|mac|posix|win))?'
35 # Fragment of a regular expression that matches C++ and Objective-C++
36 # implementation files.
37 _IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
39 # Regular expression that matches code only used for test binaries
40 # (best effort).
41 _TEST_CODE_EXCLUDED_PATHS = (
42 r'.*[/\\](fake_|test_|mock_).+%s' % _IMPLEMENTATION_EXTENSIONS,
43 r'.+_test_(base|support|util)%s' % _IMPLEMENTATION_EXTENSIONS,
44 r'.+_(api|browser|perf|unit|ui)?test%s%s' % (_PLATFORM_SPECIFIERS,
45 _IMPLEMENTATION_EXTENSIONS),
46 r'.+profile_sync_service_harness%s' % _IMPLEMENTATION_EXTENSIONS,
47 r'.*[/\\](test|tool(s)?)[/\\].*',
48 # At request of folks maintaining this folder.
49 r'chrome[/\\]browser[/\\]automation[/\\].*',
52 _TEST_ONLY_WARNING = (
53 'You might be calling functions intended only for testing from\n'
54 'production code. It is OK to ignore this warning if you know what\n'
55 'you are doing, as the heuristics used to detect the situation are\n'
56 'not perfect. The commit queue will not block on this warning.\n'
57 'Email joi@chromium.org if you have questions.')
60 _INCLUDE_ORDER_WARNING = (
61 'Your #include order seems to be broken. Send mail to\n'
62 'marja@chromium.org if this is not the case.')
65 _BANNED_OBJC_FUNCTIONS = (
67 'addTrackingRect:',
69 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
70 'prohibited. Please use CrTrackingArea instead.',
71 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
73 False,
76 'NSTrackingArea',
78 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
79 'instead.',
80 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
82 False,
85 'convertPointFromBase:',
87 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
88 'Please use |convertPoint:(point) fromView:nil| instead.',
89 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
91 True,
94 'convertPointToBase:',
96 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
97 'Please use |convertPoint:(point) toView:nil| instead.',
98 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
100 True,
103 'convertRectFromBase:',
105 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
106 'Please use |convertRect:(point) fromView:nil| instead.',
107 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
109 True,
112 'convertRectToBase:',
114 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
115 'Please use |convertRect:(point) toView:nil| instead.',
116 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
118 True,
121 'convertSizeFromBase:',
123 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
124 'Please use |convertSize:(point) fromView:nil| instead.',
125 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
127 True,
130 'convertSizeToBase:',
132 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
133 'Please use |convertSize:(point) toView:nil| instead.',
134 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
136 True,
141 _BANNED_CPP_FUNCTIONS = (
142 # Make sure that gtest's FRIEND_TEST() macro is not used; the
143 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
144 # used instead since that allows for FLAKY_ and DISABLED_ prefixes.
146 'FRIEND_TEST(',
148 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
149 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
151 False,
155 'ScopedAllowIO',
157 'New code should not use ScopedAllowIO. Post a task to the blocking',
158 'pool or the FILE thread instead.',
160 True,
162 r"^content[\\\/]shell[\\\/]shell_browser_main\.cc$",
168 _VALID_OS_MACROS = (
169 # Please keep sorted.
170 'OS_ANDROID',
171 'OS_BSD',
172 'OS_CAT', # For testing.
173 'OS_CHROMEOS',
174 'OS_FREEBSD',
175 'OS_IOS',
176 'OS_LINUX',
177 'OS_MACOSX',
178 'OS_NACL',
179 'OS_OPENBSD',
180 'OS_POSIX',
181 'OS_SOLARIS',
182 'OS_SUN', # Not in build/build_config.h but in skia.
183 'OS_WIN',
187 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
188 """Attempts to prevent use of functions intended only for testing in
189 non-testing code. For now this is just a best-effort implementation
190 that ignores header files and may have some false positives. A
191 better implementation would probably need a proper C++ parser.
193 # We only scan .cc files and the like, as the declaration of
194 # for-testing functions in header files are hard to distinguish from
195 # calls to such functions without a proper C++ parser.
196 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
198 base_function_pattern = r'ForTest(ing)?|for_test(ing)?'
199 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
200 exclusion_pattern = input_api.re.compile(
201 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
202 base_function_pattern, base_function_pattern))
204 def FilterFile(affected_file):
205 black_list = (_EXCLUDED_PATHS +
206 _TEST_CODE_EXCLUDED_PATHS +
207 input_api.DEFAULT_BLACK_LIST)
208 return input_api.FilterSourceFile(
209 affected_file,
210 white_list=(file_inclusion_pattern, ),
211 black_list=black_list)
213 problems = []
214 for f in input_api.AffectedSourceFiles(FilterFile):
215 local_path = f.LocalPath()
216 lines = input_api.ReadFile(f).splitlines()
217 line_number = 0
218 for line in lines:
219 if (inclusion_pattern.search(line) and
220 not exclusion_pattern.search(line)):
221 problems.append(
222 '%s:%d\n %s' % (local_path, line_number, line.strip()))
223 line_number += 1
225 if problems:
226 if not input_api.is_committing:
227 return [output_api.PresubmitPromptWarning(_TEST_ONLY_WARNING, problems)]
228 else:
229 # We don't warn on commit, to avoid stopping commits going through CQ.
230 return [output_api.PresubmitNotifyResult(_TEST_ONLY_WARNING, problems)]
231 else:
232 return []
235 def _CheckNoIOStreamInHeaders(input_api, output_api):
236 """Checks to make sure no .h files include <iostream>."""
237 files = []
238 pattern = input_api.re.compile(r'^#include\s*<iostream>',
239 input_api.re.MULTILINE)
240 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
241 if not f.LocalPath().endswith('.h'):
242 continue
243 contents = input_api.ReadFile(f)
244 if pattern.search(contents):
245 files.append(f)
247 if len(files):
248 return [ output_api.PresubmitError(
249 'Do not #include <iostream> in header files, since it inserts static '
250 'initialization into every file including the header. Instead, '
251 '#include <ostream>. See http://crbug.com/94794',
252 files) ]
253 return []
256 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
257 """Checks to make sure no source files use UNIT_TEST"""
258 problems = []
259 for f in input_api.AffectedFiles():
260 if (not f.LocalPath().endswith(('.cc', '.mm'))):
261 continue
263 for line_num, line in f.ChangedContents():
264 if 'UNIT_TEST' in line:
265 problems.append(' %s:%d' % (f.LocalPath(), line_num))
267 if not problems:
268 return []
269 return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
270 '\n'.join(problems))]
273 def _CheckNoNewWStrings(input_api, output_api):
274 """Checks to make sure we don't introduce use of wstrings."""
275 problems = []
276 for f in input_api.AffectedFiles():
277 if (not f.LocalPath().endswith(('.cc', '.h')) or
278 f.LocalPath().endswith('test.cc')):
279 continue
281 allowWString = False
282 for line_num, line in f.ChangedContents():
283 if 'presubmit: allow wstring' in line:
284 allowWString = True
285 elif not allowWString and 'wstring' in line:
286 problems.append(' %s:%d' % (f.LocalPath(), line_num))
287 allowWString = False
288 else:
289 allowWString = False
291 if not problems:
292 return []
293 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
294 ' If you are calling a cross-platform API that accepts a wstring, '
295 'fix the API.\n' +
296 '\n'.join(problems))]
299 def _CheckNoDEPSGIT(input_api, output_api):
300 """Make sure .DEPS.git is never modified manually."""
301 if any(f.LocalPath().endswith('.DEPS.git') for f in
302 input_api.AffectedFiles()):
303 return [output_api.PresubmitError(
304 'Never commit changes to .DEPS.git. This file is maintained by an\n'
305 'automated system based on what\'s in DEPS and your changes will be\n'
306 'overwritten.\n'
307 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
308 'for more information')]
309 return []
312 def _CheckNoBannedFunctions(input_api, output_api):
313 """Make sure that banned functions are not used."""
314 warnings = []
315 errors = []
317 file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
318 for f in input_api.AffectedFiles(file_filter=file_filter):
319 for line_num, line in f.ChangedContents():
320 for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
321 if func_name in line:
322 problems = warnings;
323 if error:
324 problems = errors;
325 problems.append(' %s:%d:' % (f.LocalPath(), line_num))
326 for message_line in message:
327 problems.append(' %s' % message_line)
329 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
330 for f in input_api.AffectedFiles(file_filter=file_filter):
331 for line_num, line in f.ChangedContents():
332 for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
333 def IsBlacklisted(affected_file, blacklist):
334 local_path = affected_file.LocalPath()
335 for item in blacklist:
336 if input_api.re.match(item, local_path):
337 return True
338 return False
339 if IsBlacklisted(f, excluded_paths):
340 continue
341 if func_name in line:
342 problems = warnings;
343 if error:
344 problems = errors;
345 problems.append(' %s:%d:' % (f.LocalPath(), line_num))
346 for message_line in message:
347 problems.append(' %s' % message_line)
349 result = []
350 if (warnings):
351 result.append(output_api.PresubmitPromptWarning(
352 'Banned functions were used.\n' + '\n'.join(warnings)))
353 if (errors):
354 result.append(output_api.PresubmitError(
355 'Banned functions were used.\n' + '\n'.join(errors)))
356 return result
359 def _CheckNoPragmaOnce(input_api, output_api):
360 """Make sure that banned functions are not used."""
361 files = []
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'):
366 continue
367 contents = input_api.ReadFile(f)
368 if pattern.search(contents):
369 files.append(f)
371 if files:
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',
375 files)]
376 return []
379 def _CheckNoTrinaryTrueFalse(input_api, output_api):
380 """Checks to make sure we don't introduce use of foo ? true : false."""
381 problems = []
382 pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
383 for f in input_api.AffectedFiles():
384 if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
385 continue
387 for line_num, line in f.ChangedContents():
388 if pattern.match(line):
389 problems.append(' %s:%d' % (f.LocalPath(), line_num))
391 if not problems:
392 return []
393 return [output_api.PresubmitPromptWarning(
394 'Please consider avoiding the "? true : false" pattern if possible.\n' +
395 '\n'.join(problems))]
398 def _CheckUnwantedDependencies(input_api, output_api):
399 """Runs checkdeps on #include statements added in this
400 change. Breaking - rules is an error, breaking ! rules is a
401 warning.
403 # We need to wait until we have an input_api object and use this
404 # roundabout construct to import checkdeps because this file is
405 # eval-ed and thus doesn't have __file__.
406 original_sys_path = sys.path
407 try:
408 sys.path = sys.path + [input_api.os_path.join(
409 input_api.PresubmitLocalPath(), 'tools', 'checkdeps')]
410 import checkdeps
411 from cpp_checker import CppChecker
412 from rules import Rule
413 finally:
414 # Restore sys.path to what it was before.
415 sys.path = original_sys_path
417 added_includes = []
418 for f in input_api.AffectedFiles():
419 if not CppChecker.IsCppFile(f.LocalPath()):
420 continue
422 changed_lines = [line for line_num, line in f.ChangedContents()]
423 added_includes.append([f.LocalPath(), changed_lines])
425 deps_checker = checkdeps.DepsChecker()
427 error_descriptions = []
428 warning_descriptions = []
429 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
430 added_includes):
431 description_with_path = '%s\n %s' % (path, rule_description)
432 if rule_type == Rule.DISALLOW:
433 error_descriptions.append(description_with_path)
434 else:
435 warning_descriptions.append(description_with_path)
437 results = []
438 if error_descriptions:
439 results.append(output_api.PresubmitError(
440 'You added one or more #includes that violate checkdeps rules.',
441 error_descriptions))
442 if warning_descriptions:
443 if not input_api.is_committing:
444 warning_factory = output_api.PresubmitPromptWarning
445 else:
446 # We don't want to block use of the CQ when there is a warning
447 # of this kind, so we only show a message when committing.
448 warning_factory = output_api.PresubmitNotifyResult
449 results.append(warning_factory(
450 'You added one or more #includes of files that are temporarily\n'
451 'allowed but being removed. Can you avoid introducing the\n'
452 '#include? See relevant DEPS file(s) for details and contacts.',
453 warning_descriptions))
454 return results
457 def _CheckFilePermissions(input_api, output_api):
458 """Check that all files have their permissions properly set."""
459 args = [sys.executable, 'tools/checkperms/checkperms.py', '--root',
460 input_api.change.RepositoryRoot()]
461 for f in input_api.AffectedFiles():
462 args += ['--file', f.LocalPath()]
463 errors = []
464 (errors, stderrdata) = subprocess.Popen(args).communicate()
466 results = []
467 if errors:
468 results.append(output_api.PresubmitError('checkperms.py failed.',
469 errors))
470 return results
473 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
474 """Makes sure we don't include ui/aura/window_property.h
475 in header files.
477 pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
478 errors = []
479 for f in input_api.AffectedFiles():
480 if not f.LocalPath().endswith('.h'):
481 continue
482 for line_num, line in f.ChangedContents():
483 if pattern.match(line):
484 errors.append(' %s:%d' % (f.LocalPath(), line_num))
486 results = []
487 if errors:
488 results.append(output_api.PresubmitError(
489 'Header files should not include ui/aura/window_property.h', errors))
490 return results
493 def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
494 """Checks that the lines in scope occur in the right order.
496 1. C system files in alphabetical order
497 2. C++ system files in alphabetical order
498 3. Project's .h files
501 c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
502 cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
503 custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
505 C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
507 state = C_SYSTEM_INCLUDES
509 previous_line = ''
510 previous_line_num = 0
511 problem_linenums = []
512 for line_num, line in scope:
513 if c_system_include_pattern.match(line):
514 if state != C_SYSTEM_INCLUDES:
515 problem_linenums.append((line_num, previous_line_num))
516 elif previous_line and previous_line > line:
517 problem_linenums.append((line_num, previous_line_num))
518 elif cpp_system_include_pattern.match(line):
519 if state == C_SYSTEM_INCLUDES:
520 state = CPP_SYSTEM_INCLUDES
521 elif state == CUSTOM_INCLUDES:
522 problem_linenums.append((line_num, previous_line_num))
523 elif previous_line and previous_line > line:
524 problem_linenums.append((line_num, previous_line_num))
525 elif custom_include_pattern.match(line):
526 if state != CUSTOM_INCLUDES:
527 state = CUSTOM_INCLUDES
528 elif previous_line and previous_line > line:
529 problem_linenums.append((line_num, previous_line_num))
530 else:
531 problem_linenums.append(line_num)
532 previous_line = line
533 previous_line_num = line_num
535 warnings = []
536 for (line_num, previous_line_num) in problem_linenums:
537 if line_num in changed_linenums or previous_line_num in changed_linenums:
538 warnings.append(' %s:%d' % (file_path, line_num))
539 return warnings
542 def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
543 """Checks the #include order for the given file f."""
545 system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
546 # Exclude #include <.../...> includes from the check; e.g., <sys/...> includes
547 # often need to appear in a specific order.
548 excluded_include_pattern = input_api.re.compile(r'\s*#include \<.*/.*')
549 custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
550 if_pattern = input_api.re.compile(
551 r'\s*#\s*(if|elif|else|endif|define|undef).*')
552 # Some files need specialized order of includes; exclude such files from this
553 # check.
554 uncheckable_includes_pattern = input_api.re.compile(
555 r'\s*#include '
556 '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
558 contents = f.NewContents()
559 warnings = []
560 line_num = 0
562 # Handle the special first include. If the first include file is
563 # some/path/file.h, the corresponding including file can be some/path/file.cc,
564 # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
565 # etc. It's also possible that no special first include exists.
566 for line in contents:
567 line_num += 1
568 if system_include_pattern.match(line):
569 # No special first include -> process the line again along with normal
570 # includes.
571 line_num -= 1
572 break
573 match = custom_include_pattern.match(line)
574 if match:
575 match_dict = match.groupdict()
576 header_basename = input_api.os_path.basename(
577 match_dict['FILE']).replace('.h', '')
578 if header_basename not in input_api.os_path.basename(f.LocalPath()):
579 # No special first include -> process the line again along with normal
580 # includes.
581 line_num -= 1
582 break
584 # Split into scopes: Each region between #if and #endif is its own scope.
585 scopes = []
586 current_scope = []
587 for line in contents[line_num:]:
588 line_num += 1
589 if uncheckable_includes_pattern.match(line):
590 return []
591 if if_pattern.match(line):
592 scopes.append(current_scope)
593 current_scope = []
594 elif ((system_include_pattern.match(line) or
595 custom_include_pattern.match(line)) and
596 not excluded_include_pattern.match(line)):
597 current_scope.append((line_num, line))
598 scopes.append(current_scope)
600 for scope in scopes:
601 warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
602 changed_linenums))
603 return warnings
606 def _CheckIncludeOrder(input_api, output_api):
607 """Checks that the #include order is correct.
609 1. The corresponding header for source files.
610 2. C system files in alphabetical order
611 3. C++ system files in alphabetical order
612 4. Project's .h files in alphabetical order
614 Each region separated by #if, #elif, #else, #endif, #define and #undef follows
615 these rules separately.
618 warnings = []
619 for f in input_api.AffectedFiles():
620 if f.LocalPath().endswith(('.cc', '.h')):
621 changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
622 warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
624 results = []
625 if warnings:
626 if not input_api.is_committing:
627 results.append(output_api.PresubmitPromptWarning(_INCLUDE_ORDER_WARNING,
628 warnings))
629 else:
630 # We don't warn on commit, to avoid stopping commits going through CQ.
631 results.append(output_api.PresubmitNotifyResult(_INCLUDE_ORDER_WARNING,
632 warnings))
633 return results
636 def _CheckForVersionControlConflictsInFile(input_api, f):
637 pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
638 errors = []
639 for line_num, line in f.ChangedContents():
640 if pattern.match(line):
641 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
642 return errors
645 def _CheckForVersionControlConflicts(input_api, output_api):
646 """Usually this is not intentional and will cause a compile failure."""
647 errors = []
648 for f in input_api.AffectedFiles():
649 errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
651 results = []
652 if errors:
653 results.append(output_api.PresubmitError(
654 'Version control conflict markers found, please resolve.', errors))
655 return results
658 def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
659 def FilterFile(affected_file):
660 """Filter function for use with input_api.AffectedSourceFiles,
661 below. This filters out everything except non-test files from
662 top-level directories that generally speaking should not hard-code
663 service URLs (e.g. src/android_webview/, src/content/ and others).
665 return input_api.FilterSourceFile(
666 affected_file,
667 white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
668 black_list=(_EXCLUDED_PATHS +
669 _TEST_CODE_EXCLUDED_PATHS +
670 input_api.DEFAULT_BLACK_LIST))
672 pattern = input_api.re.compile('"[^"]*google\.com[^"]*"')
673 problems = [] # items are (filename, line_number, line)
674 for f in input_api.AffectedSourceFiles(FilterFile):
675 for line_num, line in f.ChangedContents():
676 if pattern.search(line):
677 problems.append((f.LocalPath(), line_num, line))
679 if problems:
680 if not input_api.is_committing:
681 warning_factory = output_api.PresubmitPromptWarning
682 else:
683 # We don't want to block use of the CQ when there is a warning
684 # of this kind, so we only show a message when committing.
685 warning_factory = output_api.PresubmitNotifyResult
686 return [warning_factory(
687 'Most layers below src/chrome/ should not hardcode service URLs.\n'
688 'Are you sure this is correct? (Contact: joi@chromium.org)',
689 [' %s:%d: %s' % (
690 problem[0], problem[1], problem[2]) for problem in problems])]
691 else:
692 return []
695 def _CheckNoAbbreviationInPngFileName(input_api, output_api):
696 """Makes sure there are no abbreviations in the name of PNG files.
698 pattern = input_api.re.compile(r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$')
699 errors = []
700 for f in input_api.AffectedFiles(include_deletes=False):
701 if pattern.match(f.LocalPath()):
702 errors.append(' %s' % f.LocalPath())
704 results = []
705 if errors:
706 results.append(output_api.PresubmitError(
707 'The name of PNG files should not have abbreviations. \n'
708 'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
709 'Contact oshima@chromium.org if you have questions.', errors))
710 return results
713 def _CommonChecks(input_api, output_api):
714 """Checks common to both upload and commit."""
715 results = []
716 results.extend(input_api.canned_checks.PanProjectChecks(
717 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
718 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
719 results.extend(
720 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
721 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
722 results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
723 results.extend(_CheckNoNewWStrings(input_api, output_api))
724 results.extend(_CheckNoDEPSGIT(input_api, output_api))
725 results.extend(_CheckNoBannedFunctions(input_api, output_api))
726 results.extend(_CheckNoPragmaOnce(input_api, output_api))
727 results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
728 results.extend(_CheckUnwantedDependencies(input_api, output_api))
729 results.extend(_CheckFilePermissions(input_api, output_api))
730 results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
731 results.extend(_CheckIncludeOrder(input_api, output_api))
732 results.extend(_CheckForVersionControlConflicts(input_api, output_api))
733 results.extend(_CheckPatchFiles(input_api, output_api))
734 results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
735 results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
736 results.extend(_CheckForInvalidOSMacros(input_api, output_api))
738 if any('PRESUBMIT.py' == f.LocalPath() for f in input_api.AffectedFiles()):
739 results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
740 input_api, output_api,
741 input_api.PresubmitLocalPath(),
742 whitelist=[r'^PRESUBMIT_test\.py$']))
743 return results
746 def _CheckSubversionConfig(input_api, output_api):
747 """Verifies the subversion config file is correctly setup.
749 Checks that autoprops are enabled, returns an error otherwise.
751 join = input_api.os_path.join
752 if input_api.platform == 'win32':
753 appdata = input_api.environ.get('APPDATA', '')
754 if not appdata:
755 return [output_api.PresubmitError('%APPDATA% is not configured.')]
756 path = join(appdata, 'Subversion', 'config')
757 else:
758 home = input_api.environ.get('HOME', '')
759 if not home:
760 return [output_api.PresubmitError('$HOME is not configured.')]
761 path = join(home, '.subversion', 'config')
763 error_msg = (
764 'Please look at http://dev.chromium.org/developers/coding-style to\n'
765 'configure your subversion configuration file. This enables automatic\n'
766 'properties to simplify the project maintenance.\n'
767 'Pro-tip: just download and install\n'
768 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
770 try:
771 lines = open(path, 'r').read().splitlines()
772 # Make sure auto-props is enabled and check for 2 Chromium standard
773 # auto-prop.
774 if (not '*.cc = svn:eol-style=LF' in lines or
775 not '*.pdf = svn:mime-type=application/pdf' in lines or
776 not 'enable-auto-props = yes' in lines):
777 return [
778 output_api.PresubmitNotifyResult(
779 'It looks like you have not configured your subversion config '
780 'file or it is not up-to-date.\n' + error_msg)
782 except (OSError, IOError):
783 return [
784 output_api.PresubmitNotifyResult(
785 'Can\'t find your subversion config file.\n' + error_msg)
787 return []
790 def _CheckAuthorizedAuthor(input_api, output_api):
791 """For non-googler/chromites committers, verify the author's email address is
792 in AUTHORS.
794 # TODO(maruel): Add it to input_api?
795 import fnmatch
797 author = input_api.change.author_email
798 if not author:
799 input_api.logging.info('No author, skipping AUTHOR check')
800 return []
801 authors_path = input_api.os_path.join(
802 input_api.PresubmitLocalPath(), 'AUTHORS')
803 valid_authors = (
804 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
805 for line in open(authors_path))
806 valid_authors = [item.group(1).lower() for item in valid_authors if item]
807 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
808 input_api.logging.info('Valid authors are %s', ', '.join(valid_authors))
809 return [output_api.PresubmitPromptWarning(
810 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
811 '\n'
812 'http://www.chromium.org/developers/contributing-code and read the '
813 '"Legal" section\n'
814 'If you are a chromite, verify the contributor signed the CLA.') %
815 author)]
816 return []
819 def _CheckPatchFiles(input_api, output_api):
820 problems = [f.LocalPath() for f in input_api.AffectedFiles()
821 if f.LocalPath().endswith(('.orig', '.rej'))]
822 if problems:
823 return [output_api.PresubmitError(
824 "Don't commit .rej and .orig files.", problems)]
825 else:
826 return []
829 def _DidYouMeanOSMacro(bad_macro):
830 try:
831 return {'A': 'OS_ANDROID',
832 'B': 'OS_BSD',
833 'C': 'OS_CHROMEOS',
834 'F': 'OS_FREEBSD',
835 'L': 'OS_LINUX',
836 'M': 'OS_MACOSX',
837 'N': 'OS_NACL',
838 'O': 'OS_OPENBSD',
839 'P': 'OS_POSIX',
840 'S': 'OS_SOLARIS',
841 'W': 'OS_WIN'}[bad_macro[3].upper()]
842 except KeyError:
843 return ''
846 def _CheckForInvalidOSMacrosInFile(input_api, f):
847 """Check for sensible looking, totally invalid OS macros."""
848 preprocessor_statement = input_api.re.compile(r'^\s*#')
849 os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
850 results = []
851 for lnum, line in f.ChangedContents():
852 if preprocessor_statement.search(line):
853 for match in os_macro.finditer(line):
854 if not match.group(1) in _VALID_OS_MACROS:
855 good = _DidYouMeanOSMacro(match.group(1))
856 did_you_mean = ' (did you mean %s?)' % good if good else ''
857 results.append(' %s:%d %s%s' % (f.LocalPath(),
858 lnum,
859 match.group(1),
860 did_you_mean))
861 return results
864 def _CheckForInvalidOSMacros(input_api, output_api):
865 """Check all affected files for invalid OS macros."""
866 bad_macros = []
867 for f in input_api.AffectedFiles():
868 if not f.LocalPath().endswith(('.py', '.js', '.html', '.css')):
869 bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
871 if not bad_macros:
872 return []
874 return [output_api.PresubmitError(
875 'Possibly invalid OS macro[s] found. Please fix your code\n'
876 'or add your macro to src/PRESUBMIT.py.', bad_macros)]
879 def CheckChangeOnUpload(input_api, output_api):
880 results = []
881 results.extend(_CommonChecks(input_api, output_api))
882 return results
885 def CheckChangeOnCommit(input_api, output_api):
886 results = []
887 results.extend(_CommonChecks(input_api, output_api))
888 # TODO(thestig) temporarily disabled, doesn't work in third_party/
889 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
890 # input_api, output_api, sources))
891 # Make sure the tree is 'open'.
892 results.extend(input_api.canned_checks.CheckTreeIsOpen(
893 input_api,
894 output_api,
895 json_url='http://chromium-status.appspot.com/current?format=json'))
896 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
897 output_api, 'http://codereview.chromium.org',
898 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
899 'tryserver@chromium.org'))
901 results.extend(input_api.canned_checks.CheckChangeHasBugField(
902 input_api, output_api))
903 results.extend(input_api.canned_checks.CheckChangeHasDescription(
904 input_api, output_api))
905 results.extend(_CheckSubversionConfig(input_api, output_api))
906 return results
909 def GetPreferredTrySlaves(project, change):
910 files = change.LocalPaths()
912 if not files or all(re.search(r'[\\/]OWNERS$', f) for f in files):
913 return []
915 if all(re.search('\.(m|mm)$|(^|[/_])mac[/_.]', f) for f in files):
916 return ['mac_rel', 'mac_asan', 'mac:compile']
917 if all(re.search('(^|[/_])win[/_.]', f) for f in files):
918 return ['win_rel', 'win7_aura', 'win:compile']
919 if all(re.search('(^|[/_])android[/_.]', f) for f in files):
920 return ['android_dbg', 'android_clang_dbg']
921 if all(re.search('^native_client_sdk', f) for f in files):
922 return ['linux_nacl_sdk', 'win_nacl_sdk', 'mac_nacl_sdk']
923 if all(re.search('[/_]ios[/_.]', f) for f in files):
924 return ['ios_rel_device', 'ios_dbg_simulator']
926 trybots = [
927 'android_clang_dbg',
928 'android_dbg',
929 'ios_dbg_simulator',
930 'ios_rel_device',
931 'linux_asan',
932 'linux_aura',
933 'linux_chromeos',
934 'linux_clang:compile',
935 'linux_rel',
936 'mac_asan',
937 'mac_rel',
938 'mac:compile',
939 'win7_aura',
940 'win_rel',
941 'win:compile',
944 # Match things like path/aura/file.cc and path/file_aura.cc.
945 # Same for chromeos.
946 if any(re.search('[/_](aura|chromeos)', f) for f in files):
947 trybots += ['linux_chromeos_clang:compile', 'linux_chromeos_asan']
949 return trybots