| #!/usr/bin/env python |
| # Copyright (c) 2013 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. |
| |
| """Helper script for PPAPI's PRESUBMIT.py to detect if additions or removals of |
| PPAPI interfaces have been propagated to the Native Client libraries (.dsc |
| files). |
| |
| For example, if a user adds "ppapi/c/foo.h", we check that the interface has |
| been added to "native_client_sdk/src/libraries/ppapi/library.dsc". |
| """ |
| |
| import argparse |
| import os |
| import sys |
| |
| from build_paths import PPAPI_DIR, SRC_DIR, SDK_LIBRARY_DIR |
| import parse_dsc |
| |
| |
| # Add a file to this list if it should not be added to a .dsc file; i.e. if it |
| # should not be included in the Native Client SDK. This will silence the |
| # presubmit warning. |
| # |
| # Some examples of files that should not be added to the SDK are: Dev and |
| # Private interfaces that are either not available to NaCl plugins or are only |
| # available to Flash or other privileged plugins. |
| IGNORED_FILES = set([ |
| 'pp_video_dev.h', |
| ]) |
| |
| |
| class VerifyException(Exception): |
| def __init__(self, lib_path, expected, unexpected): |
| self.expected = expected |
| self.unexpected = unexpected |
| |
| msg = 'In %s:\n' % lib_path |
| if expected: |
| msg += ' these files are missing and should be added:\n' |
| for filename in sorted(expected): |
| msg += ' %s\n' % filename |
| if unexpected: |
| msg += ' these files no longer exist and should be removed:\n' |
| for filename in sorted(unexpected): |
| msg += ' %s\n' % filename |
| |
| Exception.__init__(self, msg) |
| |
| |
| def PartitionFiles(filenames): |
| c_filenames = set() |
| cpp_filenames = set() |
| private_filenames = set() |
| |
| for filename in filenames: |
| if os.path.splitext(filename)[1] not in ('.cc', '.h'): |
| continue |
| |
| parts = filename.split(os.sep) |
| basename = os.path.basename(filename) |
| if basename in IGNORED_FILES: |
| continue |
| |
| if 'private' in filename: |
| if 'flash' in filename: |
| continue |
| private_filenames.add(filename) |
| elif parts[0:2] == ['ppapi', 'c']: |
| if len(parts) >= 2 and parts[2] in ('documentation', 'trusted'): |
| continue |
| c_filenames.add(filename) |
| elif (parts[0:2] == ['ppapi', 'cpp'] or |
| parts[0:2] == ['ppapi', 'utility']): |
| if len(parts) >= 2 and parts[2] in ('documentation', 'trusted'): |
| continue |
| cpp_filenames.add(filename) |
| else: |
| continue |
| |
| return { |
| 'ppapi': c_filenames, |
| 'ppapi_cpp': cpp_filenames, |
| 'ppapi_cpp_private': private_filenames |
| } |
| |
| |
| def GetDirectoryList(directory_path, relative_to): |
| result = [] |
| for root, _, files in os.walk(directory_path): |
| rel_root = os.path.relpath(root, relative_to) |
| if rel_root == '.': |
| rel_root = '' |
| for base_name in files: |
| result.append(os.path.join(rel_root, base_name)) |
| return result |
| |
| |
| def GetDscSourcesAndHeaders(dsc): |
| result = [] |
| for headers_info in dsc.get('HEADERS', []): |
| result.extend(headers_info['FILES']) |
| for targets_info in dsc.get('TARGETS', []): |
| result.extend(targets_info['SOURCES']) |
| return result |
| |
| |
| def GetChangedAndRemovedFilenames(modified_filenames, directory_list): |
| changed = set() |
| removed = set() |
| directory_list_set = set(directory_list) |
| for filename in modified_filenames: |
| if filename in directory_list_set: |
| # We can't know if a file was added (that would require knowing the |
| # previous state of the working directory). Instead, we assume that a |
| # changed file may have been added, and check it accordingly. |
| changed.add(filename) |
| else: |
| removed.add(filename) |
| return changed, removed |
| |
| |
| def GetDscFilenameFromLibraryName(lib_name): |
| return os.path.join(SDK_LIBRARY_DIR, lib_name, 'library.dsc') |
| |
| |
| def Verify(dsc_filename, dsc_sources_and_headers, changed_filenames, |
| removed_filenames): |
| expected_filenames = set() |
| unexpected_filenames = set() |
| |
| for filename in changed_filenames: |
| basename = os.path.basename(filename) |
| if basename not in dsc_sources_and_headers: |
| expected_filenames.add(filename) |
| |
| for filename in removed_filenames: |
| basename = os.path.basename(filename) |
| if basename in dsc_sources_and_headers: |
| unexpected_filenames.add(filename) |
| |
| if expected_filenames or unexpected_filenames: |
| raise VerifyException(dsc_filename, expected_filenames, |
| unexpected_filenames) |
| |
| |
| def VerifyOrPrintError(dsc_filename, dsc_sources_and_headers, changed_filenames, |
| removed_filenames, is_private=False): |
| try: |
| Verify(dsc_filename, dsc_sources_and_headers, changed_filenames, |
| removed_filenames) |
| except VerifyException as e: |
| should_fail = True |
| if is_private and e.expected: |
| # For ppapi_cpp_private, we don't fail if there are expected filenames... |
| # we may not want to include them. We still want to fail if there are |
| # unexpected filenames, though. |
| sys.stderr.write('>>> WARNING: private interface files changed. ' |
| 'Should they be added to the Native Client SDK? <<<\n') |
| if not e.unexpected: |
| should_fail = False |
| sys.stderr.write(str(e) + '\n') |
| if should_fail: |
| return False |
| return True |
| |
| |
| def main(args): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('sources', nargs='+') |
| options = parser.parse_args(args) |
| |
| retval = 0 |
| lib_files = PartitionFiles(options.sources) |
| directory_list = GetDirectoryList(PPAPI_DIR, relative_to=SRC_DIR) |
| for lib_name, filenames in lib_files.iteritems(): |
| if not filenames: |
| continue |
| |
| changed_filenames, removed_filenames = \ |
| GetChangedAndRemovedFilenames(filenames, directory_list) |
| |
| dsc_filename = GetDscFilenameFromLibraryName(lib_name) |
| dsc = parse_dsc.LoadProject(dsc_filename) |
| dsc_sources_and_headers = GetDscSourcesAndHeaders(dsc) |
| |
| # Use the relative path to the .dsc to make the error messages shorter. |
| rel_dsc_filename = os.path.relpath(dsc_filename, SRC_DIR) |
| is_private = lib_name == 'ppapi_cpp_private' |
| if not VerifyOrPrintError(rel_dsc_filename, dsc_sources_and_headers, |
| changed_filenames, removed_filenames, |
| is_private=is_private): |
| retval = 1 |
| return retval |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |