blob: 0581954ba02598ff97044c823fb5cf9d750d1893 [file] [log] [blame]
#!/usr/bin/env python3.8
# Copyright 2021 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.
import argparse
import json
import os
import subprocess
import sys
from typing import List, Set, Tuple
from depfile import DepFile
from assembly import AssemblyInputBundle, AIBCreator, FilePath, PackageManifest, assembly_input_bundle
from serialization.serialization import json_load
def create_bundle(args: argparse.Namespace) -> None:
"""Create an Assembly Input Bundle (AIB).
"""
aib_creator = AIBCreator(args.outdir)
# Add the base and cache packages, if they exist.
if args.base_pkg_list:
add_pkg_list_from_file(aib_creator, args.base_pkg_list, "base")
if args.cache_pkg_list:
add_pkg_list_from_file(aib_creator, args.cache_pkg_list, "cache")
# Add any bootloaders.
if args.qemu_kernel:
aib_creator.qemu_kernel = args.qemu_kernel
# Create the AIB itself.
(assembly_input_bundle, assembly_config, deps) = aib_creator.build()
# Write out a dep file if one is requested.
if args.depfile:
DepFile.from_deps(assembly_config, deps).write_to(args.depfile)
# Write out a fini manifest of the files that have been copied, to create a
# package or archive that contains all of the files in the bundle.
if args.export_manifest:
assembly_input_bundle.write_fini_manifest(
args.export_manifest, base_dir=args.outdir)
def add_pkg_list_from_file(
aib_creator: AIBCreator, pkg_list_file, pkg_set_name: str):
pkg_set: Set = getattr(aib_creator, pkg_set_name)
try:
pkg_list = json.load(pkg_list_file)
except Exception as ex:
ex.args = (*ex.args, f"While parsing {pkg_list_file.name}")
raise
for pkg_manifest_path in pkg_list:
if pkg_manifest_path in pkg_set:
raise ValueError(
f"duplicate pkg manifest found: {pkg_manifest_path}")
pkg_set.add(pkg_manifest_path)
def generate_package_creation_manifest(args: argparse.Namespace) -> None:
"""Generate a package creation manifest for an Assembly Input Bundle (AIB)
Each AIB has a contents manifest that was created with it. This file lists
all of the files in the AIB, and their path within the build dir::
AIB/path/to/file_1=outdir/path/to/AIB/path/to/file_1
AIB/path/to/file_2=outdir/path/to/AIB/path/to/file_2
This format locates all the files in the AIB, relative to the
root_build_dir, and then gives their destination path within the AIB package
and archive.
To generate the package the AIB, a creation manifest is required (also in
FINI format). This is the same file, with the addition of the path to the
package metadata file::
meta/package=path/to/metadata/file
This fn generates the package metadata file, and then generates the creation
manifest file by appending the path to the metadata file to the entries in
the AIB contents manifest.
"""
meta_package_content = {'name': args.name, 'version': '0'}
json.dump(meta_package_content, args.meta_package)
contents_manifest = args.contents_manifest.read()
args.output.write(contents_manifest)
args.output.write("meta/package={}".format(args.meta_package.name))
def generate_archive(args: argparse.Namespace) -> None:
"""Generate an archive of an Assembly Input Bundle (AIB)
Each AIB has a contents manifest that was created with it. This file lists
all of the files in the AIB, and their path within the build dir::
AIB/path/to/file_1=outdir/path/to/AIB/path/to/file_1
AIB/path/to/file_2=outdir/path/to/AIB/path/to/file_2
This format locates all the files in the AIB, relative to the
root_build_dir, and then gives their destination path within the AIB package
and archive.
To generate the archive of the AIB, a creation manifest is required (also in
FINI format). This is the same file, with the addition of the path to the
package meta.far.
meta.far=path/to/meta.far
This fn generates the creation manifest, appending the package meta.far to
the contents manifest, and then calling the tarmaker tool to build the
archive itself, using the generated creation manifest.
"""
deps: Set[str] = set()
# Read the AIB's contents manifest, all of these files will be added to the
# creation manifest for the archive.
contents_manifest = args.contents_manifest.readlines()
deps.add(args.contents_manifest.name)
with open(args.creation_manifest, 'w') as creation_manifest:
if args.meta_far:
# Add the AIB's package meta.far to the creation manifest if one was
# provided.
creation_manifest.write("meta.far={}\n".format(args.meta_far))
# Add all files from the AIB's contents manifest.
for line in contents_manifest:
# Split out the lines so that a depfile for the action can be made
# from the contents_manifest's source paths.
src = line.split('=', 1)[1]
deps.add(src.strip())
creation_manifest.write(line)
# Build the archive itself.
cmd_args = [
args.tarmaker, "-manifest", args.creation_manifest, "-output",
args.output
]
subprocess.run(cmd_args, check=True)
if args.depfile:
DepFile.from_deps(args.output, deps).write_to(args.depfile)
def diff_bundles(args: argparse.Namespace) -> None:
first = AssemblyInputBundle.json_load(args.first)
second = AssemblyInputBundle.json_load(args.second)
result = first.difference(second)
if args.output:
result.json_dump(args.output)
else:
print(result)
def intersect_bundles(args: argparse.Namespace) -> None:
bundles = [AssemblyInputBundle.json_load(file) for file in args.bundles]
result = bundles[0]
for next_bundle in bundles[1:]:
result = result.intersection(next_bundle)
if args.output:
result.json_dump(args.output)
else:
print(result)
def find_blob(args: argparse.Namespace) -> None:
bundle = AssemblyInputBundle.json_load(args.bundle_config)
bundle_dir = os.path.dirname(args.bundle_config.name)
found_at: List[Tuple[FilePath, FilePath]] = []
for pkg_set in [bundle.base, bundle.cache, bundle.system]:
for pkg_manifest_path in pkg_set:
with open(os.path.join(bundle_dir, pkg_manifest_path),
'r') as pkg_manifest_file:
manifest = json_load(PackageManifest, pkg_manifest_file)
for blob in manifest.blobs:
if blob.merkle == args.blob:
found_at.append((pkg_manifest_path, blob.path))
if found_at:
pkg_header = "Package"
path_header = "Path"
pkg_column_width = max(
len(pkg_header), *[len(entry[0]) for entry in found_at])
path_column_width = max(
len(path_header), *[len(entry[1]) for entry in found_at])
formatter = f"{{0: <{pkg_column_width}}} | {{1: <{path_column_width}}}"
header = formatter.format(pkg_header, path_header)
print(header)
print("=" * len(header))
for pkg, path in found_at:
print(formatter.format(pkg, path))
def main():
parser = argparse.ArgumentParser(
description=
"Tool for creating Assembly Input Bundles in-tree, for use with out-of-tree assembly"
)
sub_parsers = parser.add_subparsers(
title="Commands",
description="Commands for working with Assembly Input Bundles")
###
#
# 'assembly_input_bundle_tool create' subcommand parser
#
bundle_creation_parser = sub_parsers.add_parser(
"create", help="Create an Assembly Input Bundle")
bundle_creation_parser.add_argument(
"--outdir",
required=True,
help="Path to the outdir that will contain the AIB")
bundle_creation_parser.add_argument(
"--base-pkg-list",
type=argparse.FileType('r'),
help=
"Path to a json list of package manifests for the 'base' package set")
bundle_creation_parser.add_argument(
"--cache-pkg-list",
type=argparse.FileType('r'),
help=
"Path to a json list of package manifests for the 'cache' package set")
bundle_creation_parser.add_argument(
"--qemu-kernel", help="Path to the qemu kernel")
bundle_creation_parser.add_argument(
"--depfile",
type=argparse.FileType('w'),
help="Path to write a dependency file to")
bundle_creation_parser.add_argument(
"--export-manifest",
type=argparse.FileType('w'),
help="Path to write a FINI manifest of the contents of the AIB")
bundle_creation_parser.set_defaults(handler=create_bundle)
###
#
# 'assembly_input_bundle_tool diff' subcommand parser
#
diff_bundles_parser = sub_parsers.add_parser(
"diff",
help=
"Calculate the difference between the first and second bundles (A-B).")
diff_bundles_parser.add_argument(
"first", help='The first bundle (A)', type=argparse.FileType('r'))
diff_bundles_parser.add_argument(
"second", help='The second bundle (B)', type=argparse.FileType('r'))
diff_bundles_parser.add_argument(
"--output",
help='A file to write the output to, instead of stdout.',
type=argparse.FileType('w'))
diff_bundles_parser.set_defaults(handler=diff_bundles)
###
#
# 'assembly_input_bundle_tool intersect' subcommand parser
#
intersect_bundles_parser = sub_parsers.add_parser(
"intersect", help="Calculate the intersection of the provided bundles.")
intersect_bundles_parser.add_argument(
"bundles",
nargs="+",
action="extend",
help='Paths to the bundle configs.',
type=argparse.FileType('r'))
intersect_bundles_parser.add_argument(
"--output",
help='A file to write the output to, instead of stdout.',
type=argparse.FileType('w'))
intersect_bundles_parser.set_defaults(handler=intersect_bundles)
###
#
# 'assembly_input_bundle_tool generate-package-creation-manifest' subcommand
# parser
#
package_creation_manifest_parser = sub_parsers.add_parser(
"generate-package-creation-manifest",
help=
"(build tool) Generate the creation manifest for the package that contains an Assembly Input Bundle."
)
package_creation_manifest_parser.add_argument(
"--contents-manifest", type=argparse.FileType('r'), required=True)
package_creation_manifest_parser.add_argument("--name", required=True)
package_creation_manifest_parser.add_argument(
"--meta-package", type=argparse.FileType('w'), required=True)
package_creation_manifest_parser.add_argument(
"--output", type=argparse.FileType('w'), required=True)
package_creation_manifest_parser.set_defaults(
handler=generate_package_creation_manifest)
###
#
# 'assembly_input_bundle_tool generate-archive' subcommand parser
#
archive_creation_parser = sub_parsers.add_parser(
"generate-archive",
help=
"(build tool) Generate the tarmaker creation manifest for the tgz that contains an Assembly Input Bundle."
)
archive_creation_parser.add_argument("--tarmaker", required=True)
archive_creation_parser.add_argument(
"--contents-manifest", type=argparse.FileType('r'), required=True)
archive_creation_parser.add_argument("--meta-far")
archive_creation_parser.add_argument("--creation-manifest", required=True)
archive_creation_parser.add_argument("--output", required=True)
archive_creation_parser.add_argument(
"--depfile", type=argparse.FileType('w'))
archive_creation_parser.set_defaults(handler=generate_archive)
###
#
# 'assembly_input_bundle_tool find-blob' subcommand parser
#
find_blob_parser = sub_parsers.add_parser(
"find-blob",
help=
"Find what causes a blob to be included in the Assembly Input Bundle.")
find_blob_parser.add_argument(
"--bundle-config",
required=True,
type=argparse.FileType('r'),
help="Path to the assembly_config.json for the bundle")
find_blob_parser.add_argument(
"--blob", required=True, help="Merkle of the blob to search for.")
find_blob_parser.set_defaults(handler=find_blob)
args = parser.parse_args()
if "handler" in args:
# Dispatch to the handler fn.
args.handler(args)
else:
# argparse doesn't seem to automatically catch that not subparser was
# called, and so if there isn't a handler function (which is set by
# having specified a subcommand), then just display usage instead of
# a cryptic KeyError.
parser.print_help()
if __name__ == "__main__":
sys.exit(main())