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/enums.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 'ppapi/thunk/interfaces_ppb_public_socket.h') 172 HISTOGRAM_XML_FILE = 'tools/metrics/histograms/enums.xml' 173 interface_changes = [] 174 has_histogram_xml_change = False 175 for filename in files: 176 path = filename.LocalPath() 177 if path in INTERFACE_FILES: 178 interface_changes.append(path) 179 if path == HISTOGRAM_XML_FILE: 180 has_histogram_xml_change = True 181 182 if interface_changes and not has_histogram_xml_change: 183 return [output_api.PresubmitNotifyResult( 184 'Missing change to tools/metrics/histograms/enums.xml.\n' + 185 'Run pepper_hash_for_uma to make get values for new interfaces.\n' + 186 'Interface changes:\n' + '\n'.join(interface_changes))] 187 return [] 188 189def CheckChange(input_api, output_api): 190 results = [] 191 192 results.extend(RunUnittests(input_api, output_api)) 193 194 results.extend(CheckTODO(input_api, output_api)) 195 196 results.extend(CheckUnversionedPPB(input_api, output_api)) 197 198 results.extend(CheckUpdatedNaClSDK(input_api, output_api)) 199 200 results.extend(CheckHistogramXml(input_api, output_api)) 201 202 # Verify all modified *.idl have a matching *.h 203 files = input_api.LocalPaths() 204 h_files = [] 205 idl_files = [] 206 generators_changed = False 207 208 # These are autogenerated by the command buffer generator, they don't go 209 # through idl. 210 whitelist = ['ppb_opengles2', 'ppb_opengles2ext_dev'] 211 212 # The PDF interface is hand-written. 213 whitelist += ['ppb_pdf', 'ppp_pdf'] 214 215 # Find all relevant .h and .idl files. 216 for filename in files: 217 name, ext = os.path.splitext(filename) 218 name_parts = name.split(os.sep) 219 if name_parts[-1] in whitelist: 220 continue 221 if name_parts[0:2] == ['ppapi', 'c'] and ext == '.h': 222 h_files.append('/'.join(name_parts[2:])) 223 elif name_parts[0:2] == ['ppapi', 'api'] and ext == '.idl': 224 idl_files.append('/'.join(name_parts[2:])) 225 elif name_parts[0:2] == ['ppapi', 'generators']: 226 generators_changed = True 227 228 # Generate a list of all appropriate *.h and *.idl changes in this CL. 229 both = h_files + idl_files 230 231 # If there aren't any, we are done checking. 232 if not both: return results 233 234 missing = [] 235 for filename in idl_files: 236 if filename not in set(h_files): 237 missing.append('ppapi/api/%s.idl' % filename) 238 239 # An IDL change that includes [generate_thunk] doesn't need to have 240 # an update to the corresponding .h file. 241 new_thunk_files = [] 242 for filename in missing: 243 lines = input_api.RightHandSideLines(lambda f: f.LocalPath() == filename) 244 for line in lines: 245 if line[2].strip() == '[generate_thunk]': 246 new_thunk_files.append(filename) 247 for filename in new_thunk_files: 248 missing.remove(filename) 249 250 if missing: 251 results.append( 252 output_api.PresubmitPromptWarning( 253 'Missing PPAPI header, no change or skipped generation?', 254 long_text='\n '.join(missing))) 255 256 missing_dev = [] 257 missing_stable = [] 258 missing_priv = [] 259 for filename in h_files: 260 if filename not in set(idl_files): 261 name_parts = filename.split(os.sep) 262 263 if name_parts[-1] == 'pp_macros': 264 # The C header generator adds a PPAPI_RELEASE macro based on all the 265 # IDL files, so pp_macros.h may change while its IDL does not. 266 lines = input_api.RightHandSideLines( 267 lambda f: f.LocalPath() == 'ppapi/c/%s.h' % filename) 268 releaseChanged = False 269 for line in lines: 270 if line[2].split()[:2] == ['#define', 'PPAPI_RELEASE']: 271 results.append( 272 output_api.PresubmitPromptOrNotify( 273 'PPAPI_RELEASE has changed', long_text=line[2])) 274 releaseChanged = True 275 break 276 if releaseChanged: 277 continue 278 279 if 'trusted' in name_parts: 280 missing_priv.append(' ppapi/c/%s.h' % filename) 281 continue 282 283 if 'private' in name_parts: 284 missing_priv.append(' ppapi/c/%s.h' % filename) 285 continue 286 287 if 'dev' in name_parts: 288 missing_dev.append(' ppapi/c/%s.h' % filename) 289 continue 290 291 missing_stable.append(' ppapi/c/%s.h' % filename) 292 293 if missing_priv: 294 results.append( 295 output_api.PresubmitPromptWarning( 296 'Missing PPAPI IDL for private interface, please generate IDL:', 297 long_text='\n'.join(missing_priv))) 298 299 if missing_dev: 300 results.append( 301 output_api.PresubmitPromptWarning( 302 'Missing PPAPI IDL for DEV, required before moving to stable:', 303 long_text='\n'.join(missing_dev))) 304 305 if missing_stable: 306 # It might be okay that the header changed without a corresponding IDL 307 # change. E.g., comment indenting may have been changed. Treat this as a 308 # warning. 309 if generators_changed: 310 results.append( 311 output_api.PresubmitPromptWarning( 312 'Missing PPAPI IDL for stable interface (due to change in ' + 313 'generators?):', 314 long_text='\n'.join(missing_stable))) 315 else: 316 results.append( 317 output_api.PresubmitError( 318 'Missing PPAPI IDL for stable interface:', 319 long_text='\n'.join(missing_stable))) 320 321 # Verify all *.h files match *.idl definitions, use: 322 # --test to prevent output to disk 323 # --diff to generate a unified diff 324 # --out to pick which files to examine (only the ones in the CL) 325 ppapi_dir = input_api.PresubmitLocalPath() 326 cmd = [sys.executable, 'generator.py', 327 '--wnone', '--diff', '--test','--cgen', '--range=start,end'] 328 329 # Only generate output for IDL files references (as *.h or *.idl) in this CL 330 cmd.append('--out=' + ','.join([name + '.idl' for name in both])) 331 cmd_results = RunCmdAndCheck(cmd, 332 'PPAPI IDL Diff detected: Run the generator.', 333 output_api, 334 os.path.join(ppapi_dir, 'generators')) 335 if cmd_results: 336 results.extend(cmd_results) 337 338 return results 339 340 341def CheckChangeOnUpload(input_api, output_api): 342 return CheckChange(input_api, output_api) 343 344 345def CheckChangeOnCommit(input_api, output_api): 346 return CheckChange(input_api, output_api) 347