Home | History | Annotate | Download | only in build
      1 # Copyright (c) 2015 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 import os
      6 import re
      7 import sys
      8 import time
      9 
     10 import checklicenses
     11 
     12 from tracing import tracing_project
     13 
     14 
     15 def _FormatError(msg, files):
     16   return ('%s in these files:\n' % msg +
     17           '\n'.join('  ' + x for x in files))
     18 
     19 
     20 def _ReportErrorFileAndLine(filename, line_num, dummy_line):
     21   """Default error formatter for _FindNewViolationsOfRule."""
     22   return '%s:%s' % (filename, line_num)
     23 
     24 
     25 def _FindNewViolationsOfRule(callable_rule, input_api,
     26                              error_formatter=_ReportErrorFileAndLine):
     27   """Find all newly introduced violations of a per-line rule (a callable).
     28 
     29   Arguments:
     30     callable_rule: a callable taking a file extension and line of input and
     31       returning True if the rule is satisfied and False if there was a problem.
     32     input_api: object to enumerate the affected files.
     33     source_file_filter: a filter to be passed to the input api.
     34     error_formatter: a callable taking (filename, line_number, line) and
     35       returning a formatted error string.
     36 
     37   Returns:
     38     A list of the newly-introduced violations reported by the rule.
     39   """
     40   errors = []
     41   for f in input_api.AffectedFiles(include_deletes=False):
     42     # For speed, we do two passes, checking first the full file.  Shelling out
     43     # to the SCM to determine the changed region can be quite expensive on
     44     # Win32.  Assuming that most files will be kept problem-free, we can
     45     # skip the SCM operations most of the time.
     46     extension = str(f.LocalPath()).rsplit('.', 1)[-1]
     47     if all(callable_rule(extension, line) for line in f.NewContents()):
     48       continue  # No violation found in full text: can skip considering diff.
     49 
     50     if tracing_project.TracingProject.IsIgnoredFile(f):
     51       continue
     52 
     53     for line_num, line in f.ChangedContents():
     54       if not callable_rule(extension, line):
     55         errors.append(error_formatter(f.LocalPath(), line_num, line))
     56 
     57   return errors
     58 
     59 
     60 def CheckCopyright(input_api):
     61   results = []
     62   results += _CheckCopyrightThirdParty(input_api)
     63   results += _CheckCopyrightNonThirdParty(input_api)
     64   return results
     65 
     66 
     67 def _CheckCopyrightThirdParty(input_api):
     68   results = []
     69   has_third_party_change = any(
     70       tracing_project.TracingProject.IsThirdParty(f)
     71       for f in input_api.AffectedFiles(include_deletes=False))
     72   if has_third_party_change:
     73     tracing_root = os.path.abspath(
     74         os.path.join(os.path.dirname(__file__), '..'))
     75     tracing_third_party = os.path.join(tracing_root, 'tracing', 'third_party')
     76     has_invalid_license = checklicenses.check_licenses(
     77         tracing_root, tracing_third_party)
     78     if has_invalid_license:
     79       results.append(
     80           'License check encountered invalid licenses in tracing/third_party/.')
     81   return results
     82 
     83 
     84 def _CheckCopyrightNonThirdParty(input_api):
     85   project_name = 'Chromium'
     86 
     87   current_year = int(time.strftime('%Y'))
     88   allow_old_years=True
     89   if allow_old_years:
     90     allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
     91   else:
     92     allowed_years = [str(current_year)]
     93   years_re = '(' + '|'.join(allowed_years) + ')'
     94 
     95   # The (c) is deprecated, but tolerate it until it's removed from all files.
     96   non_html_license_header = (
     97       r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
     98         r'All rights reserved\.\n'
     99       r'.*? Use of this source code is governed by a BSD-style license that '
    100         r'can be\n'
    101       r'.*? found in the LICENSE file\.(?: \*/)?\n'
    102   ) % {
    103       'year': years_re,
    104       'project': project_name,
    105   }
    106   non_html_license_re = re.compile(non_html_license_header, re.MULTILINE)
    107 
    108   html_license_header = (
    109       r'^Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
    110         r'All rights reserved\.\n'
    111       r'Use of this source code is governed by a BSD-style license that '
    112         r'can be\n'
    113       r'found in the LICENSE file\.(?: \*/)?\n'
    114   ) % {
    115       'year': years_re,
    116       'project': project_name,
    117   }
    118   html_license_re = re.compile(html_license_header, re.MULTILINE)
    119 
    120   sources = list(s for s in input_api.AffectedFiles(include_deletes=False)
    121                  if not tracing_project.TracingProject.IsThirdParty(s))
    122 
    123   html_sources = [f for f in sources
    124                   if os.path.splitext(f.LocalPath())[1] == '.html']
    125   non_html_sources = [f for f in sources
    126                       if os.path.splitext(f.LocalPath())[1] != '.html']
    127 
    128   results = []
    129   results += _Check(input_api, html_license_re, html_sources)
    130   results += _Check(input_api, non_html_license_re, non_html_sources)
    131   return results
    132 
    133 
    134 def _Check(input_api, license_re, sources):
    135   bad_files = []
    136   for f in sources:
    137     if tracing_project.TracingProject.IsIgnoredFile(f):
    138       continue
    139     contents = '\n'.join(f.NewContents())
    140     if not license_re.search(contents):
    141       bad_files.append(f.LocalPath())
    142   if bad_files:
    143     return [_FormatError(
    144         'License must match:\n%s\n' % license_re.pattern +
    145         'Found a bad license header',
    146         bad_files)]
    147   return []
    148 
    149 
    150 def CheckLongLines(input_api, maxlen=80):
    151   """Checks the line length in all text files to be submitted."""
    152   maxlens = {
    153       '': maxlen,
    154   }
    155 
    156   # Language specific exceptions to max line length.
    157   # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
    158   # superset of CPP_EXCEPTIONS.
    159   CPP_FILE_EXTS = ('c', 'cc')
    160   CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
    161   JAVA_FILE_EXTS = ('java',)
    162   JAVA_EXCEPTIONS = ('import ', 'package ')
    163   OBJC_FILE_EXTS = ('h', 'm', 'mm')
    164   OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
    165                      '#pragma')
    166 
    167   LANGUAGE_EXCEPTIONS = [
    168       (CPP_FILE_EXTS, CPP_EXCEPTIONS),
    169       (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
    170       (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
    171   ]
    172 
    173   def no_long_lines(file_extension, line):
    174     # Check for language specific exceptions.
    175     if any(file_extension in exts and line.startswith(exceptions)
    176            for exts, exceptions in LANGUAGE_EXCEPTIONS):
    177       return True
    178 
    179     file_maxlen = maxlens.get(file_extension, maxlens[''])
    180     # Stupidly long symbols that needs to be worked around if takes 66% of line.
    181     long_symbol = file_maxlen * 2 / 3
    182     # Hard line length limit at 50% more.
    183     extra_maxlen = file_maxlen * 3 / 2
    184 
    185     line_len = len(line)
    186     if line_len <= file_maxlen:
    187       return True
    188 
    189     if '@suppress longLineCheck' in line:
    190       return True
    191 
    192     if line_len > extra_maxlen:
    193       return False
    194 
    195     if any((url in line) for url in ('file://', 'http://', 'https://')):
    196       return True
    197 
    198     if 'url(' in line and file_extension == 'css':
    199       return True
    200 
    201     if '<include' in line and file_extension in ('css', 'html', 'js'):
    202       return True
    203 
    204     return re.match(
    205         r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
    206 
    207   def format_error(filename, line_num, line):
    208     return '%s, line %s, %s chars' % (filename, line_num, len(line))
    209 
    210   errors = _FindNewViolationsOfRule(no_long_lines, input_api,
    211                                     error_formatter=format_error)
    212   if errors:
    213     return [_FormatError(
    214         'Found lines longer than %s characters' % maxlen,
    215         errors)]
    216   else:
    217     return []
    218 
    219 def CheckChangeLogBug(input_api):
    220   results = []
    221   if input_api.change.BUG is None or re.match('\#\d+$', input_api.change.BUG):
    222     return []
    223   return [('Invalid bug "%s". BUG= should either not be present or start'
    224            ' with # for a github issue.' % input_api.change.BUG)]
    225 
    226 
    227 def RunChecks(input_api):
    228   results = []
    229   results += CheckCopyright(input_api)
    230   results += CheckLongLines(input_api)
    231   results += CheckChangeLogBug(input_api)
    232   return results
    233