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 5import os 6import re 7import sys 8import time 9 10import checklicenses 11 12from tracing import tracing_project 13 14 15def _FormatError(msg, files): 16 return ('%s in these files:\n' % msg + 17 '\n'.join(' ' + x for x in files)) 18 19 20def _ReportErrorFileAndLine(filename, line_num, dummy_line): 21 """Default error formatter for _FindNewViolationsOfRule.""" 22 return '%s:%s' % (filename, line_num) 23 24 25def _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 60def CheckCopyright(input_api): 61 results = [] 62 results += _CheckCopyrightThirdParty(input_api) 63 results += _CheckCopyrightNonThirdParty(input_api) 64 return results 65 66 67def _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 84def _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 134def _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 150def 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 219def 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 227def RunChecks(input_api): 228 results = [] 229 results += CheckCopyright(input_api) 230 results += CheckLongLines(input_api) 231 results += CheckChangeLogBug(input_api) 232 return results 233