blob: 6014d6ce00cd5175c2739b0352e33974c4f1f66c [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# 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.
"""Generates the necessary files to provide an sdk_atom() target
for including a package in the SDK, and writes these to the output
directory. See `//build/sdk/sdk_atom.gni` for exact description of each file.
Files include:
* `file_path`
* `metadata`
Additionally, each package manifest has their `source_path` entries altered
such that both blobs and subpackage manifests point to their new SDK locations.
These final package manifests are also written to the output directory.
For use in sdk_fuchsia_package() template."""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Set
# SDK directory of blobs across all package manifests,
# each renamed to their merkle.
BLOBS_DIR = "packages/blobs"
# SDK directory of subpackage manifests, each renamed to their merkle.
SUBPACKAGE_MANIFEST_DIR = "packages/subpackage_manifests"
# sdk://
# └── packages/
# ├── blobs/
# │ └── CONTENT_HASH_1
# ├── PACKAGE_BAR/
# │ ├── VARIANT/
# │ │ └── release/
# │ │ └── package_manifest.json
# │ └── meta.json
# └── subpackage_manifests/
# └── META_FAR_MERKLE.package_manifest.json
# SDK directory of blobs, relative to each SDK package.
PACKAGE_DIR_TO_BLOBS_DIR = "../../../blobs"
# SDK directory of blobs, relative to `subpackage_manifests`.
SUBPACKAGE_MANIFESTS_DIR_TO_BLOBS_DIR = "../blobs"
# SDK directory of subpackage manifests, relative to individual SDK packages.
PACKAGE_DIR_TO_SUBPACKAGE_MANIFESTS_DIR = "../../../subpackage_manifests"
def handle_package_manifest(
output_dir: Path,
input_manifest_path: Path,
sdk_file_map: Set[str],
sdk_metadata: Dict[str, Any],
depfile_collection: Dict[Path, Path],
inputs: Dict[str, str],
visited_subpackages: Set[Path],
content_checklist_path: Path,
is_subpackage=False,
) -> Path:
"""
For the given `input_manifest_path`, does the following:
* Re-writes all source paths to be relative to SDK location,
where relative location is determined by if it is a subpackage.
* Adds files for inclusion to the `sdk_atom`'s `file_list`.
* Adds files and base package manifest to the `sdk_atom`'s
`metadata`.
* Adds files to `depfile`.
* Writes re-written package manifest to desired `output_dir` location.
Above is recursed across all subpackages.
Args:
output_dir: Path to build directory to write final package
manifest filepath to.
input_manifest_path: Path to package manifest to convert into
SDK-friendly format.
sdk_file_map: Set containing <dst>=<src> entries, for use in
`sdk_atom`'s `file_list`.
sdk_metadata: Object used to build a
`//build/sdk/meta/package.json` entry.
depfile_collection: Object constructed for use as a depfile.
inputs: Object containing `api_level`, `arch`, and
`distribution_name`. Used for constructing end
paths and other structures.
visited_subpackages: Set used for tracking levels of recursion in
subpackages.
content_checklist_path: Path to content_checklist file which will be
added to files of this variant.
is_subpackage: Boolean used to track if this iteration is
processing a subpackages. Used to determine if
`sdk_metadata` changes are required, as well as
pathing.
"""
api_level, arch, distribution_name = (
inputs["api_level"],
inputs["arch"],
inputs["distribution_name"],
)
subtype = f"{arch}-api-{api_level}"
with open(input_manifest_path, "r") as manifest_file:
input_manifest = json.load(manifest_file)
# Re-wire will be relative to package manifest location.
input_manifest["blob_sources_relative"] = "file"
# Subpackage manifests have different relative paths.
relative_blobs_dir = (
SUBPACKAGE_MANIFESTS_DIR_TO_BLOBS_DIR
if is_subpackage
else PACKAGE_DIR_TO_BLOBS_DIR
)
relative_subpackage_manifests_dir = (
"" if is_subpackage else PACKAGE_DIR_TO_SUBPACKAGE_MANIFESTS_DIR
)
target_files = [content_checklist_path]
# `meta_far_merkle` necessary for naming subpackage manifests.
meta_far_merkle = ""
for blob in input_manifest["blobs"]:
merkle, source_path = blob["merkle"], blob["source_path"]
relative_blob_path = f"{relative_blobs_dir}/{merkle}"
if blob["path"] == "meta/":
meta_far_merkle = merkle
# Re-wire source path to point to SDK blob store.
blob["source_path"] = relative_blob_path
sdk_file_map.add(f"{BLOBS_DIR}/{merkle}={source_path}")
target_files.append(f"{BLOBS_DIR}/{merkle}")
# Handle subpackages.
subpackage_list = input_manifest.get("subpackages", [])
for subpackage in subpackage_list:
subpackage_manifest_path, subpackage_merkle = (
subpackage["manifest_path"],
subpackage["merkle"],
)
# Re-wire subpackage manifest paths.
subpackage[
"manifest_path"
] = f"{relative_subpackage_manifests_dir}/{subpackage_merkle}"
if subpackage_manifest_path in visited_subpackages:
# No need to re-visit same subpackage multiple times.
continue
# Recursively handle subpackages.
target_files.append(
handle_package_manifest(
output_dir,
subpackage_manifest_path,
sdk_file_map,
sdk_metadata,
depfile_collection,
inputs,
visited_subpackages,
is_subpackage=True,
)
)
if is_subpackage:
manifest_file_name = f"{meta_far_merkle}.package_manifest.json"
sdk_output_manifest_path = (
f"{SUBPACKAGE_MANIFEST_DIR}/{manifest_file_name}"
)
visited_subpackages.add(input_manifest_path)
else:
manifest_file_name = "package_manifest.json"
sdk_output_manifest_path = f"packages/{distribution_name}/{subtype}/release/{manifest_file_name}"
target_files.append(sdk_output_manifest_path)
target_files = sorted(list(set(target_files)))
# Ensure metadata is aware of SDK manifest location.
sdk_metadata["variants"] = [
{
"manifest_file": sdk_output_manifest_path,
"arch": arch,
"api_level": api_level,
"files": target_files,
}
]
# Ensure package name matches distribution name.
input_manifest["package"]["name"] = distribution_name
build_output_manifest_path = f"{output_dir}/{manifest_file_name}"
sdk_file_map.add(f"{sdk_output_manifest_path}={build_output_manifest_path}")
# Write altered manifest to build output location.
with open(build_output_manifest_path, "w") as manifest_file:
json.dump(input_manifest, manifest_file, indent=2)
depfile_collection[build_output_manifest_path] = [input_manifest_path]
return sdk_output_manifest_path
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--distribution-name",
help="Name of package for publication in the SDK.",
required=True,
)
parser.add_argument(
"--manifest", help="Path to the package manifest.", required=True
)
parser.add_argument(
"--sdk-package-content-checklist-path",
help="Path to the SDK package content checklist file.",
required=True,
)
parser.add_argument(
"--output", help="Directory to write SDK objects to.", required=True
)
parser.add_argument("--api-level", help="Target API level.", required=True)
parser.add_argument(
"--target-cpu", help="Target build architecture.", required=True
)
parser.add_argument(
"--depfile", help="Path for generating depfile.", required=False
)
args = parser.parse_args()
# File containing file mappings. Each line in the file should contain a
# "dest=source" mapping, similarly to file scopes.
# See `sdk_atom` template definition of `file_list`.
sdk_file_map = set()
# Inputs used for directory naming.
api_level, arch, distribution_name = (
args.api_level,
args.target_cpu,
args.distribution_name,
)
inputs = {
"api_level": int(api_level),
"arch": arch,
"distribution_name": distribution_name,
}
# See `sdk_atom` template definition of `metadata`.
sdk_metadata = {
"name": distribution_name,
"variants": [],
"type": "package",
}
depfile_collection = {}
visited_subpackages = set()
# Capture content checklist file in metadata and file list.
subtype = f"{arch}-api-{api_level}"
content_checklist_path = (
f"packages/{distribution_name}/{subtype}/release/content_checklist_path"
)
sdk_file_map.add(
f"{content_checklist_path}={args.sdk_package_content_checklist_path}"
)
handle_package_manifest(
args.output,
args.manifest,
sdk_file_map,
sdk_metadata,
depfile_collection,
inputs,
visited_subpackages,
content_checklist_path,
is_subpackage=False,
)
# Write out sorted file list.
sdk_file_list = sorted(list(sdk_file_map))
sdk_file_list_path = os.path.join(args.output, "file_list.fini")
with open(sdk_file_list_path, "w") as out_file:
out_file.write("\n".join(sdk_file_list))
out_file.write("\n")
# Write out metadata.
metadata_path = os.path.join(args.output, "metadata")
with open(metadata_path, "w") as metadata_file:
json.dump(
sdk_metadata,
metadata_file,
indent=2,
sort_keys=True,
separators=(",", ": "),
)
# Write out depfile
if args.depfile:
os.makedirs(os.path.dirname(args.depfile), exist_ok=True)
with open(args.depfile, "w") as f:
for out_file in sorted(depfile_collection.keys()):
in_file_list = sorted(depfile_collection[out_file])
f.write(f"{out_file}: {' '.join(in_file_list)}")
return 0
if __name__ == "__main__":
sys.exit(main())