| #!/usr/bin/env python3.8 |
| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Verify the runtime dependencies of a prebuilt SDK library (static or shared). |
| See //build/cpp/verify_runtime_deps.gni for details. This takes two input files: |
| |
| - A JSON file that lists the runtime dependencies of the library, each |
| one of them through a schema described in //build/cpp/verify_runtime_deps.gni |
| |
| - An SDK manifest file, another JSON file that describes the SDK atom for |
| the target as well as _all_ its transitive dependencies. See sdk_atom() |
| template for more details. |
| |
| On success, a stamp file is written. On failure, error messages are printed |
| to stderr and the script returns with an error status. |
| """ |
| |
| import argparse |
| import collections |
| import json |
| import os |
| import sys |
| |
| # The following runtime libraries are provided directly by the SDK sysroot, |
| # and not as SDK atoms. |
| _SYSROOT_LIBS = ['libc.so', 'libzircon.so'] |
| |
| |
| def parse_sdk_manifest(manifest): |
| '''Parse SDK manifest file and extract atom id and dependencies. |
| |
| Args: |
| manifest: A directionary representation of the JSON SDK manifest. |
| |
| Returns: |
| an (sdk_id, deps) tuple, where 'sdk_id' is a string identifying the |
| atom (e.g. 'sdk://pkg/async'), and 'deps' is a dictionary mapping |
| the sdk ids of all transitive dependencies to the corresponding |
| manifest JSON object. |
| ''' |
| atom_id = manifest['ids'][0] |
| |
| def find_atom(id): |
| return next(a for a in manifest['atoms'] if a['id'] == id) |
| |
| atom = find_atom(atom_id) |
| deps = [find_atom(a) for a in atom['deps']] |
| deps += [atom] |
| |
| # Maps sdk_ids to the corresponding entry |
| deps_map = {dep['id']: dep for dep in deps} |
| |
| return atom_id, deps_map |
| |
| |
| _NON_SDK_DEPS_ERROR_HEADER = r'''## Non-SDK dependencies required at runtime: |
| ''' |
| |
| _NON_SDK_DEPS_ERROR_FOOTER = r''' |
| HINT: These should be defined by an sdk_shared_library() call, or by a |
| zx_library() one that sets 'sdk = "shared"'. |
| ''' |
| |
| _BAD_SDK_DEPS_ERROR_HEADER = r'''## Non prebuilt libraries required at runtime: |
| ''' |
| |
| _BAD_SDK_DEPS_ERROR_FOOTER = _NON_SDK_DEPS_ERROR_FOOTER |
| |
| _MISSING_SDK_DEPS_ERROR_HEADER = r'''## No dependency generates SDK runtime requirement: |
| ''' |
| |
| _MISSING_SDK_DEPS_ERROR_FOOTER = r''' |
| HINT: Add the missing atom(s)'s targets to the 'runtime_deps' list. |
| ''' |
| |
| |
| class DependencyErrors(object): |
| """Models list of errors found during verification.""" |
| |
| def __init__(self): |
| self._errors = [] |
| |
| def has_error(self): |
| """Return True iff this instance contains errors.""" |
| return bool(self._errors) |
| |
| def add_non_sdk_dependency(self, entry): |
| """Add an entry for a non-SDK dependency. |
| |
| This happens when an SDK atom depends on a shared_library() instance |
| directly, instead of an skd_shared_library() one. |
| """ |
| self._errors.append((entry, 'non_sdk_deps')) |
| |
| def add_bad_sdk_dependency(self, entry): |
| """Add an entry for a non-prebuilt shared library.""" |
| self._errors.append((entry, 'bad_sdk_deps')) |
| |
| def add_missing_sdk_dependency(self, entry): |
| """Add an entry for a non-SDK shared library dependency.""" |
| self._errors.append((entry, 'missing_sdk_deps')) |
| |
| def __str__(self): |
| result = '' |
| # Split errors by categories |
| errors = collections.defaultdict(list) |
| for entry, category in self._errors: |
| errors[category].append(entry) |
| |
| for category, entries in errors.items(): |
| if category == 'non_sdk_deps': |
| result += _NON_SDK_DEPS_ERROR_HEADER |
| for entry in entries: |
| result += '- %s generated_by %s\n' % ( |
| entry['source'], entry['label']) |
| result += _NON_SDK_DEPS_ERROR_FOOTER |
| |
| elif category == 'bad_sdk_deps': |
| result += _BAD_SDK_DEPS_ERROR_HEADER |
| for entry in entries: |
| result += '- SDK_atom %s generated_by %s\n' % ( |
| entry['sdk_id'], entry['label']) |
| result += _BAD_SDK_DEPS_ERROR_FOOTER |
| |
| elif category == 'missing_sdk_deps': |
| result += _MISSING_SDK_DEPS_ERROR_HEADER |
| for entry in entries: |
| result += '- SDK_atom %s generated_by %s\n' % ( |
| entry['sdk_id'], entry['label']) |
| result += _MISSING_SDK_DEPS_ERROR_FOOTER |
| |
| else: |
| assert False, 'Unknown category: ' % category |
| |
| return result |
| |
| |
| def check_missing_files(runtime_files, atom_deps): |
| """Verify runtime requirements, and return a DependencyErrors instance.""" |
| errors = DependencyErrors() |
| |
| for entry in runtime_files: |
| gn_label = entry['label'] |
| sdk_id = entry.get('sdk_id') |
| source = entry.get('source') |
| |
| assert bool(sdk_id) != bool(source), ( |
| 'Exactly one of "sdk_id" or "source" must be defined in entry: %s' % |
| entry) |
| |
| if sdk_id: |
| # This runtime dependency is an SDK library, verify that it is part |
| # of the SDK manifest. |
| dep = atom_deps.get(sdk_id) |
| if not dep: |
| errors.add_missing_sdk_dependency(entry) |
| elif dep['type'] != 'cc_prebuilt_library': |
| errors.add_bad_sdk_dependency(entry) |
| elif source: |
| # Ignore sysroot libs |
| if os.path.basename(source) in _SYSROOT_LIBS: |
| continue |
| |
| # This runtime dependency is *not* an SDK library, this is an error. |
| errors.add_non_sdk_dependency(entry) |
| |
| return errors |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| parser.add_argument( |
| '--sdk-runtime-deps', |
| help='Path to the list of runtime deps.', |
| required=True) |
| parser.add_argument( |
| '--sdk-manifest', |
| help='Path to the target\'s SDK manifest file.', |
| required=True) |
| parser.add_argument( |
| '--stamp-file', help='Path to the output stamp file.', required=True) |
| args = parser.parse_args() |
| |
| with open(args.sdk_runtime_deps, 'r') as runtime_deps_file: |
| runtime_files = json.load(runtime_deps_file) |
| |
| # Read the list of package dependencies for the library's SDK incarnation. |
| with open(args.sdk_manifest, 'r') as manifest_file: |
| manifest = json.load(manifest_file) |
| |
| atom_id, deps = parse_sdk_manifest(manifest) |
| |
| # Find atom label with `_sdk_manifest($toolchain_suffix)` removed |
| atom_label, _, _ = deps[atom_id]['gn-label'].rpartition('_sdk_manifest(') |
| |
| # Check whether all runtime files are available for packaging. |
| errors = check_missing_files(runtime_files, deps) |
| if errors.has_error(): |
| print( |
| r''' |
| ERROR: When verifying runtime dependencies for the SDK atom: %s generated_by %s |
| |
| %s''' % (atom_id, atom_label, errors), |
| file=sys.stderr) |
| return 1 |
| |
| with open(args.stamp_file, 'w') as stamp: |
| stamp.write('Success!') |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |