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