Home | History | Annotate | Download | only in chromium_org
      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.
      4 
      5 """Top-level presubmit script for Chromium.
      6 
      7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
      8 for more details about the presubmit API built into gcl.
      9 """
     10 
     11 
     12 import re
     13 import subprocess
     14 import sys
     15 
     16 
     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".+[\\\/]pnacl_shim\.c$",
     28 )
     29 
     30 # Fragment of a regular expression that matches C++ and Objective-C++
     31 # implementation files.
     32 _IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
     33 
     34 # Regular expression that matches code only used for test binaries
     35 # (best effort).
     36 _TEST_CODE_EXCLUDED_PATHS = (
     37     r'.*[/\\](fake_|test_|mock_).+%s' % _IMPLEMENTATION_EXTENSIONS,
     38     r'.+_test_(base|support|util)%s' % _IMPLEMENTATION_EXTENSIONS,
     39     r'.+_(api|browser|perf|pixel|unit|ui)?test(_[a-z]+)?%s' %
     40         _IMPLEMENTATION_EXTENSIONS,
     41     r'.+profile_sync_service_harness%s' % _IMPLEMENTATION_EXTENSIONS,
     42     r'.*[/\\](test|tool(s)?)[/\\].*',
     43     # content_shell is used for running layout tests.
     44     r'content[/\\]shell[/\\].*',
     45     # At request of folks maintaining this folder.
     46     r'chrome[/\\]browser[/\\]automation[/\\].*',
     47 )
     48 
     49 _TEST_ONLY_WARNING = (
     50     'You might be calling functions intended only for testing from\n'
     51     'production code.  It is OK to ignore this warning if you know what\n'
     52     'you are doing, as the heuristics used to detect the situation are\n'
     53     'not perfect.  The commit queue will not block on this warning.\n'
     54     'Email joi (at] chromium.org if you have questions.')
     55 
     56 
     57 _INCLUDE_ORDER_WARNING = (
     58     'Your #include order seems to be broken. Send mail to\n'
     59     'marja (at] chromium.org if this is not the case.')
     60 
     61 
     62 _BANNED_OBJC_FUNCTIONS = (
     63     (
     64       'addTrackingRect:',
     65       (
     66        'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
     67        'prohibited. Please use CrTrackingArea instead.',
     68        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
     69       ),
     70       False,
     71     ),
     72     (
     73       'NSTrackingArea',
     74       (
     75        'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
     76        'instead.',
     77        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
     78       ),
     79       False,
     80     ),
     81     (
     82       'convertPointFromBase:',
     83       (
     84        'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
     85        'Please use |convertPoint:(point) fromView:nil| instead.',
     86        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
     87       ),
     88       True,
     89     ),
     90     (
     91       'convertPointToBase:',
     92       (
     93        'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
     94        'Please use |convertPoint:(point) toView:nil| instead.',
     95        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
     96       ),
     97       True,
     98     ),
     99     (
    100       'convertRectFromBase:',
    101       (
    102        'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
    103        'Please use |convertRect:(point) fromView:nil| instead.',
    104        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
    105       ),
    106       True,
    107     ),
    108     (
    109       'convertRectToBase:',
    110       (
    111        'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
    112        'Please use |convertRect:(point) toView:nil| instead.',
    113        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
    114       ),
    115       True,
    116     ),
    117     (
    118       'convertSizeFromBase:',
    119       (
    120        'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
    121        'Please use |convertSize:(point) fromView:nil| instead.',
    122        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
    123       ),
    124       True,
    125     ),
    126     (
    127       'convertSizeToBase:',
    128       (
    129        'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
    130        'Please use |convertSize:(point) toView:nil| instead.',
    131        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
    132       ),
    133       True,
    134     ),
    135 )
    136 
    137 
    138 _BANNED_CPP_FUNCTIONS = (
    139     # Make sure that gtest's FRIEND_TEST() macro is not used; the
    140     # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
    141     # used instead since that allows for FLAKY_ and DISABLED_ prefixes.
    142     (
    143       'FRIEND_TEST(',
    144       (
    145        'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
    146        'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
    147       ),
    148       False,
    149       (),
    150     ),
    151     (
    152       'ScopedAllowIO',
    153       (
    154        'New code should not use ScopedAllowIO. Post a task to the blocking',
    155        'pool or the FILE thread instead.',
    156       ),
    157       True,
    158       (
    159         r"^content[\\\/]shell[\\\/]shell_browser_main\.cc$",
    160         r"^net[\\\/]disk_cache[\\\/]cache_util\.cc$",
    161       ),
    162     ),
    163     (
    164       'SkRefPtr',
    165       (
    166         'The use of SkRefPtr is prohibited. ',
    167         'Please use skia::RefPtr instead.'
    168       ),
    169       True,
    170       (),
    171     ),
    172     (
    173       'SkAutoRef',
    174       (
    175         'The indirect use of SkRefPtr via SkAutoRef is prohibited. ',
    176         'Please use skia::RefPtr instead.'
    177       ),
    178       True,
    179       (),
    180     ),
    181     (
    182       'SkAutoTUnref',
    183       (
    184         'The use of SkAutoTUnref is dangerous because it implicitly ',
    185         'converts to a raw pointer. Please use skia::RefPtr instead.'
    186       ),
    187       True,
    188       (),
    189     ),
    190     (
    191       'SkAutoUnref',
    192       (
    193         'The indirect use of SkAutoTUnref through SkAutoUnref is dangerous ',
    194         'because it implicitly converts to a raw pointer. ',
    195         'Please use skia::RefPtr instead.'
    196       ),
    197       True,
    198       (),
    199     ),
    200 )
    201 
    202 
    203 _VALID_OS_MACROS = (
    204     # Please keep sorted.
    205     'OS_ANDROID',
    206     'OS_BSD',
    207     'OS_CAT',       # For testing.
    208     'OS_CHROMEOS',
    209     'OS_FREEBSD',
    210     'OS_IOS',
    211     'OS_LINUX',
    212     'OS_MACOSX',
    213     'OS_NACL',
    214     'OS_OPENBSD',
    215     'OS_POSIX',
    216     'OS_SOLARIS',
    217     'OS_WIN',
    218 )
    219 
    220 
    221 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
    222   """Attempts to prevent use of functions intended only for testing in
    223   non-testing code. For now this is just a best-effort implementation
    224   that ignores header files and may have some false positives. A
    225   better implementation would probably need a proper C++ parser.
    226   """
    227   # We only scan .cc files and the like, as the declaration of
    228   # for-testing functions in header files are hard to distinguish from
    229   # calls to such functions without a proper C++ parser.
    230   file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
    231 
    232   base_function_pattern = r'ForTest(ing)?|for_test(ing)?'
    233   inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
    234   comment_pattern = input_api.re.compile(r'//.*%s' % base_function_pattern)
    235   exclusion_pattern = input_api.re.compile(
    236     r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
    237       base_function_pattern, base_function_pattern))
    238 
    239   def FilterFile(affected_file):
    240     black_list = (_EXCLUDED_PATHS +
    241                   _TEST_CODE_EXCLUDED_PATHS +
    242                   input_api.DEFAULT_BLACK_LIST)
    243     return input_api.FilterSourceFile(
    244       affected_file,
    245       white_list=(file_inclusion_pattern, ),
    246       black_list=black_list)
    247 
    248   problems = []
    249   for f in input_api.AffectedSourceFiles(FilterFile):
    250     local_path = f.LocalPath()
    251     lines = input_api.ReadFile(f).splitlines()
    252     line_number = 0
    253     for line in lines:
    254       if (inclusion_pattern.search(line) and
    255           not comment_pattern.search(line) and
    256           not exclusion_pattern.search(line)):
    257         problems.append(
    258           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
    259       line_number += 1
    260 
    261   if problems:
    262     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
    263   else:
    264     return []
    265 
    266 
    267 def _CheckNoIOStreamInHeaders(input_api, output_api):
    268   """Checks to make sure no .h files include <iostream>."""
    269   files = []
    270   pattern = input_api.re.compile(r'^#include\s*<iostream>',
    271                                  input_api.re.MULTILINE)
    272   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
    273     if not f.LocalPath().endswith('.h'):
    274       continue
    275     contents = input_api.ReadFile(f)
    276     if pattern.search(contents):
    277       files.append(f)
    278 
    279   if len(files):
    280     return [ output_api.PresubmitError(
    281         'Do not #include <iostream> in header files, since it inserts static '
    282         'initialization into every file including the header. Instead, '
    283         '#include <ostream>. See http://crbug.com/94794',
    284         files) ]
    285   return []
    286 
    287 
    288 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
    289   """Checks to make sure no source files use UNIT_TEST"""
    290   problems = []
    291   for f in input_api.AffectedFiles():
    292     if (not f.LocalPath().endswith(('.cc', '.mm'))):
    293       continue
    294 
    295     for line_num, line in f.ChangedContents():
    296       if 'UNIT_TEST' in line:
    297         problems.append('    %s:%d' % (f.LocalPath(), line_num))
    298 
    299   if not problems:
    300     return []
    301   return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
    302       '\n'.join(problems))]
    303 
    304 
    305 def _CheckNoNewWStrings(input_api, output_api):
    306   """Checks to make sure we don't introduce use of wstrings."""
    307   problems = []
    308   for f in input_api.AffectedFiles():
    309     if (not f.LocalPath().endswith(('.cc', '.h')) or
    310         f.LocalPath().endswith('test.cc')):
    311       continue
    312 
    313     allowWString = False
    314     for line_num, line in f.ChangedContents():
    315       if 'presubmit: allow wstring' in line:
    316         allowWString = True
    317       elif not allowWString and 'wstring' in line:
    318         problems.append('    %s:%d' % (f.LocalPath(), line_num))
    319         allowWString = False
    320       else:
    321         allowWString = False
    322 
    323   if not problems:
    324     return []
    325   return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
    326       '  If you are calling a cross-platform API that accepts a wstring, '
    327       'fix the API.\n' +
    328       '\n'.join(problems))]
    329 
    330 
    331 def _CheckNoDEPSGIT(input_api, output_api):
    332   """Make sure .DEPS.git is never modified manually."""
    333   if any(f.LocalPath().endswith('.DEPS.git') for f in
    334       input_api.AffectedFiles()):
    335     return [output_api.PresubmitError(
    336       'Never commit changes to .DEPS.git. This file is maintained by an\n'
    337       'automated system based on what\'s in DEPS and your changes will be\n'
    338       'overwritten.\n'
    339       'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
    340       'for more information')]
    341   return []
    342 
    343 
    344 def _CheckNoBannedFunctions(input_api, output_api):
    345   """Make sure that banned functions are not used."""
    346   warnings = []
    347   errors = []
    348 
    349   file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
    350   for f in input_api.AffectedFiles(file_filter=file_filter):
    351     for line_num, line in f.ChangedContents():
    352       for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
    353         if func_name in line:
    354           problems = warnings;
    355           if error:
    356             problems = errors;
    357           problems.append('    %s:%d:' % (f.LocalPath(), line_num))
    358           for message_line in message:
    359             problems.append('      %s' % message_line)
    360 
    361   file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
    362   for f in input_api.AffectedFiles(file_filter=file_filter):
    363     for line_num, line in f.ChangedContents():
    364       for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
    365         def IsBlacklisted(affected_file, blacklist):
    366           local_path = affected_file.LocalPath()
    367           for item in blacklist:
    368             if input_api.re.match(item, local_path):
    369               return True
    370           return False
    371         if IsBlacklisted(f, excluded_paths):
    372           continue
    373         if func_name in line:
    374           problems = warnings;
    375           if error:
    376             problems = errors;
    377           problems.append('    %s:%d:' % (f.LocalPath(), line_num))
    378           for message_line in message:
    379             problems.append('      %s' % message_line)
    380 
    381   result = []
    382   if (warnings):
    383     result.append(output_api.PresubmitPromptWarning(
    384         'Banned functions were used.\n' + '\n'.join(warnings)))
    385   if (errors):
    386     result.append(output_api.PresubmitError(
    387         'Banned functions were used.\n' + '\n'.join(errors)))
    388   return result
    389 
    390 
    391 def _CheckNoPragmaOnce(input_api, output_api):
    392   """Make sure that banned functions are not used."""
    393   files = []
    394   pattern = input_api.re.compile(r'^#pragma\s+once',
    395                                  input_api.re.MULTILINE)
    396   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
    397     if not f.LocalPath().endswith('.h'):
    398       continue
    399     contents = input_api.ReadFile(f)
    400     if pattern.search(contents):
    401       files.append(f)
    402 
    403   if files:
    404     return [output_api.PresubmitError(
    405         'Do not use #pragma once in header files.\n'
    406         'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
    407         files)]
    408   return []
    409 
    410 
    411 def _CheckNoTrinaryTrueFalse(input_api, output_api):
    412   """Checks to make sure we don't introduce use of foo ? true : false."""
    413   problems = []
    414   pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
    415   for f in input_api.AffectedFiles():
    416     if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
    417       continue
    418 
    419     for line_num, line in f.ChangedContents():
    420       if pattern.match(line):
    421         problems.append('    %s:%d' % (f.LocalPath(), line_num))
    422 
    423   if not problems:
    424     return []
    425   return [output_api.PresubmitPromptWarning(
    426       'Please consider avoiding the "? true : false" pattern if possible.\n' +
    427       '\n'.join(problems))]
    428 
    429 
    430 def _CheckUnwantedDependencies(input_api, output_api):
    431   """Runs checkdeps on #include statements added in this
    432   change. Breaking - rules is an error, breaking ! rules is a
    433   warning.
    434   """
    435   # We need to wait until we have an input_api object and use this
    436   # roundabout construct to import checkdeps because this file is
    437   # eval-ed and thus doesn't have __file__.
    438   original_sys_path = sys.path
    439   try:
    440     sys.path = sys.path + [input_api.os_path.join(
    441         input_api.PresubmitLocalPath(), 'tools', 'checkdeps')]
    442     import checkdeps
    443     from cpp_checker import CppChecker
    444     from rules import Rule
    445   finally:
    446     # Restore sys.path to what it was before.
    447     sys.path = original_sys_path
    448 
    449   added_includes = []
    450   for f in input_api.AffectedFiles():
    451     if not CppChecker.IsCppFile(f.LocalPath()):
    452       continue
    453 
    454     changed_lines = [line for line_num, line in f.ChangedContents()]
    455     added_includes.append([f.LocalPath(), changed_lines])
    456 
    457   deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
    458 
    459   error_descriptions = []
    460   warning_descriptions = []
    461   for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
    462       added_includes):
    463     description_with_path = '%s\n    %s' % (path, rule_description)
    464     if rule_type == Rule.DISALLOW:
    465       error_descriptions.append(description_with_path)
    466     else:
    467       warning_descriptions.append(description_with_path)
    468 
    469   results = []
    470   if error_descriptions:
    471     results.append(output_api.PresubmitError(
    472         'You added one or more #includes that violate checkdeps rules.',
    473         error_descriptions))
    474   if warning_descriptions:
    475     results.append(output_api.PresubmitPromptOrNotify(
    476         'You added one or more #includes of files that are temporarily\n'
    477         'allowed but being removed. Can you avoid introducing the\n'
    478         '#include? See relevant DEPS file(s) for details and contacts.',
    479         warning_descriptions))
    480   return results
    481 
    482 
    483 def _CheckFilePermissions(input_api, output_api):
    484   """Check that all files have their permissions properly set."""
    485   args = [sys.executable, 'tools/checkperms/checkperms.py', '--root',
    486           input_api.change.RepositoryRoot()]
    487   for f in input_api.AffectedFiles():
    488     args += ['--file', f.LocalPath()]
    489   errors = []
    490   (errors, stderrdata) = subprocess.Popen(args).communicate()
    491 
    492   results = []
    493   if errors:
    494     results.append(output_api.PresubmitError('checkperms.py failed.',
    495                                              errors))
    496   return results
    497 
    498 
    499 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
    500   """Makes sure we don't include ui/aura/window_property.h
    501   in header files.
    502   """
    503   pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
    504   errors = []
    505   for f in input_api.AffectedFiles():
    506     if not f.LocalPath().endswith('.h'):
    507       continue
    508     for line_num, line in f.ChangedContents():
    509       if pattern.match(line):
    510         errors.append('    %s:%d' % (f.LocalPath(), line_num))
    511 
    512   results = []
    513   if errors:
    514     results.append(output_api.PresubmitError(
    515       'Header files should not include ui/aura/window_property.h', errors))
    516   return results
    517 
    518 
    519 def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
    520   """Checks that the lines in scope occur in the right order.
    521 
    522   1. C system files in alphabetical order
    523   2. C++ system files in alphabetical order
    524   3. Project's .h files
    525   """
    526 
    527   c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
    528   cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
    529   custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
    530 
    531   C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
    532 
    533   state = C_SYSTEM_INCLUDES
    534 
    535   previous_line = ''
    536   previous_line_num = 0
    537   problem_linenums = []
    538   for line_num, line in scope:
    539     if c_system_include_pattern.match(line):
    540       if state != C_SYSTEM_INCLUDES:
    541         problem_linenums.append((line_num, previous_line_num))
    542       elif previous_line and previous_line > line:
    543         problem_linenums.append((line_num, previous_line_num))
    544     elif cpp_system_include_pattern.match(line):
    545       if state == C_SYSTEM_INCLUDES:
    546         state = CPP_SYSTEM_INCLUDES
    547       elif state == CUSTOM_INCLUDES:
    548         problem_linenums.append((line_num, previous_line_num))
    549       elif previous_line and previous_line > line:
    550         problem_linenums.append((line_num, previous_line_num))
    551     elif custom_include_pattern.match(line):
    552       if state != CUSTOM_INCLUDES:
    553         state = CUSTOM_INCLUDES
    554       elif previous_line and previous_line > line:
    555         problem_linenums.append((line_num, previous_line_num))
    556     else:
    557       problem_linenums.append(line_num)
    558     previous_line = line
    559     previous_line_num = line_num
    560 
    561   warnings = []
    562   for (line_num, previous_line_num) in problem_linenums:
    563     if line_num in changed_linenums or previous_line_num in changed_linenums:
    564       warnings.append('    %s:%d' % (file_path, line_num))
    565   return warnings
    566 
    567 
    568 def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
    569   """Checks the #include order for the given file f."""
    570 
    571   system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
    572   # Exclude #include <.../...> includes from the check; e.g., <sys/...> includes
    573   # often need to appear in a specific order.
    574   excluded_include_pattern = input_api.re.compile(r'\s*#include \<.*/.*')
    575   custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
    576   if_pattern = input_api.re.compile(
    577       r'\s*#\s*(if|elif|else|endif|define|undef).*')
    578   # Some files need specialized order of includes; exclude such files from this
    579   # check.
    580   uncheckable_includes_pattern = input_api.re.compile(
    581       r'\s*#include '
    582       '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
    583 
    584   contents = f.NewContents()
    585   warnings = []
    586   line_num = 0
    587 
    588   # Handle the special first include. If the first include file is
    589   # some/path/file.h, the corresponding including file can be some/path/file.cc,
    590   # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
    591   # etc. It's also possible that no special first include exists.
    592   for line in contents:
    593     line_num += 1
    594     if system_include_pattern.match(line):
    595       # No special first include -> process the line again along with normal
    596       # includes.
    597       line_num -= 1
    598       break
    599     match = custom_include_pattern.match(line)
    600     if match:
    601       match_dict = match.groupdict()
    602       header_basename = input_api.os_path.basename(
    603           match_dict['FILE']).replace('.h', '')
    604       if header_basename not in input_api.os_path.basename(f.LocalPath()):
    605         # No special first include -> process the line again along with normal
    606         # includes.
    607         line_num -= 1
    608       break
    609 
    610   # Split into scopes: Each region between #if and #endif is its own scope.
    611   scopes = []
    612   current_scope = []
    613   for line in contents[line_num:]:
    614     line_num += 1
    615     if uncheckable_includes_pattern.match(line):
    616       return []
    617     if if_pattern.match(line):
    618       scopes.append(current_scope)
    619       current_scope = []
    620     elif ((system_include_pattern.match(line) or
    621            custom_include_pattern.match(line)) and
    622           not excluded_include_pattern.match(line)):
    623       current_scope.append((line_num, line))
    624   scopes.append(current_scope)
    625 
    626   for scope in scopes:
    627     warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
    628                                                changed_linenums))
    629   return warnings
    630 
    631 
    632 def _CheckIncludeOrder(input_api, output_api):
    633   """Checks that the #include order is correct.
    634 
    635   1. The corresponding header for source files.
    636   2. C system files in alphabetical order
    637   3. C++ system files in alphabetical order
    638   4. Project's .h files in alphabetical order
    639 
    640   Each region separated by #if, #elif, #else, #endif, #define and #undef follows
    641   these rules separately.
    642   """
    643 
    644   warnings = []
    645   for f in input_api.AffectedFiles():
    646     if f.LocalPath().endswith(('.cc', '.h')):
    647       changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
    648       warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
    649 
    650   results = []
    651   if warnings:
    652     results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
    653                                                       warnings))
    654   return results
    655 
    656 
    657 def _CheckForVersionControlConflictsInFile(input_api, f):
    658   pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
    659   errors = []
    660   for line_num, line in f.ChangedContents():
    661     if pattern.match(line):
    662       errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
    663   return errors
    664 
    665 
    666 def _CheckForVersionControlConflicts(input_api, output_api):
    667   """Usually this is not intentional and will cause a compile failure."""
    668   errors = []
    669   for f in input_api.AffectedFiles():
    670     errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
    671 
    672   results = []
    673   if errors:
    674     results.append(output_api.PresubmitError(
    675       'Version control conflict markers found, please resolve.', errors))
    676   return results
    677 
    678 
    679 def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
    680   def FilterFile(affected_file):
    681     """Filter function for use with input_api.AffectedSourceFiles,
    682     below.  This filters out everything except non-test files from
    683     top-level directories that generally speaking should not hard-code
    684     service URLs (e.g. src/android_webview/, src/content/ and others).
    685     """
    686     return input_api.FilterSourceFile(
    687       affected_file,
    688       white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
    689       black_list=(_EXCLUDED_PATHS +
    690                   _TEST_CODE_EXCLUDED_PATHS +
    691                   input_api.DEFAULT_BLACK_LIST))
    692 
    693   base_pattern = '"[^"]*google\.com[^"]*"'
    694   comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
    695   pattern = input_api.re.compile(base_pattern)
    696   problems = []  # items are (filename, line_number, line)
    697   for f in input_api.AffectedSourceFiles(FilterFile):
    698     for line_num, line in f.ChangedContents():
    699       if not comment_pattern.search(line) and pattern.search(line):
    700         problems.append((f.LocalPath(), line_num, line))
    701 
    702   if problems:
    703     return [output_api.PresubmitPromptOrNotify(
    704         'Most layers below src/chrome/ should not hardcode service URLs.\n'
    705         'Are you sure this is correct? (Contact: joi (at] chromium.org)',
    706         ['  %s:%d:  %s' % (
    707             problem[0], problem[1], problem[2]) for problem in problems])]
    708   else:
    709     return []
    710 
    711 
    712 def _CheckNoAbbreviationInPngFileName(input_api, output_api):
    713   """Makes sure there are no abbreviations in the name of PNG files.
    714   """
    715   pattern = input_api.re.compile(r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$')
    716   errors = []
    717   for f in input_api.AffectedFiles(include_deletes=False):
    718     if pattern.match(f.LocalPath()):
    719       errors.append('    %s' % f.LocalPath())
    720 
    721   results = []
    722   if errors:
    723     results.append(output_api.PresubmitError(
    724         'The name of PNG files should not have abbreviations. \n'
    725         'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
    726         'Contact oshima (at] chromium.org if you have questions.', errors))
    727   return results
    728 
    729 
    730 def _DepsFilesToCheck(re, changed_lines):
    731   """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
    732   a set of DEPS entries that we should look up."""
    733   results = set()
    734 
    735   # This pattern grabs the path without basename in the first
    736   # parentheses, and the basename (if present) in the second. It
    737   # relies on the simple heuristic that if there is a basename it will
    738   # be a header file ending in ".h".
    739   pattern = re.compile(
    740       r"""['"]\+([^'"]+?)(/[a-zA-Z0-9_]+\.h)?['"].*""")
    741   for changed_line in changed_lines:
    742     m = pattern.match(changed_line)
    743     if m:
    744       path = m.group(1)
    745       if not (path.startswith('grit/') or path == 'grit'):
    746         results.add('%s/DEPS' % m.group(1))
    747   return results
    748 
    749 
    750 def _CheckAddedDepsHaveTargetApprovals(input_api, output_api):
    751   """When a dependency prefixed with + is added to a DEPS file, we
    752   want to make sure that the change is reviewed by an OWNER of the
    753   target file or directory, to avoid layering violations from being
    754   introduced. This check verifies that this happens.
    755   """
    756   changed_lines = set()
    757   for f in input_api.AffectedFiles():
    758     filename = input_api.os_path.basename(f.LocalPath())
    759     if filename == 'DEPS':
    760       changed_lines |= set(line.strip()
    761                            for line_num, line
    762                            in f.ChangedContents())
    763   if not changed_lines:
    764     return []
    765 
    766   virtual_depended_on_files = _DepsFilesToCheck(input_api.re, changed_lines)
    767   if not virtual_depended_on_files:
    768     return []
    769 
    770   if input_api.is_committing:
    771     if input_api.tbr:
    772       return [output_api.PresubmitNotifyResult(
    773           '--tbr was specified, skipping OWNERS check for DEPS additions')]
    774     if not input_api.change.issue:
    775       return [output_api.PresubmitError(
    776           "DEPS approval by OWNERS check failed: this change has "
    777           "no Rietveld issue number, so we can't check it for approvals.")]
    778     output = output_api.PresubmitError
    779   else:
    780     output = output_api.PresubmitNotifyResult
    781 
    782   owners_db = input_api.owners_db
    783   owner_email, reviewers = input_api.canned_checks._RietveldOwnerAndReviewers(
    784       input_api,
    785       owners_db.email_regexp,
    786       approval_needed=input_api.is_committing)
    787 
    788   owner_email = owner_email or input_api.change.author_email
    789 
    790   reviewers_plus_owner = set(reviewers)
    791   if owner_email:
    792     reviewers_plus_owner.add(owner_email)
    793   missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
    794                                                  reviewers_plus_owner)
    795   unapproved_dependencies = ["'+%s'," % path[:-len('/DEPS')]
    796                              for path in missing_files]
    797 
    798   if unapproved_dependencies:
    799     output_list = [
    800       output('Missing LGTM from OWNERS of directories added to DEPS:\n    %s' %
    801              '\n    '.join(sorted(unapproved_dependencies)))]
    802     if not input_api.is_committing:
    803       suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
    804       output_list.append(output(
    805           'Suggested missing target path OWNERS:\n    %s' %
    806           '\n    '.join(suggested_owners or [])))
    807     return output_list
    808 
    809   return []
    810 
    811 
    812 def _CommonChecks(input_api, output_api):
    813   """Checks common to both upload and commit."""
    814   results = []
    815   results.extend(input_api.canned_checks.PanProjectChecks(
    816       input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
    817   results.extend(_CheckAuthorizedAuthor(input_api, output_api))
    818   results.extend(
    819     _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
    820   results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
    821   results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
    822   results.extend(_CheckNoNewWStrings(input_api, output_api))
    823   results.extend(_CheckNoDEPSGIT(input_api, output_api))
    824   results.extend(_CheckNoBannedFunctions(input_api, output_api))
    825   results.extend(_CheckNoPragmaOnce(input_api, output_api))
    826   results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
    827   results.extend(_CheckUnwantedDependencies(input_api, output_api))
    828   results.extend(_CheckFilePermissions(input_api, output_api))
    829   results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
    830   results.extend(_CheckIncludeOrder(input_api, output_api))
    831   results.extend(_CheckForVersionControlConflicts(input_api, output_api))
    832   results.extend(_CheckPatchFiles(input_api, output_api))
    833   results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
    834   results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
    835   results.extend(_CheckForInvalidOSMacros(input_api, output_api))
    836   results.extend(_CheckAddedDepsHaveTargetApprovals(input_api, output_api))
    837   results.extend(
    838       input_api.canned_checks.CheckChangeHasNoTabs(
    839           input_api,
    840           output_api,
    841           source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
    842 
    843   if any('PRESUBMIT.py' == f.LocalPath() for f in input_api.AffectedFiles()):
    844     results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
    845         input_api, output_api,
    846         input_api.PresubmitLocalPath(),
    847         whitelist=[r'^PRESUBMIT_test\.py$']))
    848   return results
    849 
    850 
    851 def _CheckSubversionConfig(input_api, output_api):
    852   """Verifies the subversion config file is correctly setup.
    853 
    854   Checks that autoprops are enabled, returns an error otherwise.
    855   """
    856   join = input_api.os_path.join
    857   if input_api.platform == 'win32':
    858     appdata = input_api.environ.get('APPDATA', '')
    859     if not appdata:
    860       return [output_api.PresubmitError('%APPDATA% is not configured.')]
    861     path = join(appdata, 'Subversion', 'config')
    862   else:
    863     home = input_api.environ.get('HOME', '')
    864     if not home:
    865       return [output_api.PresubmitError('$HOME is not configured.')]
    866     path = join(home, '.subversion', 'config')
    867 
    868   error_msg = (
    869       'Please look at http://dev.chromium.org/developers/coding-style to\n'
    870       'configure your subversion configuration file. This enables automatic\n'
    871       'properties to simplify the project maintenance.\n'
    872       'Pro-tip: just download and install\n'
    873       'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
    874 
    875   try:
    876     lines = open(path, 'r').read().splitlines()
    877     # Make sure auto-props is enabled and check for 2 Chromium standard
    878     # auto-prop.
    879     if (not '*.cc = svn:eol-style=LF' in lines or
    880         not '*.pdf = svn:mime-type=application/pdf' in lines or
    881         not 'enable-auto-props = yes' in lines):
    882       return [
    883           output_api.PresubmitNotifyResult(
    884               'It looks like you have not configured your subversion config '
    885               'file or it is not up-to-date.\n' + error_msg)
    886       ]
    887   except (OSError, IOError):
    888     return [
    889         output_api.PresubmitNotifyResult(
    890             'Can\'t find your subversion config file.\n' + error_msg)
    891     ]
    892   return []
    893 
    894 
    895 def _CheckAuthorizedAuthor(input_api, output_api):
    896   """For non-googler/chromites committers, verify the author's email address is
    897   in AUTHORS.
    898   """
    899   # TODO(maruel): Add it to input_api?
    900   import fnmatch
    901 
    902   author = input_api.change.author_email
    903   if not author:
    904     input_api.logging.info('No author, skipping AUTHOR check')
    905     return []
    906   authors_path = input_api.os_path.join(
    907       input_api.PresubmitLocalPath(), 'AUTHORS')
    908   valid_authors = (
    909       input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
    910       for line in open(authors_path))
    911   valid_authors = [item.group(1).lower() for item in valid_authors if item]
    912   if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
    913     input_api.logging.info('Valid authors are %s', ', '.join(valid_authors))
    914     return [output_api.PresubmitPromptWarning(
    915         ('%s is not in AUTHORS file. If you are a new contributor, please visit'
    916         '\n'
    917         'http://www.chromium.org/developers/contributing-code and read the '
    918         '"Legal" section\n'
    919         'If you are a chromite, verify the contributor signed the CLA.') %
    920         author)]
    921   return []
    922 
    923 
    924 def _CheckPatchFiles(input_api, output_api):
    925   problems = [f.LocalPath() for f in input_api.AffectedFiles()
    926       if f.LocalPath().endswith(('.orig', '.rej'))]
    927   if problems:
    928     return [output_api.PresubmitError(
    929         "Don't commit .rej and .orig files.", problems)]
    930   else:
    931     return []
    932 
    933 
    934 def _DidYouMeanOSMacro(bad_macro):
    935   try:
    936     return {'A': 'OS_ANDROID',
    937             'B': 'OS_BSD',
    938             'C': 'OS_CHROMEOS',
    939             'F': 'OS_FREEBSD',
    940             'L': 'OS_LINUX',
    941             'M': 'OS_MACOSX',
    942             'N': 'OS_NACL',
    943             'O': 'OS_OPENBSD',
    944             'P': 'OS_POSIX',
    945             'S': 'OS_SOLARIS',
    946             'W': 'OS_WIN'}[bad_macro[3].upper()]
    947   except KeyError:
    948     return ''
    949 
    950 
    951 def _CheckForInvalidOSMacrosInFile(input_api, f):
    952   """Check for sensible looking, totally invalid OS macros."""
    953   preprocessor_statement = input_api.re.compile(r'^\s*#')
    954   os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
    955   results = []
    956   for lnum, line in f.ChangedContents():
    957     if preprocessor_statement.search(line):
    958       for match in os_macro.finditer(line):
    959         if not match.group(1) in _VALID_OS_MACROS:
    960           good = _DidYouMeanOSMacro(match.group(1))
    961           did_you_mean = ' (did you mean %s?)' % good if good else ''
    962           results.append('    %s:%d %s%s' % (f.LocalPath(),
    963                                              lnum,
    964                                              match.group(1),
    965                                              did_you_mean))
    966   return results
    967 
    968 
    969 def _CheckForInvalidOSMacros(input_api, output_api):
    970   """Check all affected files for invalid OS macros."""
    971   bad_macros = []
    972   for f in input_api.AffectedFiles():
    973     if not f.LocalPath().endswith(('.py', '.js', '.html', '.css')):
    974       bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
    975 
    976   if not bad_macros:
    977     return []
    978 
    979   return [output_api.PresubmitError(
    980       'Possibly invalid OS macro[s] found. Please fix your code\n'
    981       'or add your macro to src/PRESUBMIT.py.', bad_macros)]
    982 
    983 
    984 def CheckChangeOnUpload(input_api, output_api):
    985   results = []
    986   results.extend(_CommonChecks(input_api, output_api))
    987   return results
    988 
    989 
    990 def CheckChangeOnCommit(input_api, output_api):
    991   results = []
    992   results.extend(_CommonChecks(input_api, output_api))
    993   # TODO(thestig) temporarily disabled, doesn't work in third_party/
    994   #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
    995   #    input_api, output_api, sources))
    996   # Make sure the tree is 'open'.
    997   results.extend(input_api.canned_checks.CheckTreeIsOpen(
    998       input_api,
    999       output_api,
   1000       json_url='http://chromium-status.appspot.com/current?format=json'))
   1001   results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
   1002       output_api, 'http://codereview.chromium.org',
   1003       ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
   1004       'tryserver (at] chromium.org'))
   1005 
   1006   results.extend(input_api.canned_checks.CheckChangeHasBugField(
   1007       input_api, output_api))
   1008   results.extend(input_api.canned_checks.CheckChangeHasDescription(
   1009       input_api, output_api))
   1010   results.extend(_CheckSubversionConfig(input_api, output_api))
   1011   return results
   1012 
   1013 
   1014 def GetPreferredTrySlaves(project, change):
   1015   files = change.LocalPaths()
   1016 
   1017   if not files or all(re.search(r'[\\/]OWNERS$', f) for f in files):
   1018     return []
   1019 
   1020   if all(re.search('\.(m|mm)$|(^|[/_])mac[/_.]', f) for f in files):
   1021     return ['mac_rel', 'mac:compile']
   1022   if all(re.search('(^|[/_])win[/_.]', f) for f in files):
   1023     return ['win_rel', 'win7_aura', 'win:compile']
   1024   if all(re.search('(^|[/_])android[/_.]', f) for f in files):
   1025     return ['android_aosp', 'android_dbg', 'android_clang_dbg']
   1026   if all(re.search('^native_client_sdk', f) for f in files):
   1027     return ['linux_nacl_sdk', 'win_nacl_sdk', 'mac_nacl_sdk']
   1028   if all(re.search('[/_]ios[/_.]', f) for f in files):
   1029     return ['ios_rel_device', 'ios_dbg_simulator']
   1030 
   1031   trybots = [
   1032       'android_clang_dbg',
   1033       'android_dbg',
   1034       'ios_dbg_simulator',
   1035       'ios_rel_device',
   1036       'linux_asan',
   1037       'linux_aura',
   1038       'linux_chromeos',
   1039       'linux_clang:compile',
   1040       'linux_rel',
   1041       'mac_rel',
   1042       'mac:compile',
   1043       'win7_aura',
   1044       'win_rel',
   1045       'win:compile',
   1046       'win_x64_rel:compile',
   1047   ]
   1048 
   1049   # Match things like path/aura/file.cc and path/file_aura.cc.
   1050   # Same for chromeos.
   1051   if any(re.search('[/_](aura|chromeos)', f) for f in files):
   1052     trybots += ['linux_chromeos_clang:compile', 'linux_chromeos_asan']
   1053 
   1054   # The AOSP bot doesn't build the chrome/ layer, so ignore any changes to it
   1055   # unless they're .gyp(i) files as changes to those files can break the gyp
   1056   # step on that bot.
   1057   if (not all(re.search('^chrome', f) for f in files) or
   1058       any(re.search('\.gypi?$', f) for f in files)):
   1059     trybots += ['android_aosp']
   1060 
   1061   return trybots
   1062