| # Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import re |
| import sys |
| import time |
| |
| import checklicenses |
| |
| from tracing import tracing_project |
| |
| |
| def _FormatError(msg, files): |
| return ('%s in these files:\n' % msg + |
| '\n'.join(' ' + x for x in files)) |
| |
| |
| def _ReportErrorFileAndLine(filename, line_num, dummy_line): |
| """Default error formatter for _FindNewViolationsOfRule.""" |
| return '%s:%s' % (filename, line_num) |
| |
| |
| def _FindNewViolationsOfRule(callable_rule, input_api, |
| error_formatter=_ReportErrorFileAndLine): |
| """Find all newly introduced violations of a per-line rule (a callable). |
| |
| Arguments: |
| callable_rule: a callable taking a file extension and line of input and |
| returning True if the rule is satisfied and False if there was a problem. |
| input_api: object to enumerate the affected files. |
| source_file_filter: a filter to be passed to the input api. |
| error_formatter: a callable taking (filename, line_number, line) and |
| returning a formatted error string. |
| |
| Returns: |
| A list of the newly-introduced violations reported by the rule. |
| """ |
| errors = [] |
| for f in input_api.AffectedFiles(include_deletes=False): |
| # For speed, we do two passes, checking first the full file. Shelling out |
| # to the SCM to determine the changed region can be quite expensive on |
| # Win32. Assuming that most files will be kept problem-free, we can |
| # skip the SCM operations most of the time. |
| extension = str(f.LocalPath()).rsplit('.', 1)[-1] |
| if all(callable_rule(extension, line) for line in f.NewContents()): |
| continue # No violation found in full text: can skip considering diff. |
| |
| if tracing_project.TracingProject.IsIgnoredFile(f): |
| continue |
| |
| for line_num, line in f.ChangedContents(): |
| if not callable_rule(extension, line): |
| errors.append(error_formatter(f.LocalPath(), line_num, line)) |
| |
| return errors |
| |
| |
| def CheckCopyright(input_api): |
| results = [] |
| results += _CheckCopyrightThirdParty(input_api) |
| results += _CheckCopyrightNonThirdParty(input_api) |
| return results |
| |
| |
| def _CheckCopyrightThirdParty(input_api): |
| results = [] |
| has_third_party_change = any( |
| tracing_project.TracingProject.IsThirdParty(f) |
| for f in input_api.AffectedFiles(include_deletes=False)) |
| if has_third_party_change: |
| tracing_root = os.path.abspath( |
| os.path.join(os.path.dirname(__file__), '..')) |
| tracing_third_party = os.path.join(tracing_root, 'tracing', 'third_party') |
| has_invalid_license = checklicenses.check_licenses( |
| tracing_root, tracing_third_party) |
| if has_invalid_license: |
| results.append( |
| 'License check encountered invalid licenses in tracing/third_party/.') |
| return results |
| |
| |
| def _CheckCopyrightNonThirdParty(input_api): |
| project_name = 'Chromium' |
| |
| current_year = int(time.strftime('%Y')) |
| allow_old_years=True |
| if allow_old_years: |
| allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1))) |
| else: |
| allowed_years = [str(current_year)] |
| years_re = '(' + '|'.join(allowed_years) + ')' |
| |
| # The (c) is deprecated, but tolerate it until it's removed from all files. |
| non_html_license_header = ( |
| r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. ' |
| r'All rights reserved\.\n' |
| r'.*? Use of this source code is governed by a BSD-style license that ' |
| r'can be\n' |
| r'.*? found in the LICENSE file\.(?: \*/)?\n' |
| ) % { |
| 'year': years_re, |
| 'project': project_name, |
| } |
| non_html_license_re = re.compile(non_html_license_header, re.MULTILINE) |
| |
| html_license_header = ( |
| r'^Copyright (\(c\) )?%(year)s The %(project)s Authors\. ' |
| r'All rights reserved\.\n' |
| r'Use of this source code is governed by a BSD-style license that ' |
| r'can be\n' |
| r'found in the LICENSE file\.(?: \*/)?\n' |
| ) % { |
| 'year': years_re, |
| 'project': project_name, |
| } |
| html_license_re = re.compile(html_license_header, re.MULTILINE) |
| |
| sources = list(s for s in input_api.AffectedFiles(include_deletes=False) |
| if not tracing_project.TracingProject.IsThirdParty(s)) |
| |
| html_sources = [f for f in sources |
| if os.path.splitext(f.LocalPath())[1] == '.html'] |
| non_html_sources = [f for f in sources |
| if os.path.splitext(f.LocalPath())[1] != '.html'] |
| |
| results = [] |
| results += _Check(input_api, html_license_re, html_sources) |
| results += _Check(input_api, non_html_license_re, non_html_sources) |
| return results |
| |
| |
| def _Check(input_api, license_re, sources): |
| bad_files = [] |
| for f in sources: |
| if tracing_project.TracingProject.IsIgnoredFile(f): |
| continue |
| contents = '\n'.join(f.NewContents()) |
| if not license_re.search(contents): |
| bad_files.append(f.LocalPath()) |
| if bad_files: |
| return [_FormatError( |
| 'License must match:\n%s\n' % license_re.pattern + |
| 'Found a bad license header', |
| bad_files)] |
| return [] |
| |
| |
| def CheckLongLines(input_api, maxlen=80): |
| """Checks the line length in all text files to be submitted.""" |
| maxlens = { |
| '': maxlen, |
| } |
| |
| # Language specific exceptions to max line length. |
| # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a |
| # superset of CPP_EXCEPTIONS. |
| CPP_FILE_EXTS = ('c', 'cc') |
| CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma') |
| JAVA_FILE_EXTS = ('java',) |
| JAVA_EXCEPTIONS = ('import ', 'package ') |
| OBJC_FILE_EXTS = ('h', 'm', 'mm') |
| OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include', |
| '#pragma') |
| |
| LANGUAGE_EXCEPTIONS = [ |
| (CPP_FILE_EXTS, CPP_EXCEPTIONS), |
| (JAVA_FILE_EXTS, JAVA_EXCEPTIONS), |
| (OBJC_FILE_EXTS, OBJC_EXCEPTIONS), |
| ] |
| |
| def no_long_lines(file_extension, line): |
| # Check for language specific exceptions. |
| if any(file_extension in exts and line.startswith(exceptions) |
| for exts, exceptions in LANGUAGE_EXCEPTIONS): |
| return True |
| |
| file_maxlen = maxlens.get(file_extension, maxlens['']) |
| # Stupidly long symbols that needs to be worked around if takes 66% of line. |
| long_symbol = file_maxlen * 2 / 3 |
| # Hard line length limit at 50% more. |
| extra_maxlen = file_maxlen * 3 / 2 |
| |
| line_len = len(line) |
| if line_len <= file_maxlen: |
| return True |
| |
| if '@suppress longLineCheck' in line: |
| return True |
| |
| if line_len > extra_maxlen: |
| return False |
| |
| if any((url in line) for url in ('file://', 'http://', 'https://')): |
| return True |
| |
| if 'url(' in line and file_extension == 'css': |
| return True |
| |
| if '<include' in line and file_extension in ('css', 'html', 'js'): |
| return True |
| |
| return re.match( |
| r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line) |
| |
| def format_error(filename, line_num, line): |
| return '%s, line %s, %s chars' % (filename, line_num, len(line)) |
| |
| errors = _FindNewViolationsOfRule(no_long_lines, input_api, |
| error_formatter=format_error) |
| if errors: |
| return [_FormatError( |
| 'Found lines longer than %s characters' % maxlen, |
| errors)] |
| else: |
| return [] |
| |
| def CheckChangeLogBug(input_api): |
| results = [] |
| if input_api.change.BUG is None or re.match('\#\d+$', input_api.change.BUG): |
| return [] |
| return [('Invalid bug "%s". BUG= should either not be present or start' |
| ' with # for a github issue.' % input_api.change.BUG)] |
| |
| |
| def RunChecks(input_api): |
| results = [] |
| results += CheckCopyright(input_api) |
| results += CheckLongLines(input_api) |
| results += CheckChangeLogBug(input_api) |
| return results |