blob: 9c518dd5c569d603a58ef4ca6bc7ae0a6eec4514 [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 crate::operations::product::assembly_builder::ImageAssemblyConfigBuilder;
use anyhow::{bail, Context, Result};
use assembly_config_schema::assembly_config::{
AdditionalPackageContents, CompiledPackageDefinition,
};
use assembly_config_schema::developer_overrides::DeveloperOverrides;
use assembly_config_schema::{
AssemblyConfig, BoardInformation, BoardInputBundle, FeatureSupportLevel,
};
use assembly_file_relative_path::SupportsFileRelativePaths;
use assembly_images_config::ImagesConfig;
use assembly_tool::SdkToolProvider;
use assembly_util::{read_config, BlobfsCompiledPackageDestination, CompiledPackageDestination};
use camino::Utf8PathBuf;
use ffx_assembly_args::{PackageMode, PackageValidationHandling, ProductArgs};
use std::collections::BTreeMap;
use tracing::info;
mod assembly_builder;
pub fn assemble(args: ProductArgs) -> Result<()> {
let ProductArgs {
product,
board_info,
outdir,
gendir: _,
input_bundles_dir,
legacy_bundle,
additional_packages_path,
package_validation,
mode,
custom_kernel_aib,
developer_overrides,
} = args;
let developer_overrides = if let Some(overrides_path) = developer_overrides {
let developer_overrides =
read_config(&overrides_path).context("Reading developer overrides")?;
print_developer_overrides_banner(&developer_overrides, &overrides_path);
Some(developer_overrides)
} else {
None
};
info!("Reading configuration files.");
info!(" product: {}", product);
let config: AssemblyConfig = read_config(&product).context("Reading product configuration")?;
let board_info_path = board_info;
let board_info = read_config::<BoardInformation>(&board_info_path)
.context("Reading board information")?
// and then resolve the file-relative paths to be relative to the cwd instead.
.resolve_paths_from_file(&board_info_path)
.context("Resolving paths in board configuration.")?;
// Parse the board's Board Input Bundles, if it has them, and merge their
// configuration fields into that of the board_info struct.
let mut board_input_bundles = Vec::new();
for bundle_path in &board_info.input_bundles {
let bundle_path = bundle_path.as_utf8_pathbuf().join("board_input_bundle.json");
let bundle = read_config::<BoardInputBundle>(&bundle_path)
.with_context(|| format!("Reading board input bundle: {bundle_path}"))?;
let bundle = bundle
.resolve_paths_from_file(&bundle_path)
.with_context(|| format!("resolving paths in board input bundle: {bundle_path}"))?;
board_input_bundles.push((bundle_path, bundle));
}
let board_input_bundles = board_input_bundles;
// Find the Board Input Bundle that's providing the configuration files, by first finding _all_
// structs that aren't None, and then verifying that we only have one of them. This is perhaps
// more complicated than strictly necessary to get that struct, because it collects all paths to
// the bundles that are providing a Some() value, and reporting them all in the error.
let board_configuration_files = board_input_bundles
.iter()
.filter_map(
|(path, bib)| if let Some(cfg) = &bib.configuration { Some((path, cfg)) } else { None },
)
.collect::<Vec<_>>();
let board_provided_config = if board_configuration_files.len() > 1 {
let paths = board_configuration_files
.iter()
.map(|(path, _)| format!(" - {path}"))
.collect::<Vec<_>>();
let paths = paths.join("\n");
bail!("Only one board input bundle can provide configuration files, found: \n{paths}");
} else {
board_configuration_files.first().map(|(_, cfg)| (*cfg).clone()).unwrap_or_default()
};
// Replace board_info with a new one that swaps its empty 'configuraton' field
// for the consolidated one created from the board's input bundles.
let board_info = BoardInformation { configuration: board_provided_config, ..board_info };
// Get platform configuration based on the AssemblyConfig and the BoardInformation.
let ramdisk_image = mode == PackageMode::DiskImageInZbi;
let resource_dir = input_bundles_dir.join("resources");
let configuration = assembly_platform_configuration::define_configuration(
&config,
&board_info,
ramdisk_image,
&outdir,
&resource_dir,
)?;
// Now that all the configuration has been determined, create the builder
// and start doing the work of creating the image assembly config.
let mut builder = ImageAssemblyConfigBuilder::new(config.platform.build_type.clone());
// Set the developer overrides, if any.
if let Some(developer_overrides) = developer_overrides {
builder
.add_developer_overrides(developer_overrides)
.context("Setting developer overrides")?;
}
// Add the special platform AIB for the zircon kernel, or if provided, an
// AIB that contains a custom kernel to use instead.
let kernel_aib_path = match custom_kernel_aib {
None => make_bundle_path(&input_bundles_dir, "zircon"),
Some(custom_kernel_aib_path) => custom_kernel_aib_path,
};
builder
.add_bundle(&kernel_aib_path)
.with_context(|| format!("Adding kernel input bundle ({kernel_aib_path})"))?;
// Set the info used for BoardDriver arguments.
builder
.set_board_driver_arguments(&board_info)
.context("Setting arguments for the Board Driver")?;
// Set the configuration for the rest of the packages.
for (package, config) in configuration.package_configs {
builder.set_package_config(package, config)?;
}
// Add the kernel cmdline arguments
builder.add_kernel_args(configuration.kernel_args)?;
// Add the domain config packages.
for (package, config) in configuration.domain_configs {
builder.add_domain_config(package, config)?;
}
// Add the configuration capabilities.
builder.add_configuration_capabilities(configuration.configuration_capabilities)?;
// Add the board's Board Input Bundles, if it has them.
for (bundle_path, bundle) in board_input_bundles {
builder
.add_board_input_bundle(
bundle,
config.platform.feature_set_level == FeatureSupportLevel::Bootstrap,
)
.with_context(|| format!("Adding board input bundle from: {bundle_path}"))?;
}
// Add the platform Assembly Input Bundles that were chosen by the configuration.
for platform_bundle_name in &configuration.bundles {
let platform_bundle_path = make_bundle_path(&input_bundles_dir, platform_bundle_name);
builder.add_bundle(&platform_bundle_path).with_context(|| {
format!("Adding platform bundle {platform_bundle_name} ({platform_bundle_path})")
})?;
}
// Add the core shards.
if !configuration.core_shards.is_empty() {
let compiled_package_def =
CompiledPackageDefinition::Additional(AdditionalPackageContents {
name: CompiledPackageDestination::Blob(BlobfsCompiledPackageDestination::Core),
component_shards: BTreeMap::from([(
"core".to_string(),
configuration.core_shards.clone(),
)]),
});
builder
.add_compiled_package(&compiled_package_def, "".into())
.context("Adding core shards")?;
}
// Add the legacy bundle.
let legacy_bundle_path = legacy_bundle.join("assembly_config.json");
builder
.add_bundle(&legacy_bundle_path)
.context(format!("Adding legacy bundle: {legacy_bundle_path}"))?;
// Add the bootfs files.
builder.add_bootfs_files(&configuration.bootfs.files).context("Adding bootfs files")?;
// Add product-specified packages and configuration
builder
.add_product_packages(config.product.packages)
.context("Adding product-provided packages")?;
builder
.add_product_base_drivers(config.product.base_drivers)
.context("Adding product-provided base-drivers")?;
if let Some(package_config_path) = additional_packages_path {
let additional_packages =
read_config(package_config_path).context("Reading additional package config")?;
builder.add_product_packages(additional_packages).context("Adding additional packages")?;
}
// Get the tool set.
let tools = SdkToolProvider::try_new()?;
// Serialize the builder state for forensic use.
let builder_forensics_file_path = outdir.join("assembly_builder_forensics.json");
let board_forensics_file_path = outdir.join("board_configuration_forensics.json");
if let Some(parent_dir) = builder_forensics_file_path.parent() {
std::fs::create_dir_all(parent_dir)
.with_context(|| format!("unable to create outdir: {outdir}"))?;
}
let builder_forensics_file =
std::fs::File::create(&builder_forensics_file_path).with_context(|| {
format!("Failed to create builder forensics files: {builder_forensics_file_path}")
})?;
serde_json::to_writer_pretty(builder_forensics_file, &builder).with_context(|| {
format!("Writing builder forensics file to: {builder_forensics_file_path}")
})?;
let board_forensics_file =
std::fs::File::create(&board_forensics_file_path).with_context(|| {
format!("Failed to create builder forensics files: {builder_forensics_file_path}")
})?;
serde_json::to_writer_pretty(board_forensics_file, &board_info)
.with_context(|| format!("Writing board forensics file to: {board_forensics_file_path}"))?;
// Add devicetree binary
if let Some(devicetree_path) = &board_info.devicetree {
builder
.add_devicetree(devicetree_path.as_utf8_pathbuf())
.context("Adding devicetree binary")?;
}
// Do the actual building of everything for the Image Assembly config.
let mut image_assembly =
builder.build(&outdir, &tools).context("Building Image Assembly config")?;
let images = ImagesConfig::from_product_and_board(
&config.platform.storage.filesystems,
&board_info.filesystems,
)
.context("Constructing images config")?;
image_assembly.images_config = images;
// Validate the built product assembly.
assembly_validate_product::validate_product(
&image_assembly,
package_validation == PackageValidationHandling::Warning,
)?;
// Serialize out the Image Assembly configuration.
let image_assembly_path = outdir.join("image_assembly.json");
let image_assembly_file = std::fs::File::create(&image_assembly_path).with_context(|| {
format!("Failed to create image assembly config file: {image_assembly_path}")
})?;
serde_json::to_writer_pretty(image_assembly_file, &image_assembly)
.with_context(|| format!("Writing image assembly config file: {image_assembly_path}"))?;
Ok(())
}
fn make_bundle_path(bundles_dir: &Utf8PathBuf, name: &str) -> Utf8PathBuf {
bundles_dir.join(name).join("assembly_config.json")
}
fn print_developer_overrides_banner(overrides: &DeveloperOverrides, overrides_path: &Utf8PathBuf) {
let overrides_target = if let Some(target_name) = &overrides.target_name {
target_name.as_str()
} else {
overrides_path.as_str()
};
println!();
println!("WARNING!: Adding the following via developer overrides from: {overrides_target}");
if overrides.developer_only_options.all_packages_in_base {
println!();
println!(" Options:");
println!(" all_packages_in_base: enabled")
}
if !overrides.kernel.command_line_args.is_empty() {
println!();
println!(" Additional kernel command line arguments:");
for arg in &overrides.kernel.command_line_args {
println!(" {arg}");
}
}
println!();
}