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