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