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