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 return results 85 86 87 def _CheckSubversionConfig(input_api, output_api): 88 """Verifies the subversion config file is correctly setup. 89 90 Checks that autoprops are enabled, returns an error otherwise. 91 """ 92 join = input_api.os_path.join 93 if input_api.platform == 'win32': 94 appdata = input_api.environ.get('APPDATA', '') 95 if not appdata: 96 return [output_api.PresubmitError('%APPDATA% is not configured.')] 97 path = join(appdata, 'Subversion', 'config') 98 else: 99 home = input_api.environ.get('HOME', '') 100 if not home: 101 return [output_api.PresubmitError('$HOME is not configured.')] 102 path = join(home, '.subversion', 'config') 103 104 error_msg = ( 105 'Please look at http://dev.chromium.org/developers/coding-style to\n' 106 'configure your subversion configuration file. This enables automatic\n' 107 'properties to simplify the project maintenance.\n' 108 'Pro-tip: just download and install\n' 109 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n') 110 111 try: 112 lines = open(path, 'r').read().splitlines() 113 # Make sure auto-props is enabled and check for 2 Chromium standard 114 # auto-prop. 115 if (not '*.cc = svn:eol-style=LF' in lines or 116 not '*.pdf = svn:mime-type=application/pdf' in lines or 117 not 'enable-auto-props = yes' in lines): 118 return [ 119 output_api.PresubmitNotifyResult( 120 'It looks like you have not configured your subversion config ' 121 'file or it is not up-to-date.\n' + error_msg) 122 ] 123 except (OSError, IOError): 124 return [ 125 output_api.PresubmitNotifyResult( 126 'Can\'t find your subversion config file.\n' + error_msg) 127 ] 128 return [] 129 130 131 def _CheckPatchFiles(input_api, output_api): 132 problems = [f.LocalPath() for f in input_api.AffectedFiles() 133 if f.LocalPath().endswith(('.orig', '.rej'))] 134 if problems: 135 return [output_api.PresubmitError( 136 "Don't commit .rej and .orig files.", problems)] 137 else: 138 return [] 139 140 141 def _CheckTestExpectations(input_api, output_api): 142 local_paths = [f.LocalPath() for f in input_api.AffectedFiles()] 143 if any(path.startswith('LayoutTests') for path in local_paths): 144 lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 145 'Tools', 'Scripts', 'lint-test-expectations') 146 _, errs = input_api.subprocess.Popen( 147 [input_api.python_executable, lint_path], 148 stdout=input_api.subprocess.PIPE, 149 stderr=input_api.subprocess.PIPE).communicate() 150 if not errs: 151 return [output_api.PresubmitError( 152 "lint-test-expectations failed " 153 "to produce output; check by hand. ")] 154 if errs.strip() != 'Lint succeeded.': 155 return [output_api.PresubmitError(errs)] 156 return [] 157 158 159 def _CheckStyle(input_api, output_api): 160 style_checker_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 161 'Tools', 'Scripts', 'check-webkit-style') 162 args = ([input_api.python_executable, style_checker_path, '--diff-files'] 163 + [f.LocalPath() for f in input_api.AffectedFiles()]) 164 results = [] 165 166 try: 167 child = input_api.subprocess.Popen(args, 168 stderr=input_api.subprocess.PIPE) 169 _, stderrdata = child.communicate() 170 if child.returncode != 0: 171 results.append(output_api.PresubmitError( 172 'check-webkit-style failed', [stderrdata])) 173 except Exception as e: 174 results.append(output_api.PresubmitNotifyResult( 175 'Could not run check-webkit-style', [str(e)])) 176 177 return results 178 179 180 def _CheckUnwantedDependencies(input_api, output_api): 181 """Runs checkdeps on #include statements added in this 182 change. Breaking - rules is an error, breaking ! rules is a 183 warning. 184 """ 185 # We need to wait until we have an input_api object and use this 186 # roundabout construct to import checkdeps because this file is 187 # eval-ed and thus doesn't have __file__. 188 original_sys_path = sys.path 189 try: 190 sys.path = sys.path + [input_api.os_path.realpath(input_api.os_path.join( 191 input_api.PresubmitLocalPath(), '..', '..', 'tools', 'checkdeps'))] 192 import checkdeps 193 from cpp_checker import CppChecker 194 from rules import Rule 195 finally: 196 # Restore sys.path to what it was before. 197 sys.path = original_sys_path 198 199 added_includes = [] 200 for f in input_api.AffectedFiles(): 201 if not CppChecker.IsCppFile(f.LocalPath()): 202 continue 203 204 changed_lines = [line for line_num, line in f.ChangedContents()] 205 added_includes.append([f.LocalPath(), changed_lines]) 206 207 deps_checker = checkdeps.DepsChecker( 208 input_api.os_path.join(input_api.PresubmitLocalPath())) 209 210 error_descriptions = [] 211 warning_descriptions = [] 212 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes( 213 added_includes): 214 description_with_path = '%s\n %s' % (path, rule_description) 215 if rule_type == Rule.DISALLOW: 216 error_descriptions.append(description_with_path) 217 else: 218 warning_descriptions.append(description_with_path) 219 220 results = [] 221 if error_descriptions: 222 results.append(output_api.PresubmitError( 223 'You added one or more #includes that violate checkdeps rules.', 224 error_descriptions)) 225 if warning_descriptions: 226 results.append(output_api.PresubmitPromptOrNotify( 227 'You added one or more #includes of files that are temporarily\n' 228 'allowed but being removed. Can you avoid introducing the\n' 229 '#include? See relevant DEPS file(s) for details and contacts.', 230 warning_descriptions)) 231 return results 232 233 234 def _CheckChromiumPlatformMacros(input_api, output_api, source_file_filter=None): 235 """Ensures that Blink code uses WTF's platform macros instead of 236 Chromium's. Using the latter has resulted in at least one subtle 237 build breakage.""" 238 os_macro_re = input_api.re.compile(r'^\s*#(el)?if.*\bOS_') 239 errors = input_api.canned_checks._FindNewViolationsOfRule( 240 lambda _, x: not os_macro_re.search(x), 241 input_api, source_file_filter) 242 errors = ['Found use of Chromium OS_* macro in %s. ' 243 'Use WTF platform macros instead.' % violation for violation in errors] 244 if errors: 245 return [output_api.PresubmitPromptWarning('\n'.join(errors))] 246 return [] 247 248 249 def _CompileDevtoolsFrontend(input_api, output_api): 250 if not input_api.platform.startswith('linux'): 251 return [] 252 local_paths = [f.LocalPath() for f in input_api.AffectedFiles()] 253 if (any("devtools/front_end" in path for path in local_paths) or 254 any("InjectedScriptSource.js" in path for path in local_paths) or 255 any("InjectedScriptCanvasModuleSource.js" in path for path in local_paths)): 256 lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 257 "Source", "devtools", "scripts", "compile_frontend.py") 258 out, _ = input_api.subprocess.Popen( 259 [input_api.python_executable, lint_path], 260 stdout=input_api.subprocess.PIPE, 261 stderr=input_api.subprocess.STDOUT).communicate() 262 if "WARNING" in out or "ERROR" in out: 263 return [output_api.PresubmitError(out)] 264 return [] 265 266 267 def _CheckForPrintfDebugging(input_api, output_api): 268 """Generally speaking, we'd prefer not to land patches that printf 269 debug output.""" 270 os_macro_re = input_api.re.compile(r'^\s*printf\(') 271 errors = input_api.canned_checks._FindNewViolationsOfRule( 272 lambda _, x: not os_macro_re.search(x), 273 input_api, None) 274 errors = [' * %s' % violation for violation in errors] 275 if errors: 276 return [output_api.PresubmitPromptOrNotify( 277 'printf debugging is best debugging! That said, it might ' 278 'be a good idea to drop the following occurances from ' 279 'your patch before uploading:\n%s' % '\n'.join(errors))] 280 return [] 281 282 283 def _CheckForFailInFile(input_api, f): 284 pattern = input_api.re.compile('^FAIL') 285 errors = [] 286 for line_num, line in f.ChangedContents(): 287 if pattern.match(line): 288 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line)) 289 return errors 290 291 292 def CheckChangeOnUpload(input_api, output_api): 293 results = [] 294 results.extend(_CommonChecks(input_api, output_api)) 295 results.extend(_CheckStyle(input_api, output_api)) 296 results.extend(_CheckForPrintfDebugging(input_api, output_api)) 297 results.extend(_CompileDevtoolsFrontend(input_api, output_api)) 298 return results 299 300 301 def CheckChangeOnCommit(input_api, output_api): 302 results = [] 303 results.extend(_CommonChecks(input_api, output_api)) 304 results.extend(input_api.canned_checks.CheckTreeIsOpen( 305 input_api, output_api, 306 json_url='http://blink-status.appspot.com/current?format=json')) 307 results.extend(input_api.canned_checks.CheckChangeHasDescription( 308 input_api, output_api)) 309 results.extend(_CheckSubversionConfig(input_api, output_api)) 310 return results 311 312 def GetPreferredTrySlaves(project, change): 313 return [ 314 'linux_blink_rel', 'mac_blink_rel', 'win_blink_rel', 315 'linux_blink', 'mac_layout:webkit_lint', 'win_layout:webkit_lint', 316 ] 317