# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import os import re import sys import subprocess def RunCmdAndCheck(cmd, err_string, output_api, cwd=None, warning=False): results = [] p = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (p_stdout, p_stderr) = p.communicate() if p.returncode: if warning: results.append(output_api.PresubmitPromptWarning( '%s\n\n%s' % (err_string, p_stderr))) else: results.append( output_api.PresubmitError(err_string, long_text=p_stderr)) return results def RunUnittests(input_api, output_api): # Run some Generator unittests if the generator source was changed. results = [] files = input_api.LocalPaths() generator_files = [] for filename in files: name_parts = filename.split(os.sep) if name_parts[0:2] == ['ppapi', 'generators']: generator_files.append(filename) if generator_files != []: cmd = [ sys.executable, 'idl_tests.py'] ppapi_dir = input_api.PresubmitLocalPath() results.extend(RunCmdAndCheck(cmd, 'PPAPI IDL unittests failed.', output_api, os.path.join(ppapi_dir, 'generators'))) return results # Verify that the files do not contain a 'TODO' in them. RE_TODO = re.compile(r'\WTODO\W', flags=re.I) def CheckTODO(input_api, output_api): live_files = input_api.AffectedFiles(include_deletes=False) files = [f.LocalPath() for f in live_files] todo = [] for filename in files: name, ext = os.path.splitext(filename) name_parts = name.split(os.sep) # Only check normal build sources. if ext not in ['.h', '.idl']: continue # Only examine the ppapi directory. if name_parts[0] != 'ppapi': continue # Only examine public plugin facing directories. if name_parts[1] not in ['api', 'c', 'cpp', 'utility']: continue # Only examine public stable interfaces. if name_parts[2] in ['dev', 'private', 'trusted']: continue filepath = os.path.join('..', filename) if RE_TODO.search(open(filepath, 'rb').read()): todo.append(filename) if todo: return [output_api.PresubmitError( 'TODOs found in stable public PPAPI files:', long_text='\n'.join(todo))] return [] # Verify that no CPP wrappers use un-versioned PPB interface name macros. RE_UNVERSIONED_PPB = re.compile(r'\bPPB_\w+_INTERFACE\b') def CheckUnversionedPPB(input_api, output_api): live_files = input_api.AffectedFiles(include_deletes=False) files = [f.LocalPath() for f in live_files] todo = [] for filename in files: name, ext = os.path.splitext(filename) name_parts = name.split(os.sep) # Only check C++ sources. if ext not in ['.cc']: continue # Only examine the public plugin facing ppapi/cpp directory. if name_parts[0:2] != ['ppapi', 'cpp']: continue # Only examine public stable and trusted interfaces. if name_parts[2] in ['dev', 'private']: continue filepath = os.path.join('..', filename) if RE_UNVERSIONED_PPB.search(open(filepath, 'rb').read()): todo.append(filename) if todo: return [output_api.PresubmitError( 'Unversioned PPB interface references found in PPAPI C++ wrappers:', long_text='\n'.join(todo))] return [] # Verify that changes to ppapi headers/sources are also made to NaCl SDK. def CheckUpdatedNaClSDK(input_api, output_api): files = input_api.LocalPaths() # PPAPI files the Native Client SDK cares about. nacl_sdk_files = [] for filename in files: name, ext = os.path.splitext(filename) name_parts = name.split(os.sep) if len(name_parts) <= 2: continue if name_parts[0] != 'ppapi': continue if ((name_parts[1] == 'c' and ext == '.h') or (name_parts[1] in ('cpp', 'utility') and ext in ('.h', '.cc'))): if name_parts[2] in ('documentation', 'trusted'): continue nacl_sdk_files.append(filename) if not nacl_sdk_files: return [] verify_ppapi_py = os.path.join(input_api.change.RepositoryRoot(), 'native_client_sdk', 'src', 'build_tools', 'verify_ppapi.py') cmd = [sys.executable, verify_ppapi_py] + nacl_sdk_files return RunCmdAndCheck(cmd, 'PPAPI Interface modified without updating NaCl SDK.\n' '(note that some dev interfaces should not be added ' 'the NaCl SDK; when in doubt, ask a ppapi OWNER.\n' 'To ignore a file, add it to IGNORED_FILES in ' 'native_client_sdk/src/build_tools/verify_ppapi.py)', output_api, warning=True) # Verify that changes to ppapi/thunk/interfaces_* files have a corresponding # change to tools/metrics/histograms/histograms.xml for UMA tracking. def CheckHistogramXml(input_api, output_api): # We can't use input_api.LocalPaths() here because we need to know about # changes outside of ppapi/. See tools/depot_tools/presubmit_support.py for # details on input_api. files = input_api.change.AffectedFiles() INTERFACE_FILES = ('ppapi/thunk/interfaces_legacy.h', 'ppapi/thunk/interfaces_ppb_private_flash.h', 'ppapi/thunk/interfaces_ppb_private.h', 'ppapi/thunk/interfaces_ppb_private_no_permissions.h', 'ppapi/thunk/interfaces_ppb_public_dev_channel.h', 'ppapi/thunk/interfaces_ppb_public_dev.h', 'ppapi/thunk/interfaces_ppb_public_stable.h') HISTOGRAM_XML_FILE = 'tools/metrics/histograms/histograms.xml' interface_changes = [] has_histogram_xml_change = False for filename in files: path = filename.LocalPath() if path in INTERFACE_FILES: interface_changes.append(path) if path == HISTOGRAM_XML_FILE: has_histogram_xml_change = True if interface_changes and not has_histogram_xml_change: return [output_api.PresubmitNotifyResult( 'Missing change to tools/metrics/histograms/histograms.xml.\n' + 'Run pepper_hash_for_uma to make get values for new interfaces.\n' + 'Interface changes:\n' + '\n'.join(interface_changes))] return [] def CheckChange(input_api, output_api): results = [] results.extend(RunUnittests(input_api, output_api)) results.extend(CheckTODO(input_api, output_api)) results.extend(CheckUnversionedPPB(input_api, output_api)) results.extend(CheckUpdatedNaClSDK(input_api, output_api)) results.extend(CheckHistogramXml(input_api, output_api)) # Verify all modified *.idl have a matching *.h files = input_api.LocalPaths() h_files = [] idl_files = [] generators_changed = False # These are autogenerated by the command buffer generator, they don't go # through idl. whitelist = ['ppb_opengles2', 'ppb_opengles2ext_dev'] # The PDF interface is hand-written. whitelist += ['ppb_pdf', 'ppp_pdf'] # Find all relevant .h and .idl files. for filename in files: name, ext = os.path.splitext(filename) name_parts = name.split(os.sep) if name_parts[-1] in whitelist: continue if name_parts[0:2] == ['ppapi', 'c'] and ext == '.h': h_files.append('/'.join(name_parts[2:])) elif name_parts[0:2] == ['ppapi', 'api'] and ext == '.idl': idl_files.append('/'.join(name_parts[2:])) elif name_parts[0:2] == ['ppapi', 'generators']: generators_changed = True # Generate a list of all appropriate *.h and *.idl changes in this CL. both = h_files + idl_files # If there aren't any, we are done checking. if not both: return results missing = [] for filename in idl_files: if filename not in set(h_files): missing.append('ppapi/api/%s.idl' % filename) # An IDL change that includes [generate_thunk] doesn't need to have # an update to the corresponding .h file. new_thunk_files = [] for filename in missing: lines = input_api.RightHandSideLines(lambda f: f.LocalPath() == filename) for line in lines: if line[2].strip() == '[generate_thunk]': new_thunk_files.append(filename) for filename in new_thunk_files: missing.remove(filename) if missing: results.append( output_api.PresubmitPromptWarning( 'Missing PPAPI header, no change or skipped generation?', long_text='\n '.join(missing))) missing_dev = [] missing_stable = [] missing_priv = [] for filename in h_files: if filename not in set(idl_files): name_parts = filename.split(os.sep) if name_parts[-1] == 'pp_macros': # The C header generator adds a PPAPI_RELEASE macro based on all the # IDL files, so pp_macros.h may change while its IDL does not. lines = input_api.RightHandSideLines( lambda f: f.LocalPath() == 'ppapi/c/%s.h' % filename) releaseChanged = False for line in lines: if line[2].split()[:2] == ['#define', 'PPAPI_RELEASE']: results.append( output_api.PresubmitPromptOrNotify( 'PPAPI_RELEASE has changed', long_text=line[2])) releaseChanged = True break if releaseChanged: continue if 'trusted' in name_parts: missing_priv.append(' ppapi/c/%s.h' % filename) continue if 'private' in name_parts: missing_priv.append(' ppapi/c/%s.h' % filename) continue if 'dev' in name_parts: missing_dev.append(' ppapi/c/%s.h' % filename) continue missing_stable.append(' ppapi/c/%s.h' % filename) if missing_priv: results.append( output_api.PresubmitPromptWarning( 'Missing PPAPI IDL for private interface, please generate IDL:', long_text='\n'.join(missing_priv))) if missing_dev: results.append( output_api.PresubmitPromptWarning( 'Missing PPAPI IDL for DEV, required before moving to stable:', long_text='\n'.join(missing_dev))) if missing_stable: # It might be okay that the header changed without a corresponding IDL # change. E.g., comment indenting may have been changed. Treat this as a # warning. if generators_changed: results.append( output_api.PresubmitPromptWarning( 'Missing PPAPI IDL for stable interface (due to change in ' + 'generators?):', long_text='\n'.join(missing_stable))) else: results.append( output_api.PresubmitError( 'Missing PPAPI IDL for stable interface:', long_text='\n'.join(missing_stable))) # Verify all *.h files match *.idl definitions, use: # --test to prevent output to disk # --diff to generate a unified diff # --out to pick which files to examine (only the ones in the CL) ppapi_dir = input_api.PresubmitLocalPath() cmd = [sys.executable, 'generator.py', '--wnone', '--diff', '--test','--cgen', '--range=start,end'] # Only generate output for IDL files references (as *.h or *.idl) in this CL cmd.append('--out=' + ','.join([name + '.idl' for name in both])) cmd_results = RunCmdAndCheck(cmd, 'PPAPI IDL Diff detected: Run the generator.', output_api, os.path.join(ppapi_dir, 'generators')) if cmd_results: results.extend(cmd_results) return results def CheckChangeOnUpload(input_api, output_api): return CheckChange(input_api, output_api) def CheckChangeOnCommit(input_api, output_api): return CheckChange(input_api, output_api)