Home | History | Annotate | Download | only in WebKit
      1 # Copyright (c) 2013 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 Blink.
      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 import sys
     12 
     13 
     14 _EXCLUDED_PATHS = ()
     15 
     16 
     17 def _CheckForVersionControlConflictsInFile(input_api, f):
     18     pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
     19     errors = []
     20     for line_num, line in f.ChangedContents():
     21         if pattern.match(line):
     22             errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
     23     return errors
     24 
     25 
     26 def _CheckForVersionControlConflicts(input_api, output_api):
     27     """Usually this is not intentional and will cause a compile failure."""
     28     errors = []
     29     for f in input_api.AffectedFiles():
     30         errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
     31 
     32     results = []
     33     if errors:
     34         results.append(output_api.PresubmitError(
     35             'Version control conflict markers found, please resolve.', errors))
     36     return results
     37 
     38 
     39 def _CheckWatchlist(input_api, output_api):
     40     """Check that the WATCHLIST file parses correctly."""
     41     errors = []
     42     for f in input_api.AffectedFiles():
     43         if f.LocalPath() != 'WATCHLISTS':
     44             continue
     45         import StringIO
     46         import logging
     47         import watchlists
     48 
     49         log_buffer = StringIO.StringIO()
     50         log_handler = logging.StreamHandler(log_buffer)
     51         log_handler.setFormatter(
     52             logging.Formatter('%(levelname)s: %(message)s'))
     53         logger = logging.getLogger()
     54         logger.addHandler(log_handler)
     55 
     56         wl = watchlists.Watchlists(input_api.change.RepositoryRoot())
     57 
     58         logger.removeHandler(log_handler)
     59         log_handler.flush()
     60         log_buffer.flush()
     61 
     62         if log_buffer.getvalue():
     63             errors.append(output_api.PresubmitError(
     64                 'Cannot parse WATCHLISTS file, please resolve.',
     65                 log_buffer.getvalue().splitlines()))
     66     return errors
     67 
     68 
     69 def _CommonChecks(input_api, output_api):
     70     """Checks common to both upload and commit."""
     71     # We should figure out what license checks we actually want to use.
     72     license_header = r'.*'
     73 
     74     results = []
     75     results.extend(input_api.canned_checks.PanProjectChecks(
     76         input_api, output_api, excluded_paths=_EXCLUDED_PATHS,
     77         maxlen=800, license_header=license_header))
     78     results.extend(_CheckForVersionControlConflicts(input_api, output_api))
     79     results.extend(_CheckPatchFiles(input_api, output_api))
     80     results.extend(_CheckTestExpectations(input_api, output_api))
     81     results.extend(_CheckUnwantedDependencies(input_api, output_api))
     82     results.extend(_CheckChromiumPlatformMacros(input_api, output_api))
     83     results.extend(_CheckWatchlist(input_api, output_api))
     84     results.extend(_CheckFilePermissions(input_api, output_api))
     85     return results
     86 
     87 
     88 def _CheckSubversionConfig(input_api, output_api):
     89   """Verifies the subversion config file is correctly setup.
     90 
     91   Checks that autoprops are enabled, returns an error otherwise.
     92   """
     93   join = input_api.os_path.join
     94   if input_api.platform == 'win32':
     95     appdata = input_api.environ.get('APPDATA', '')
     96     if not appdata:
     97       return [output_api.PresubmitError('%APPDATA% is not configured.')]
     98     path = join(appdata, 'Subversion', 'config')
     99   else:
    100     home = input_api.environ.get('HOME', '')
    101     if not home:
    102       return [output_api.PresubmitError('$HOME is not configured.')]
    103     path = join(home, '.subversion', 'config')
    104 
    105   error_msg = (
    106       'Please look at http://dev.chromium.org/developers/coding-style to\n'
    107       'configure your subversion configuration file. This enables automatic\n'
    108       'properties to simplify the project maintenance.\n'
    109       'Pro-tip: just download and install\n'
    110       'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
    111 
    112   try:
    113     lines = open(path, 'r').read().splitlines()
    114     # Make sure auto-props is enabled and check for 2 Chromium standard
    115     # auto-prop.
    116     if (not '*.cc = svn:eol-style=LF' in lines or
    117         not '*.pdf = svn:mime-type=application/pdf' in lines or
    118         not 'enable-auto-props = yes' in lines):
    119       return [
    120           output_api.PresubmitNotifyResult(
    121               'It looks like you have not configured your subversion config '
    122               'file or it is not up-to-date.\n' + error_msg)
    123       ]
    124   except (OSError, IOError):
    125     return [
    126         output_api.PresubmitNotifyResult(
    127             'Can\'t find your subversion config file.\n' + error_msg)
    128     ]
    129   return []
    130 
    131 
    132 def _CheckPatchFiles(input_api, output_api):
    133   problems = [f.LocalPath() for f in input_api.AffectedFiles()
    134       if f.LocalPath().endswith(('.orig', '.rej'))]
    135   if problems:
    136     return [output_api.PresubmitError(
    137         "Don't commit .rej and .orig files.", problems)]
    138   else:
    139     return []
    140 
    141 
    142 def _CheckTestExpectations(input_api, output_api):
    143     local_paths = [f.LocalPath() for f in input_api.AffectedFiles()]
    144     if any(path.startswith('LayoutTests') for path in local_paths):
    145         lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
    146             'Tools', 'Scripts', 'lint-test-expectations')
    147         _, errs = input_api.subprocess.Popen(
    148             [input_api.python_executable, lint_path],
    149             stdout=input_api.subprocess.PIPE,
    150             stderr=input_api.subprocess.PIPE).communicate()
    151         if not errs:
    152             return [output_api.PresubmitError(
    153                 "lint-test-expectations failed "
    154                 "to produce output; check by hand. ")]
    155         if errs.strip() != 'Lint succeeded.':
    156             return [output_api.PresubmitError(errs)]
    157     return []
    158 
    159 
    160 def _CheckStyle(input_api, output_api):
    161     style_checker_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
    162         'Tools', 'Scripts', 'check-webkit-style')
    163     args = ([input_api.python_executable, style_checker_path, '--diff-files']
    164         + [f.LocalPath() for f in input_api.AffectedFiles()])
    165     results = []
    166 
    167     try:
    168         child = input_api.subprocess.Popen(args,
    169                                            stderr=input_api.subprocess.PIPE)
    170         _, stderrdata = child.communicate()
    171         if child.returncode != 0:
    172             results.append(output_api.PresubmitError(
    173                 'check-webkit-style failed', [stderrdata]))
    174     except Exception as e:
    175         results.append(output_api.PresubmitNotifyResult(
    176             'Could not run check-webkit-style', [str(e)]))
    177 
    178     return results
    179 
    180 
    181 def _CheckUnwantedDependencies(input_api, output_api):
    182     """Runs checkdeps on #include statements added in this
    183     change. Breaking - rules is an error, breaking ! rules is a
    184     warning.
    185     """
    186     # We need to wait until we have an input_api object and use this
    187     # roundabout construct to import checkdeps because this file is
    188     # eval-ed and thus doesn't have __file__.
    189     original_sys_path = sys.path
    190     try:
    191         sys.path = sys.path + [input_api.os_path.realpath(input_api.os_path.join(
    192                 input_api.PresubmitLocalPath(), '..', '..', 'buildtools', 'checkdeps'))]
    193         import checkdeps
    194         from cpp_checker import CppChecker
    195         from rules import Rule
    196     finally:
    197         # Restore sys.path to what it was before.
    198         sys.path = original_sys_path
    199 
    200     added_includes = []
    201     for f in input_api.AffectedFiles():
    202         if not CppChecker.IsCppFile(f.LocalPath()):
    203             continue
    204 
    205         changed_lines = [line for line_num, line in f.ChangedContents()]
    206         added_includes.append([f.LocalPath(), changed_lines])
    207 
    208     deps_checker = checkdeps.DepsChecker(
    209         input_api.os_path.join(input_api.PresubmitLocalPath()))
    210 
    211     error_descriptions = []
    212     warning_descriptions = []
    213     for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
    214             added_includes):
    215         description_with_path = '%s\n    %s' % (path, rule_description)
    216         if rule_type == Rule.DISALLOW:
    217             error_descriptions.append(description_with_path)
    218         else:
    219             warning_descriptions.append(description_with_path)
    220 
    221     results = []
    222     if error_descriptions:
    223         results.append(output_api.PresubmitError(
    224                 'You added one or more #includes that violate checkdeps rules.',
    225                 error_descriptions))
    226     if warning_descriptions:
    227         results.append(output_api.PresubmitPromptOrNotify(
    228                 'You added one or more #includes of files that are temporarily\n'
    229                 'allowed but being removed. Can you avoid introducing the\n'
    230                 '#include? See relevant DEPS file(s) for details and contacts.',
    231                 warning_descriptions))
    232     return results
    233 
    234 
    235 def _CheckChromiumPlatformMacros(input_api, output_api, source_file_filter=None):
    236     """Ensures that Blink code uses WTF's platform macros instead of
    237     Chromium's. Using the latter has resulted in at least one subtle
    238     build breakage."""
    239     os_macro_re = input_api.re.compile(r'^\s*#(el)?if.*\bOS_')
    240     errors = input_api.canned_checks._FindNewViolationsOfRule(
    241         lambda _, x: not os_macro_re.search(x),
    242         input_api, source_file_filter)
    243     errors = ['Found use of Chromium OS_* macro in %s. '
    244         'Use WTF platform macros instead.' % violation for violation in errors]
    245     if errors:
    246         return [output_api.PresubmitPromptWarning('\n'.join(errors))]
    247     return []
    248 
    249 
    250 def _CheckForPrintfDebugging(input_api, output_api):
    251     """Generally speaking, we'd prefer not to land patches that printf
    252     debug output."""
    253     printf_re = input_api.re.compile(r'^\s*printf\(')
    254     errors = input_api.canned_checks._FindNewViolationsOfRule(
    255         lambda _, x: not printf_re.search(x),
    256         input_api, None)
    257     errors = ['  * %s' % violation for violation in errors]
    258     if errors:
    259         return [output_api.PresubmitPromptOrNotify(
    260                     'printf debugging is best debugging! That said, it might '
    261                     'be a good idea to drop the following occurances from '
    262                     'your patch before uploading:\n%s' % '\n'.join(errors))]
    263     return []
    264 
    265 
    266 def _CheckForDangerousTestFunctions(input_api, output_api):
    267     """Tests should not be using serveAsynchronousMockedRequests, since it does
    268     not guarantee that the threaded HTML parser will have completed."""
    269     serve_async_requests_re = input_api.re.compile(
    270         r'serveAsynchronousMockedRequests')
    271     errors = input_api.canned_checks._FindNewViolationsOfRule(
    272         lambda _, x: not serve_async_requests_re.search(x),
    273         input_api, None)
    274     errors = ['  * %s' % violation for violation in errors]
    275     if errors:
    276         return [output_api.PresubmitError(
    277                     'You should be using FrameTestHelpers::'
    278                     'pumpPendingRequests() instead of '
    279                     'serveAsynchronousMockedRequests() in the following '
    280                     'locations:\n%s' % '\n'.join(errors))]
    281     return []
    282 
    283 
    284 def _CheckForFailInFile(input_api, f):
    285     pattern = input_api.re.compile('^FAIL')
    286     errors = []
    287     for line_num, line in f.ChangedContents():
    288         if pattern.match(line):
    289             errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
    290     return errors
    291 
    292 
    293 def _CheckFilePermissions(input_api, output_api):
    294     """Check that all files have their permissions properly set."""
    295     if input_api.platform == 'win32':
    296         return []
    297     path = input_api.os_path.join(
    298         '..', '..', 'tools', 'checkperms', 'checkperms.py')
    299     args = [sys.executable, path, '--root', input_api.change.RepositoryRoot()]
    300     for f in input_api.AffectedFiles():
    301         args += ['--file', f.LocalPath()]
    302     checkperms = input_api.subprocess.Popen(
    303         args, stdout=input_api.subprocess.PIPE)
    304     errors = checkperms.communicate()[0].strip()
    305     if errors:
    306         return [output_api.PresubmitError(
    307             'checkperms.py failed.', errors.splitlines())]
    308     return []
    309 
    310 
    311 def _CheckForInvalidPreferenceError(input_api, output_api):
    312     pattern = input_api.re.compile('Invalid name for preference: (.+)')
    313     results = []
    314 
    315     for f in input_api.AffectedFiles():
    316         if not f.LocalPath().endswith('-expected.txt'):
    317             continue
    318         for line_num, line in f.ChangedContents():
    319             error = pattern.search(line)
    320             if error:
    321                 results.append(output_api.PresubmitError('Found an invalid preference %s in expected result %s:%s' % (error.group(1), f, line_num)))
    322     return results
    323 
    324 def CheckChangeOnUpload(input_api, output_api):
    325     results = []
    326     results.extend(_CommonChecks(input_api, output_api))
    327     results.extend(_CheckStyle(input_api, output_api))
    328     results.extend(_CheckForPrintfDebugging(input_api, output_api))
    329     results.extend(_CheckForDangerousTestFunctions(input_api, output_api))
    330     results.extend(_CheckForInvalidPreferenceError(input_api, output_api))
    331     return results
    332 
    333 
    334 def CheckChangeOnCommit(input_api, output_api):
    335     results = []
    336     results.extend(_CommonChecks(input_api, output_api))
    337     results.extend(input_api.canned_checks.CheckTreeIsOpen(
    338         input_api, output_api,
    339         json_url='http://blink-status.appspot.com/current?format=json'))
    340     results.extend(input_api.canned_checks.CheckChangeHasDescription(
    341         input_api, output_api))
    342     results.extend(_CheckSubversionConfig(input_api, output_api))
    343     return results
    344 
    345 
    346 def GetPreferredTryMasters(project, change):
    347     return {
    348         'tryserver.blink': {
    349             'android_blink_compile_dbg': set(['defaulttests']),
    350             'android_blink_compile_rel': set(['defaulttests']),
    351             'android_chromium_gn_compile_rel': set(['defaulttests']),
    352             'linux_blink_dbg': set(['defaulttests']),
    353             'linux_blink_rel': set(['defaulttests']),
    354             'linux_chromium_gn_rel': set(['defaulttests']),
    355             'mac_blink_compile_dbg': set(['defaulttests']),
    356             'mac_blink_rel': set(['defaulttests']),
    357             'win_blink_compile_dbg': set(['defaulttests']),
    358             'win_blink_rel': set(['defaulttests']),
    359         },
    360         'tryserver.chromium.gpu': {
    361             'linux_gpu': set(['defaulttests']),
    362             'mac_gpu': set(['defaulttests']),
    363             'win_gpu': set(['defaulttests']),
    364         }
    365     }
    366