blob: b89df74875b3cb6a728ac52ea8beeefa4081dc85 [file] [log] [blame]
// 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.
use {
anyhow::Result,
assembly_package_list::{PackageList, PackageUrlList, WritablePackageList},
assembly_util::PackageDestination,
camino::{Utf8Path, Utf8PathBuf},
fuchsia_pkg::{PackageBuilder, PackageManifest, RelativeTo},
std::collections::BTreeMap,
};
/// The path to the static package index file in the `base` package.
const STATIC_PACKAGE_INDEX: &str = "data/static_packages";
/// The path to the cache package index file in the `base` package.
const CACHE_PACKAGE_INDEX: &str = "data/cache_packages.json";
/// A builder that constructs base packages.
#[derive(Default)]
pub struct BasePackageBuilder {
// Maps the blob destination -> source.
contents: BTreeMap<String, String>,
base_packages: PackageList,
cache_packages: PackageUrlList,
}
impl BasePackageBuilder {
/// Add all the blobs from `package` into the base package being built.
pub fn add_files_from_package(&mut self, package: PackageManifest) {
package.into_blobs().into_iter().filter(|b| b.path != "meta/").for_each(|b| {
self.contents.insert(b.path, b.source_path);
});
}
/// Add the `package` to the list of base packages, which is then added to
/// base package as file `data/static_packages`.
pub fn add_base_package(&mut self, package: PackageManifest) -> Result<()> {
self.base_packages.add_package(package)
}
/// Add the `package` to the list of cache packages, which is then added to
/// base package as file `data/cache_packages`.
pub fn add_cache_package(&mut self, package: PackageManifest) -> Result<()> {
self.cache_packages.add_package(package)
}
/// Build the base package and write the bytes to `out`.
///
/// Intermediate files will be written to the directory specified by
/// `gendir`.
pub fn build(
self,
outdir: impl AsRef<Utf8Path>,
gendir: impl AsRef<Utf8Path>,
name: impl AsRef<str>,
out: impl AsRef<Utf8Path>,
) -> Result<BasePackageBuildResults> {
let outdir = outdir.as_ref();
let gendir = gendir.as_ref();
let out = out.as_ref();
// Write all generated files in a subdir with the name of the package.
let gendir = gendir.join(name.as_ref());
let Self { contents, base_packages, cache_packages } = self;
// Capture the generated files
let mut generated_files = BTreeMap::new();
// Generate the base and cache package lists.
let (dest, path) = base_packages.write_index_file(&gendir, "base", STATIC_PACKAGE_INDEX)?;
generated_files.insert(dest, path);
let (dest, path) =
cache_packages.write_index_file(&gendir, "cache", CACHE_PACKAGE_INDEX)?;
generated_files.insert(dest, path);
// Construct the list of blobs in the base package that lives outside of the meta.far.
let mut external_contents = contents;
for (destination, source) in &generated_files {
external_contents.insert(destination.clone(), source.clone());
}
// It's not totally clear what the ABI revision means for the
// system-image package. It isn't checked anywhere. Regardless, it's
// never produced by assembly tools from one Fuchsia release and then
// read by binaries from another Fuchsia release, so the ABI revision
// for platform components seems appropriate.
//
// TODO(https://fxbug.dev/329125882): Clarify what this means.
//
// Also: all base packages are named 'system-image' internally, for
// consistency on the platform.
let mut builder =
PackageBuilder::new_platform_internal_package(PackageDestination::Base.to_string());
// However, they can have different published names. And the name here
// is the name to publish it under (and to include in the generated
// package manifest).
builder.published_name(name);
for (destination, source) in &external_contents {
builder.add_file_as_blob(destination, source)?;
}
let manifest_path = outdir.join("package_manifest.json");
builder.manifest_path(manifest_path.clone());
builder.manifest_blobs_relative_to(RelativeTo::File);
builder.build(gendir.as_std_path(), out.as_std_path())?;
Ok(BasePackageBuildResults {
contents: external_contents,
base_packages,
cache_packages,
generated_files,
manifest_path: manifest_path.clone(),
})
}
}
/// The results of building the `base` package.
///
/// These are based on the information that the builder is configured with, and
/// then augmented with the operations that the `BasePackageBuilder::build()`
/// fn performs, including an extra additions or removals.
///
/// This provides an audit trail of "what was created".
pub struct BasePackageBuildResults {
// Maps the blob destination -> source.
pub contents: BTreeMap<String, String>,
pub base_packages: PackageList,
pub cache_packages: PackageUrlList,
/// The paths to the files generated by the builder.
pub generated_files: BTreeMap<String, String>,
pub manifest_path: Utf8PathBuf,
}
#[cfg(test)]
mod tests {
use super::*;
use assembly_test_util::generate_test_manifest;
use fuchsia_archive::Utf8Reader;
use fuchsia_hash::Hash;
use fuchsia_url::PinnedAbsolutePackageUrl;
use std::fs::File;
use tempfile::{NamedTempFile, TempDir};
#[test]
fn build_with_unsupported_packages() {
let mut builder = BasePackageBuilder::default();
assert!(builder.add_base_package(generate_test_manifest("system_image", None)).is_err());
assert!(builder.add_base_package(generate_test_manifest("update", None)).is_err());
}
#[test]
fn build() {
let outdir_tmp = TempDir::new().unwrap();
let outdir = Utf8Path::from_path(outdir_tmp.path()).unwrap();
let far_path = outdir.join("base.far");
// Build the base package with an extra file, a base package, and a cache package.
let mut builder = BasePackageBuilder::default();
let test_file = NamedTempFile::new().unwrap();
builder.add_files_from_package(generate_test_manifest("package", Some(test_file.path())));
builder.add_base_package(generate_test_manifest("base_package", None)).unwrap();
builder.add_cache_package(generate_test_manifest("cache_package", None)).unwrap();
let gendir_tmp = TempDir::new().unwrap();
let gendir = Utf8Path::from_path(gendir_tmp.path()).unwrap();
let build_results = builder.build(&outdir, &gendir, "system_image", &far_path).unwrap();
// The following asserts lead up to the final one, catching earlier failure points where it
// can be more obvious as to why the test is failing, as the hashes themselves are opaque.
// Verify the package list intermediate structures.
assert_eq!(
vec![("base_package/0".to_string(), Hash::from([0u8; 32]))],
build_results.base_packages
);
let url: PinnedAbsolutePackageUrl = "fuchsia-pkg://testrepository.com/cache_package/0\
?hash=0000000000000000000000000000000000000000000000000000000000000000"
.parse()
.unwrap();
assert_eq!(vec![&url], build_results.cache_packages.get_packages());
// Inspect the generated files to verify their contents.
let gen_static_index = build_results.generated_files.get("data/static_packages").unwrap();
assert_eq!(
"base_package/0=0000000000000000000000000000000000000000000000000000000000000000\n",
std::fs::read_to_string(gen_static_index).unwrap()
);
let gen_cache_index =
build_results.generated_files.get("data/cache_packages.json").unwrap();
let cache_packages_json = r#"{"content":["fuchsia-pkg://testrepository.com/cache_package/0?hash=0000000000000000000000000000000000000000000000000000000000000000"],"version":"1"}"#;
assert_eq!(cache_packages_json, std::fs::read_to_string(gen_cache_index).unwrap());
// Validate that the generated files are in the contents.
for (generated_file, _) in &build_results.generated_files {
assert!(
build_results.contents.contains_key(generated_file),
"Unable to find generated file in base package contents: {}",
generated_file
);
}
// Read the output and ensure it contains the right files (and their hashes)
let mut far_reader = Utf8Reader::new(File::open(&far_path).unwrap()).unwrap();
let package = far_reader.read_file("meta/package").unwrap();
assert_eq!(br#"{"name":"system_image","version":"0"}"#, &*package);
let contents = far_reader.read_file("meta/contents").unwrap();
let contents = std::str::from_utf8(&contents).unwrap();
let expected_contents = "\
data/cache_packages.json=49d59d7e9567de7ce2d5fc8632ea544965402426a8fa66456fbd68dccca36b4c\n\
data/file.txt=15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b\n\
data/static_packages=2d86ccb37d003499bdc7bdd933428f4b83d9ed224d1b64ad90dc257d22cff460\n\
"
.to_string();
assert_eq!(expected_contents, contents);
}
}