blob: dfd28681507fe0116a108ca46b381a12605b6c63 [file] [log] [blame]
// Copyright 2022 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.
//! Command to create a Product Bundle Metadata (PBM) file from command
//! line args pass in through `CreateCommand`.
use {
anyhow::Result,
errors::ffx_error,
ffx_product_args::{CreateCommand, ProductBundleType},
fs_extra::dir::CopyOptions,
sdk_metadata::{
ElementType, EmuManifest, Envelope, FlashManifest, ImageBundle, Manifests, MetadataValue,
PackageBundle, ProductBundleV1,
},
serde::{Deserialize, Serialize},
std::fs::{create_dir_all, File, OpenOptions},
std::io::BufReader,
};
/// Description of the build info. This is part of product-bundle metadata.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BuildInfo {
pub product: String,
pub board: String,
pub version: String,
pub is_debug: bool,
}
/// Write the product_bundle.json file to the out directory.
/// You can choose to generate the product bundle for emulator or flash by
/// passing in is_emu flag.
pub async fn create_product_bundle(cmd: &CreateCommand) -> Result<()> {
let build_info: BuildInfo =
File::open(cmd.build_info.clone()).map(BufReader::new).map(serde_json::from_reader)??;
let description = Some(
format!("Product bundle for {}.{}", &build_info.product, &build_info.board).to_owned(),
);
let name = format!("{}.{}", &build_info.product, &build_info.board).to_owned();
let device_refs = vec![cmd.device_name.clone()];
let images_vec =
vec![ImageBundle { base_uri: "file:/images".to_owned(), format: "files".to_owned() }];
let packages_vec = vec![PackageBundle {
repo_uri: "file:/packages".to_owned(),
format: "files".to_owned(),
blob_uri: None,
}];
let metadata = vec![
("build_info_board".to_owned(), MetadataValue::StringValue(build_info.board.clone())),
("build_info_product".to_owned(), MetadataValue::StringValue(build_info.product.clone())),
("build_info_version".to_owned(), MetadataValue::StringValue(build_info.version.clone())),
("is_debug".to_owned(), MetadataValue::Boolean(build_info.is_debug.clone())),
];
let mut manifests = Manifests::default();
if cmd.types.contains(ProductBundleType::EMU) {
manifests.emu = Some(EmuManifest {
disk_images: vec!["fvm.blk".to_owned()],
initial_ramdisk: "fuchsia.zbi".to_owned(),
kernel: "multiboot.bin".to_owned(),
});
}
if cmd.types.contains(ProductBundleType::FLASH) {
manifests.flash = Some(create_product_bundle_for_flash(&cmd.flash_manifest)?);
};
let product_bundle = ProductBundleV1 {
description,
name: name.clone(),
device_refs,
images: images_vec,
packages: packages_vec,
manifests: Some(manifests),
metadata: Some(metadata),
kind: ElementType::ProductBundle,
};
let product_dir = cmd.out.join(&name);
let image_dir = product_dir.join("image");
let package_dir = product_dir.join("packages");
create_dir_all(&image_dir)?;
create_dir_all(&package_dir)?;
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(cmd.out.join("product_bundle.json"))
.map_err(|e| {
ffx_error!(r#"Cannot create product bundle file "{}": {}"#, cmd.out.display(), e)
})?;
let envelope = Envelope::<ProductBundleV1>::from(product_bundle)?;
serde_json::to_writer_pretty(&file, &envelope)?;
// Copy images and package directory to the output directory.
let options = CopyOptions { copy_inside: true, ..CopyOptions::new() };
fs_extra::copy_items(&vec![&cmd.images], &image_dir, &options)?;
fs_extra::copy_items(&vec![&cmd.packages], &package_dir, &options)?;
fs_extra::copy_items(&vec![cmd.multiboot_bin.clone()], &image_dir, &options)?;
Ok(())
}
fn create_product_bundle_for_flash(flash_manifest: &str) -> Result<FlashManifest> {
let flash: FlashManifest =
File::open(flash_manifest).map(BufReader::new).map(serde_json::from_reader)??;
Ok(flash)
}
#[cfg(test)]
mod test {
use super::*;
use ffx_product_args::ProductBundleTypes;
use std::io::Write;
use std::path::PathBuf;
fn write_file(path: PathBuf, body: &[u8]) {
let mut tmp = tempfile::NamedTempFile::new().unwrap();
tmp.write_all(body).unwrap();
tmp.persist(path).unwrap();
}
fn create_build_info(build_info_path: PathBuf) {
let data = r#"
{
"product":"workstation-oot",
"board": "x64",
"version": "6.0.0.1",
"is_debug": false
}
"#;
write_file(build_info_path, data.as_bytes());
}
fn create_flash_manifest(flash_manifest: PathBuf) {
let data = r#"
{
"hw_revision": "x64",
"products": [{
"bootloader_partitions": [],
"name": "fuchsia",
"oem_files": [],
"partitions": [
{
"name": "",
"path": "fuchsia.zbi"
},
{
"name": "",
"path": "zedboot.zbi"
},
{
"name": "",
"path": "fuchsia.vbmeta"
},
{
"name": "",
"path": "zedboot.vbmeta"
}
]}
]
}
"#;
write_file(flash_manifest, data.as_bytes());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_pbm_for_emu() {
let tempdir = tempfile::tempdir().unwrap();
let root = tempdir.path();
let out_dir = root.join("out");
let build_info_path = root.join("build_info.json");
create_build_info(build_info_path.clone());
let images = root.join("images");
let packages = root.join("amber-files");
create_dir_all(&images).unwrap();
create_dir_all(&packages).unwrap();
let multiboot_bin = images.join("multiboot.bin");
write_file(multiboot_bin.clone(), "".as_bytes());
let cmd = CreateCommand {
types: ProductBundleTypes { types: vec![ProductBundleType::EMU] },
packages: packages.into_os_string().into_string().unwrap(),
images: images.into_os_string().into_string().unwrap(),
multiboot_bin: multiboot_bin.into_os_string().into_string().unwrap(),
device_name: String::from("x64"),
build_info: build_info_path.into_os_string().into_string().unwrap(),
flash_manifest: String::from(""),
out: out_dir.clone(),
};
create_product_bundle(&cmd).await.unwrap();
let envelope: Envelope<ProductBundleV1> = File::open(&out_dir.join("product_bundle.json"))
.map(BufReader::new)
.map(serde_json::from_reader)
.unwrap()
.unwrap();
let product_bundle: ProductBundleV1 = envelope.data;
assert_eq!("Product bundle for workstation-oot.x64", product_bundle.description.unwrap());
assert_eq!("workstation-oot.x64", product_bundle.name);
assert!(product_bundle.manifests.clone().unwrap().emu.is_some());
assert!(product_bundle.manifests.clone().unwrap().flash.is_none());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_pbm_for_flash() {
let tempdir = tempfile::tempdir().unwrap();
let root = tempdir.path();
let out_dir = root.join("out");
let images = root.join("images");
let packages = root.join("amber-files");
create_dir_all(&images).unwrap();
create_dir_all(&packages).unwrap();
let multiboot_bin = images.join("multiboot.bin");
write_file(multiboot_bin.clone(), "".as_bytes());
let flash_manifest_path = root.join("flash_manifest.json");
create_flash_manifest(flash_manifest_path.clone());
let build_info_path = root.join("build_info.json");
create_build_info(build_info_path.clone());
let cmd = CreateCommand {
types: ProductBundleTypes { types: vec![ProductBundleType::FLASH] },
packages: packages.into_os_string().into_string().unwrap(),
images: images.into_os_string().into_string().unwrap(),
multiboot_bin: multiboot_bin.into_os_string().into_string().unwrap(),
device_name: String::from("x64"),
build_info: build_info_path.into_os_string().into_string().unwrap(),
flash_manifest: flash_manifest_path.into_os_string().into_string().unwrap(),
out: out_dir.clone(),
};
create_product_bundle(&cmd).await.unwrap();
let envelope: Envelope<ProductBundleV1> = File::open(&out_dir.join("product_bundle.json"))
.map(BufReader::new)
.map(serde_json::from_reader)
.unwrap()
.unwrap();
let product_bundle: ProductBundleV1 = envelope.data;
assert_eq!("Product bundle for workstation-oot.x64", product_bundle.description.unwrap());
assert_eq!("workstation-oot.x64", product_bundle.name);
assert!(product_bundle.manifests.clone().unwrap().flash.is_some());
assert!(product_bundle.manifests.clone().unwrap().emu.is_none());
}
}