|  | # 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) |