blob: 6c91662ef7033219f21c7c2fbaefdcded98e7a67 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 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.
"""Copy assembled system outputs by dynamically parsing assembled_system.json."""
import argparse
import json
import os
import shutil
import sys
from pathlib import Path
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--manifest",
type=Path,
required=True,
help="Path to assembled_system.json",
)
parser.add_argument(
"--bazel-dir",
type=Path,
required=True,
help="Path to Bazel output directory",
)
parser.add_argument(
"--ninja-dir",
type=Path,
required=True,
help="Path to Ninja output directory",
)
parser.add_argument(
"--fxfs",
help="Copy the Fxfs block image using this destination filename",
)
parser.add_argument(
"--fvm",
help="Copy the FVM block image using this destination filename",
)
parser.add_argument(
"--vbmeta",
help="Copy the vbmeta image using this destination filename",
)
args = parser.parse_args()
if not args.manifest.exists():
raise FileNotFoundError(f"Manifest {args.manifest} not found.")
with open(args.manifest, "r") as f:
manifest = json.load(f)
images = manifest.get("images", [])
if args.ninja_dir.exists():
shutil.rmtree(args.ninja_dir)
args.ninja_dir.mkdir(parents=True, exist_ok=True)
# Always copy the manifest itself to the root of the Ninja directory
shutil.copy2(args.manifest, args.ninja_dir / "assembled_system.json")
# 1. The ZBI is always present, always find and copy it.
zbi_path = find_image(images, "zbi")
if not zbi_path:
raise ValueError("ZBI image not found in manifest.")
copy_image_file(zbi_path, "fuchsia.zbi", args.bazel_dir, args.ninja_dir)
# 2. If fxfs is specified, find and copy it.
if args.fxfs:
fxfs_path = find_image(images, "blk", "fxfs")
if not fxfs_path:
raise ValueError("Fxfs image requested but not found in manifest.")
copy_image_file(fxfs_path, args.fxfs, args.bazel_dir, args.ninja_dir)
# 3. If fvm is specified, find and copy it.
if args.fvm:
fvm_path = find_image(images, "blk", "fvm")
if not fvm_path:
raise ValueError("FVM image requested but not found in manifest.")
copy_image_file(fvm_path, args.fvm, args.bazel_dir, args.ninja_dir)
# 4. If vbmeta is specified, find and copy it.
if args.vbmeta:
vbmeta_path = find_image(images, "vbmeta")
if not vbmeta_path:
raise ValueError(
"VBMeta image requested but not found in manifest."
)
copy_image_file(
vbmeta_path, args.vbmeta, args.bazel_dir, args.ninja_dir
)
return 0
def find_image(
images: list[dict[str, str]],
img_type: str,
name_hint: str | None = None,
) -> Path | None:
for img in images:
t = img.get("type")
path = img.get("path")
if t == img_type and path is not None:
if name_hint:
if name_hint in path:
return Path(path)
else:
return Path(path)
return None
def copy_image_file(
img_path: Path, dest_name: str, bazel_dir: Path, ninja_dir: Path
) -> None:
src_file: Path = bazel_dir / img_path
dst_file: Path = ninja_dir / dest_name
try:
# Use hardlink for zero-overhead copy
os.link(src_file, dst_file)
except Exception as e:
raise RuntimeError(
f"Failed to link {src_file} -> {dst_file}: {e}"
) from e
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)