blob: da3775b6999b4ebe9e86151f417204ae083a1cda [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2025 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.
"""Wraps the component manifest creation process with a script that combines
each of the separate steps into a single python process.
"""
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
from depfile import DepFile
def check_expected_includes(
expected_includes: Path,
manifest_path_file: Path,
cmc: Path,
includeroot: Path,
includepaths: list[Path],
depfile: DepFile,
expected_includes_depfile: Path,
) -> int:
with open(expected_includes) as expected_includes_file:
if expected_includes_file.read() == "":
# nothing to expect, exit early
return 0
with open(manifest_path_file) as f:
manifest_path_json = json.load(f)
if len(manifest_path_json) != 1:
print(
"ERROR: --manifest-path-file must be a json list containing a single file path",
file=sys.stderr,
)
return -2
manifest_path: str = manifest_path_json[0]
expected_includes_cmd: list[str | Path] = [
cmc,
"--stamp",
"/dev/null",
"check-includes",
manifest_path,
"--fromfile",
expected_includes,
"--depfile",
expected_includes_depfile,
"--includeroot",
includeroot,
]
for p in includepaths:
expected_includes_cmd += ["--includepath", p]
expected_includes_result = subprocess.run(expected_includes_cmd)
if expected_includes_result.returncode != 0:
return expected_includes_result.returncode
# Read the depfile written by cmc, and add its inputs to our own depfile's inputs
# This depfile needs to be added to the action's implicit outputs, for action_tracer.py
# to see
depfile.add_output(expected_includes_depfile)
with open(expected_includes_depfile) as file:
depfile.update(DepFile.read_from(file).deps)
return 0
def check_references(
check_references_dist_manifest: Path,
check_references_fini_manifest: Path,
cmc: Path,
compiled_manifest: Path,
label: str,
depfile: DepFile,
) -> int:
import distribution_manifest
# It needs to be recursively expanded and deduplicated in order to make
# it into a list of files that
depfile.add_input(str(check_references_dist_manifest))
with open(check_references_dist_manifest) as f:
dist_manifest_json = json.load(f)
inputs: set[str] = set()
entries, error = distribution_manifest.expand_manifest(
dist_manifest_json, inputs
)
depfile.update(inputs)
# Write out a package creation manifest for the references, to use with CMC
# This file needs to be added to the action's implicit outputs, for action_tracer.py
# to see
depfile.add_output(check_references_fini_manifest)
with open(check_references_fini_manifest, "w") as fini_manifest:
for entry in sorted(entries, key=lambda x: x.destination):
fini_manifest.write(f"{entry.destination}={entry.source}\n")
check_references_result = subprocess.run(
[
cmc,
"validate-references",
"--component-manifest",
compiled_manifest,
# This is actually a "package creation manifest", or a "fini" manifest
"--package-manifest",
check_references_fini_manifest,
"--context",
label,
]
)
if check_references_result.returncode != 0:
return check_references_result.returncode
return 0
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--label", required=True, help="GN label of this action"
)
parser.add_argument(
"--compiled-manifest",
type=Path,
required=True,
help="Path of the compiled manifest being validated",
)
parser.add_argument(
"--output",
type=Path,
required=True,
help="Destination path for the validated compiled manifest",
)
parser.add_argument(
"--depfile", type=Path, help="Path to a depfile to write when done"
)
parser.add_argument(
"--cmc",
type=Path,
help="Path to the 'cmc' tool",
)
parser.add_argument(
"--manifest-path-file",
type=Path,
help="Path to a file that contains the path of the CML source file for the compiled manifest",
)
parser.add_argument(
"--expected-includes",
type=Path,
help="Path to a file containing a list of cml files that are expected to be included by '--manifest-path-file'",
)
parser.add_argument(
"--includeroot",
help="Path to root of includes.",
type=Path,
)
parser.add_argument(
"--includepath",
help="Additional path for relative includes.",
type=Path,
nargs="+",
)
parser.add_argument(
"--check-references-dist-manifest",
type=Path,
help="Input distribution manifest used to check includes",
)
parser.add_argument(
"--check-references-fini-manifest",
type=Path,
help="Intermediate fini manifest to produce when checking references",
)
args = parser.parse_args()
# Some error-checking on provided arguments just to help make it a bit more clear when
# something is mishandled.
if (
args.expected_includes
or (
args.check_references_dist_manifest
or args.check_references_fini_manifest
)
) and not args.cmc:
if not args.cmc:
print(
"ERROR: --cmc must be provided with --expect-includes and --check-manifest-*",
file=sys.stderr,
)
return -1
if bool(args.check_references_dist_manifest) ^ bool(
args.check_references_fini_manifest
):
print(
"ERROR: need both or neither of --check-references-dist-manifest and --check-references-fini-manifest",
file=sys.stderr,
)
return -1
# Setup a depfile for the action
depfile = DepFile(args.output)
# If requested, validate that the cml source file includes all the expected cml shards that
# the binary's libraries require.
if args.expected_includes:
if args.manifest_path_file:
rc = check_expected_includes(
args.expected_includes,
args.manifest_path_file,
args.cmc,
args.includeroot,
args.includepath,
depfile,
args.output.parent / f"{args.output.name}.d",
)
if rc != 0:
return rc
else:
print(
"ERROR: --manifest-path-file must be provided with --expected-includes",
file=sys.stderr,
)
return -1
# If requested, check that the files reference by the compiled component are all
# included as distribution entries of this component.
if args.check_references_dist_manifest:
rc = check_references(
args.check_references_dist_manifest,
args.check_references_fini_manifest,
args.cmc,
args.compiled_manifest,
args.label,
depfile,
)
if rc != 0:
return rc
# After validation, copy the input manifest to the output location to signify completion
if args.output.exists():
args.output.unlink()
shutil.copy(args.compiled_manifest, args.output)
depfile.add_input(args.compiled_manifest)
# And write out the depfile
with open(args.depfile, "w") as f:
depfile.write_to(f)
return 0
if __name__ == "__main__":
sys.exit(main())