blob: d4172917f519548e1cc1f12103f93f404a620d35 [file] [log] [blame]
#!/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 = ['', '']
def parse_sdk_manifest(manifest):
'''Parse SDK manifest file and extract atom id and dependencies.
manifest: A directionary representation of the JSON SDK manifest.
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:
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:
_MISSING_SDK_DEPS_ERROR_HEADER = r'''## No dependency generates SDK runtime requirement:
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:
for category, entries in errors.items():
if category == 'non_sdk_deps':
for entry in entries:
result += '- %s generated_by %s\n' % (
entry['source'], entry['label'])
elif category == 'bad_sdk_deps':
for entry in entries:
result += '- SDK_atom %s generated_by %s\n' % (
entry['sdk_id'], entry['label'])
elif category == 'missing_sdk_deps':
for entry in entries:
result += '- SDK_atom %s generated_by %s\n' % (
entry['sdk_id'], entry['label'])
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' %
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:
elif dep['type'] != 'cc_prebuilt_library':
elif source:
# Ignore sysroot libs
if os.path.basename(source) in _SYSROOT_LIBS:
# This runtime dependency is *not* an SDK library, this is an error.
return errors
def main():
parser = argparse.ArgumentParser(
help='Path to the list of runtime deps.',
help='Path to the target\'s SDK manifest file.',
'--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():
ERROR: When verifying runtime dependencies for the SDK atom: %s generated_by %s
%s''' % (atom_id, atom_label, errors),
return 1
with open(args.stamp_file, 'w') as stamp:
return 0
if __name__ == '__main__':