| # 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/enums.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/enums.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/enums.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) |