1# Copyright (c) 2012 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 subprocess 9 10 11def RunCmdAndCheck(cmd, err_string, output_api, cwd=None, warning=False): 12 results = [] 13 p = subprocess.Popen(cmd, cwd=cwd, 14 stdout=subprocess.PIPE, 15 stderr=subprocess.PIPE) 16 (p_stdout, p_stderr) = p.communicate() 17 if p.returncode: 18 if warning: 19 results.append(output_api.PresubmitPromptWarning( 20 '%s\n\n%s' % (err_string, p_stderr))) 21 else: 22 results.append( 23 output_api.PresubmitError(err_string, 24 long_text=p_stderr)) 25 return results 26 27 28def RunUnittests(input_api, output_api): 29 # Run some Generator unittests if the generator source was changed. 30 results = [] 31 files = input_api.LocalPaths() 32 generator_files = [] 33 for filename in files: 34 name_parts = filename.split(os.sep) 35 if name_parts[0:2] == ['ppapi', 'generators']: 36 generator_files.append(filename) 37 if generator_files != []: 38 cmd = [ sys.executable, 'idl_tests.py'] 39 ppapi_dir = input_api.PresubmitLocalPath() 40 results.extend(RunCmdAndCheck(cmd, 41 'PPAPI IDL unittests failed.', 42 output_api, 43 os.path.join(ppapi_dir, 'generators'))) 44 return results 45 46 47# Verify that the files do not contain a 'TODO' in them. 48RE_TODO = re.compile(r'\WTODO\W', flags=re.I) 49def CheckTODO(input_api, output_api): 50 live_files = input_api.AffectedFiles(include_deletes=False) 51 files = [f.LocalPath() for f in live_files] 52 todo = [] 53 54 for filename in files: 55 name, ext = os.path.splitext(filename) 56 name_parts = name.split(os.sep) 57 58 # Only check normal build sources. 59 if ext not in ['.h', '.idl']: 60 continue 61 62 # Only examine the ppapi directory. 63 if name_parts[0] != 'ppapi': 64 continue 65 66 # Only examine public plugin facing directories. 67 if name_parts[1] not in ['api', 'c', 'cpp', 'utility']: 68 continue 69 70 # Only examine public stable interfaces. 71 if name_parts[2] in ['dev', 'private', 'trusted']: 72 continue 73 74 filepath = os.path.join('..', filename) 75 if RE_TODO.search(open(filepath, 'rb').read()): 76 todo.append(filename) 77 78 if todo: 79 return [output_api.PresubmitError( 80 'TODOs found in stable public PPAPI files:', 81 long_text='\n'.join(todo))] 82 return [] 83 84# Verify that no CPP wrappers use un-versioned PPB interface name macros. 85RE_UNVERSIONED_PPB = re.compile(r'\bPPB_\w+_INTERFACE\b') 86def CheckUnversionedPPB(input_api, output_api): 87 live_files = input_api.AffectedFiles(include_deletes=False) 88 files = [f.LocalPath() for f in live_files] 89 todo = [] 90 91 for filename in files: 92 name, ext = os.path.splitext(filename) 93 name_parts = name.split(os.sep) 94 95 # Only check C++ sources. 96 if ext not in ['.cc']: 97 continue 98 99 # Only examine the public plugin facing ppapi/cpp directory. 100 if name_parts[0:2] != ['ppapi', 'cpp']: 101 continue 102 103 # Only examine public stable and trusted interfaces. 104 if name_parts[2] in ['dev', 'private']: 105 continue 106 107 filepath = os.path.join('..', filename) 108 if RE_UNVERSIONED_PPB.search(open(filepath, 'rb').read()): 109 todo.append(filename) 110 111 if todo: 112 return [output_api.PresubmitError( 113 'Unversioned PPB interface references found in PPAPI C++ wrappers:', 114 long_text='\n'.join(todo))] 115 return [] 116 117# Verify that changes to ppapi headers/sources are also made to NaCl SDK. 118def CheckUpdatedNaClSDK(input_api, output_api): 119 files = input_api.LocalPaths() 120 121 # PPAPI files the Native Client SDK cares about. 122 nacl_sdk_files = [] 123 124 for filename in files: 125 name, ext = os.path.splitext(filename) 126 name_parts = name.split(os.sep) 127 128 if len(name_parts) <= 2: 129 continue 130 131 if name_parts[0] != 'ppapi': 132 continue 133 134 if ((name_parts[1] == 'c' and ext == '.h') or 135 (name_parts[1] in ('cpp', 'utility') and ext in ('.h', '.cc'))): 136 if name_parts[2] in ('documentation', 'trusted'): 137 continue 138 nacl_sdk_files.append(filename) 139 140 if not nacl_sdk_files: 141 return [] 142 143 verify_ppapi_py = os.path.join(input_api.change.RepositoryRoot(), 144 'native_client_sdk', 'src', 'build_tools', 145 'verify_ppapi.py') 146 cmd = [sys.executable, verify_ppapi_py] + nacl_sdk_files 147 return RunCmdAndCheck(cmd, 148 'PPAPI Interface modified without updating NaCl SDK.\n' 149 '(note that some dev interfaces should not be added ' 150 'the NaCl SDK; when in doubt, ask a ppapi OWNER.\n' 151 'To ignore a file, add it to IGNORED_FILES in ' 152 'native_client_sdk/src/build_tools/verify_ppapi.py)', 153 output_api, 154 warning=True) 155 156# Verify that changes to ppapi/thunk/interfaces_* files have a corresponding 157# change to tools/metrics/histograms/histograms.xml for UMA tracking. 158def CheckHistogramXml(input_api, output_api): 159 # We can't use input_api.LocalPaths() here because we need to know about 160 # changes outside of ppapi/. See tools/depot_tools/presubmit_support.py for 161 # details on input_api. 162 files = input_api.change.AffectedFiles() 163 164 INTERFACE_FILES = ('ppapi/thunk/interfaces_legacy.h', 165 'ppapi/thunk/interfaces_ppb_private_flash.h', 166 'ppapi/thunk/interfaces_ppb_private.h', 167 'ppapi/thunk/interfaces_ppb_private_no_permissions.h', 168 'ppapi/thunk/interfaces_ppb_public_dev_channel.h', 169 'ppapi/thunk/interfaces_ppb_public_dev.h', 170 'ppapi/thunk/interfaces_ppb_public_stable.h') 171 HISTOGRAM_XML_FILE = 'tools/metrics/histograms/histograms.xml' 172 interface_changes = [] 173 has_histogram_xml_change = False 174 for filename in files: 175 path = filename.LocalPath() 176 if path in INTERFACE_FILES: 177 interface_changes.append(path) 178 if path == HISTOGRAM_XML_FILE: 179 has_histogram_xml_change = True 180 181 if interface_changes and not has_histogram_xml_change: 182 return [output_api.PresubmitNotifyResult( 183 'Missing change to tools/metrics/histograms/histograms.xml.\n' + 184 'Run pepper_hash_for_uma to make get values for new interfaces.\n' + 185 'Interface changes:\n' + '\n'.join(interface_changes))] 186 return [] 187 188def CheckChange(input_api, output_api): 189 results = [] 190 191 results.extend(RunUnittests(input_api, output_api)) 192 193 results.extend(CheckTODO(input_api, output_api)) 194 195 results.extend(CheckUnversionedPPB(input_api, output_api)) 196 197 results.extend(CheckUpdatedNaClSDK(input_api, output_api)) 198 199 results.extend(CheckHistogramXml(input_api, output_api)) 200 201 # Verify all modified *.idl have a matching *.h 202 files = input_api.LocalPaths() 203 h_files = [] 204 idl_files = [] 205 generators_changed = False 206 207 # These are autogenerated by the command buffer generator, they don't go 208 # through idl. 209 whitelist = ['ppb_opengles2', 'ppb_opengles2ext_dev'] 210 211 # The PDF interface is hand-written. 212 whitelist += ['ppb_pdf', 'ppp_pdf'] 213 214 # Find all relevant .h and .idl files. 215 for filename in files: 216 name, ext = os.path.splitext(filename) 217 name_parts = name.split(os.sep) 218 if name_parts[-1] in whitelist: 219 continue 220 if name_parts[0:2] == ['ppapi', 'c'] and ext == '.h': 221 h_files.append('/'.join(name_parts[2:])) 222 elif name_parts[0:2] == ['ppapi', 'api'] and ext == '.idl': 223 idl_files.append('/'.join(name_parts[2:])) 224 elif name_parts[0:2] == ['ppapi', 'generators']: 225 generators_changed = True 226 227 # Generate a list of all appropriate *.h and *.idl changes in this CL. 228 both = h_files + idl_files 229 230 # If there aren't any, we are done checking. 231 if not both: return results 232 233 missing = [] 234 for filename in idl_files: 235 if filename not in set(h_files): 236 missing.append('ppapi/api/%s.idl' % filename) 237 238 # An IDL change that includes [generate_thunk] doesn't need to have 239 # an update to the corresponding .h file. 240 new_thunk_files = [] 241 for filename in missing: 242 lines = input_api.RightHandSideLines(lambda f: f.LocalPath() == filename) 243 for line in lines: 244 if line[2].strip() == '[generate_thunk]': 245 new_thunk_files.append(filename) 246 for filename in new_thunk_files: 247 missing.remove(filename) 248 249 if missing: 250 results.append( 251 output_api.PresubmitPromptWarning( 252 'Missing PPAPI header, no change or skipped generation?', 253 long_text='\n '.join(missing))) 254 255 missing_dev = [] 256 missing_stable = [] 257 missing_priv = [] 258 for filename in h_files: 259 if filename not in set(idl_files): 260 name_parts = filename.split(os.sep) 261 262 if name_parts[-1] == 'pp_macros': 263 # The C header generator adds a PPAPI_RELEASE macro based on all the 264 # IDL files, so pp_macros.h may change while its IDL does not. 265 lines = input_api.RightHandSideLines( 266 lambda f: f.LocalPath() == 'ppapi/c/%s.h' % filename) 267 releaseChanged = False 268 for line in lines: 269 if line[2].split()[:2] == ['#define', 'PPAPI_RELEASE']: 270 results.append( 271 output_api.PresubmitPromptOrNotify( 272 'PPAPI_RELEASE has changed', long_text=line[2])) 273 releaseChanged = True 274 break 275 if releaseChanged: 276 continue 277 278 if 'trusted' in name_parts: 279 missing_priv.append(' ppapi/c/%s.h' % filename) 280 continue 281 282 if 'private' in name_parts: 283 missing_priv.append(' ppapi/c/%s.h' % filename) 284 continue 285 286 if 'dev' in name_parts: 287 missing_dev.append(' ppapi/c/%s.h' % filename) 288 continue 289 290 missing_stable.append(' ppapi/c/%s.h' % filename) 291 292 if missing_priv: 293 results.append( 294 output_api.PresubmitPromptWarning( 295 'Missing PPAPI IDL for private interface, please generate IDL:', 296 long_text='\n'.join(missing_priv))) 297 298 if missing_dev: 299 results.append( 300 output_api.PresubmitPromptWarning( 301 'Missing PPAPI IDL for DEV, required before moving to stable:', 302 long_text='\n'.join(missing_dev))) 303 304 if missing_stable: 305 # It might be okay that the header changed without a corresponding IDL 306 # change. E.g., comment indenting may have been changed. Treat this as a 307 # warning. 308 if generators_changed: 309 results.append( 310 output_api.PresubmitPromptWarning( 311 'Missing PPAPI IDL for stable interface (due to change in ' + 312 'generators?):', 313 long_text='\n'.join(missing_stable))) 314 else: 315 results.append( 316 output_api.PresubmitError( 317 'Missing PPAPI IDL for stable interface:', 318 long_text='\n'.join(missing_stable))) 319 320 # Verify all *.h files match *.idl definitions, use: 321 # --test to prevent output to disk 322 # --diff to generate a unified diff 323 # --out to pick which files to examine (only the ones in the CL) 324 ppapi_dir = input_api.PresubmitLocalPath() 325 cmd = [sys.executable, 'generator.py', 326 '--wnone', '--diff', '--test','--cgen', '--range=start,end'] 327 328 # Only generate output for IDL files references (as *.h or *.idl) in this CL 329 cmd.append('--out=' + ','.join([name + '.idl' for name in both])) 330 cmd_results = RunCmdAndCheck(cmd, 331 'PPAPI IDL Diff detected: Run the generator.', 332 output_api, 333 os.path.join(ppapi_dir, 'generators')) 334 if cmd_results: 335 results.extend(cmd_results) 336 337 return results 338 339 340def CheckChangeOnUpload(input_api, output_api): 341 return CheckChange(input_api, output_api) 342 343 344def CheckChangeOnCommit(input_api, output_api): 345 return CheckChange(input_api, output_api) 346