| # 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', | 
 |                      'ppapi/thunk/interfaces_ppb_public_socket.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) |