| // 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::image_assembly_config::PartialKernelConfig; |
| use crate::platform_config::PlatformConfig; |
| use crate::PackageDetails; |
| use assembly_package_utils::PackageInternalPathBuf; |
| use assembly_util::{CompiledPackageDestination, FileEntry}; |
| use camino::Utf8PathBuf; |
| use serde::{Deserialize, Serialize}; |
| use std::collections::{BTreeMap, BTreeSet}; |
| |
| use crate::common::{DriverDetails, PackageName}; |
| use crate::product_config::ProductConfig; |
| |
| /// Configuration for a Product Assembly operation. This is a high-level operation |
| /// that takes a more abstract description of what is desired in the assembled |
| /// product images, and then generates the complete Image Assembly configuration |
| /// (`crate::config::ImageAssemblyConfig`) from that. |
| #[derive(Debug, Deserialize, Serialize)] |
| #[serde(deny_unknown_fields)] |
| pub struct AssemblyConfig { |
| pub platform: PlatformConfig, |
| pub product: ProductConfig, |
| } |
| |
| /// Configuration for Product Assembly, when developer overrides are in use. |
| /// |
| /// This deserializes to intermediate types that can be manipulated in order to |
| /// apply developer overrides, before being parsed into the PlatformConfig |
| /// and ProductConfig types. |
| #[derive(Debug, Deserialize, Serialize)] |
| #[serde(deny_unknown_fields)] |
| pub struct AssemblyConfigWrapperForOverrides { |
| // The platform config is deserialized as a Value before it is parsed into |
| // a 'PlatformConfig`` |
| pub platform: serde_json::Value, |
| pub product: ProductConfig, |
| } |
| |
| /// A typename to represent a package that contains shell command binaries, |
| /// and the paths to those binaries |
| pub type ShellCommands = BTreeMap<PackageName, BTreeSet<PackageInternalPathBuf>>; |
| |
| /// A bundle of inputs to be used in the assembly of a product. |
| #[derive(Debug, Default, Deserialize, Serialize, PartialEq)] |
| #[serde(deny_unknown_fields)] |
| pub struct AssemblyInputBundle { |
| /// The parameters that specify which kernel to put into the ZBI. |
| pub kernel: Option<PartialKernelConfig>, |
| |
| /// The qemu kernel to use when starting the emulator. |
| #[serde(default)] |
| pub qemu_kernel: Option<Utf8PathBuf>, |
| |
| /// The list of additional boot args to add. |
| #[serde(default)] |
| pub boot_args: Vec<String>, |
| |
| /// The packages that are in the bootfs package list, which are |
| /// added to the BOOTFS in the ZBI. |
| #[serde(default)] |
| pub bootfs_packages: Vec<Utf8PathBuf>, |
| |
| /// The set of files to be placed in BOOTFS in the ZBI. |
| #[serde(default)] |
| pub bootfs_files: Vec<FileEntry<String>>, |
| |
| /// Package entries that internally specify their package set, instead of being grouped |
| /// separately. |
| #[serde(default)] |
| pub packages: Vec<PackageDetails>, |
| |
| /// Entries for the `config_data` package. |
| #[serde(default)] |
| pub config_data: BTreeMap<String, Vec<FileEntry<String>>>, |
| |
| /// The blobs index of the AIB. This currently isn't used by product |
| /// assembly, as the package manifests contain the same information. |
| #[serde(default)] |
| pub blobs: Vec<Utf8PathBuf>, |
| |
| /// Configuration of base driver packages. Driver packages should not be |
| /// listed in the base package list and will be included automatically. |
| #[serde(default)] |
| pub base_drivers: Vec<DriverDetails>, |
| |
| /// Configuration of boot driver packages. Driver packages should not be |
| /// listed in the bootfs package list and will be included automatically. |
| #[serde(default)] |
| pub boot_drivers: Vec<DriverDetails>, |
| |
| /// Map of the names of packages that contain shell commands to the list of |
| /// commands within each. |
| #[serde(default)] |
| pub shell_commands: ShellCommands, |
| |
| /// Packages to create dynamically as part of the Assembly process. |
| #[serde(default)] |
| pub packages_to_compile: Vec<CompiledPackageDefinition>, |
| |
| /// A package that includes files to include in bootfs. |
| #[serde(default)] |
| pub bootfs_files_package: Option<Utf8PathBuf>, |
| } |
| |
| /// Contents of a compiled package. The contents provided by all |
| /// selected AIBs are merged by `name` into a single package |
| /// at assembly time. |
| #[derive(Debug, Deserialize, Serialize, PartialEq)] |
| #[serde(deny_unknown_fields)] |
| #[serde(untagged)] |
| pub enum CompiledPackageDefinition { |
| MainDefinition(MainPackageDefinition), |
| Additional(AdditionalPackageContents), |
| } |
| |
| /// Primary definition of a compiled package. Only a single AIB should |
| /// contain the main definition for a given package. |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] |
| #[serde(deny_unknown_fields)] |
| pub struct MainPackageDefinition { |
| pub name: CompiledPackageDestination, |
| /// Components to add to the package, mapping component name to |
| /// the primary cml definition of the component. |
| pub components: BTreeMap<String, Utf8PathBuf>, |
| /// Non-component files to add to the package. |
| #[serde(default)] |
| pub contents: Vec<FileEntry<String>>, |
| /// CML files included by the component cml. |
| #[serde(default)] |
| pub includes: Vec<Utf8PathBuf>, |
| /// Whether the contents of this package should go into bootfs. |
| /// Gated by allowlist -- please use this as a base package if possible. |
| #[serde(default)] |
| pub bootfs_package: bool, |
| } |
| |
| /// Additional contents of the package to be defined in |
| /// secondary AssemblyInputBundles. There can be many of these, and |
| /// they will be merged into the final compiled package. |
| #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] |
| #[serde(deny_unknown_fields)] |
| pub struct AdditionalPackageContents { |
| /// Name of the package to which these contents are associated. |
| /// This package should have a corresponding MainDefinition in another |
| /// AIB. |
| pub name: CompiledPackageDestination, |
| /// Additional component shards to combine with the primary manifest. |
| pub component_shards: BTreeMap<String, Vec<Utf8PathBuf>>, |
| } |
| |
| impl CompiledPackageDefinition { |
| pub fn name(&self) -> &CompiledPackageDestination { |
| match self { |
| CompiledPackageDefinition::MainDefinition(MainPackageDefinition { name, .. }) => name, |
| CompiledPackageDefinition::Additional(AdditionalPackageContents { name, .. }) => name, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::common::{FeatureControl, PackageSet}; |
| use crate::platform_config::media_config::{AudioConfig, PlatformMediaConfig}; |
| use crate::platform_config::{BuildType, FeatureSupportLevel}; |
| use crate::product_config::ProductPackageDetails; |
| use assembly_file_relative_path::FileRelativePathBuf; |
| use assembly_util as util; |
| |
| #[test] |
| fn test_product_assembly_config_from_json5() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "eng", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::Eng); |
| assert_eq!(platform.feature_set_level, FeatureSupportLevel::Standard); |
| } |
| |
| #[test] |
| fn test_bringup_product_assembly_config_from_json5() { |
| let json5 = r#" |
| { |
| platform: { |
| feature_set_level: "bootstrap", |
| build_type: "eng", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::Eng); |
| assert_eq!(platform.feature_set_level, FeatureSupportLevel::Bootstrap); |
| } |
| |
| #[test] |
| fn test_minimal_product_assembly_config_from_json5() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "eng", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::Eng); |
| assert_eq!(platform.feature_set_level, FeatureSupportLevel::Standard); |
| } |
| |
| #[test] |
| fn test_empty_product_assembly_config_from_json5() { |
| let json5 = r#" |
| { |
| platform: { |
| feature_set_level: "empty", |
| build_type: "eng", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::Eng); |
| assert_eq!(platform.feature_set_level, FeatureSupportLevel::Empty); |
| } |
| |
| #[test] |
| fn test_buildtype_deserialization_userdebug() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "userdebug", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::UserDebug); |
| } |
| |
| #[test] |
| fn test_buildtype_deserialization_user() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "user", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::User); |
| } |
| |
| #[test] |
| fn test_product_assembly_config_with_product_provided_parts() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "eng", |
| identity : { |
| "password_pinweaver": "allowed", |
| } |
| }, |
| product: { |
| packages: { |
| base: [ |
| { manifest: "path/to/base/package_manifest.json" } |
| ], |
| cache: [ |
| { manifest: "path/to/cache/package_manifest.json" } |
| ] |
| }, |
| base_drivers: [ |
| { |
| package: "path/to/base/driver/package_manifest.json", |
| components: [ "meta/path/to/component.cml" ] |
| } |
| ] |
| }, |
| } |
| "#; |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let config: AssemblyConfig = util::from_reader(&mut cursor).unwrap(); |
| let platform = config.platform; |
| assert_eq!(platform.build_type, BuildType::Eng); |
| assert_eq!( |
| config.product.packages.base, |
| vec![ProductPackageDetails { |
| manifest: "path/to/base/package_manifest.json".into(), |
| config_data: Vec::default() |
| }] |
| ); |
| assert_eq!( |
| config.product.packages.cache, |
| vec![ProductPackageDetails { |
| manifest: "path/to/cache/package_manifest.json".into(), |
| config_data: Vec::default() |
| }] |
| ); |
| assert_eq!(platform.identity.password_pinweaver, FeatureControl::Allowed); |
| assert_eq!( |
| config.product.base_drivers, |
| vec![DriverDetails { |
| package: "path/to/base/driver/package_manifest.json".into(), |
| components: vec!["meta/path/to/component.cml".into()] |
| }] |
| ) |
| } |
| |
| #[test] |
| fn test_assembly_input_bundle_from_json5() { |
| let json5 = r#" |
| { |
| // json5 files can have comments in them. |
| packages: [ |
| { |
| package: "package5", |
| set: "base", |
| }, |
| { |
| package: "package6", |
| set: "cache", |
| }, |
| ], |
| kernel: { |
| path: "path/to/kernel", |
| args: ["arg1", "arg2"], |
| }, |
| // and lists can have trailing commas |
| boot_args: ["arg1", "arg2", ], |
| bootfs_files: [ |
| { |
| source: "path/to/source", |
| destination: "path/to/destination", |
| } |
| ], |
| config_data: { |
| "package1": [ |
| { |
| source: "path/to/source.json", |
| destination: "config.json" |
| } |
| ] |
| }, |
| base_drivers: [ |
| { |
| package: "path/to/driver", |
| components: ["path/to/1234", "path/to/5678"] |
| } |
| ], |
| shell_commands: { |
| "package1": ["path/to/binary1", "path/to/binary2"] |
| }, |
| packages_to_compile: [ |
| { |
| name: "core", |
| components: { |
| "component1": "path/to/component1.cml", |
| "component2": "path/to/component2.cml", |
| }, |
| contents: [ |
| { |
| source: "path/to/source", |
| destination: "path/to/destination", |
| } |
| ], |
| includes: [ "src/path/to/include.cml" ] |
| }, |
| { |
| name: "core", |
| component_shards: { |
| "component1": [ |
| "path/to/shard1.cml", |
| "path/to/shard2.cml" |
| ] |
| } |
| } |
| ] |
| } |
| "#; |
| let bundle = |
| util::from_reader::<_, AssemblyInputBundle>(&mut std::io::Cursor::new(json5)).unwrap(); |
| assert_eq!( |
| bundle.packages, |
| vec!( |
| PackageDetails { |
| package: FileRelativePathBuf::FileRelative(Utf8PathBuf::from("package5")), |
| set: PackageSet::Base, |
| }, |
| PackageDetails { |
| package: FileRelativePathBuf::FileRelative(Utf8PathBuf::from("package6")), |
| set: PackageSet::Cache, |
| }, |
| ) |
| ); |
| let expected_kernel = PartialKernelConfig { |
| path: Some(Utf8PathBuf::from("path/to/kernel")), |
| args: vec!["arg1".to_string(), "arg2".to_string()], |
| }; |
| assert_eq!(bundle.kernel, Some(expected_kernel)); |
| assert_eq!(bundle.boot_args, vec!("arg1".to_string(), "arg2".to_string())); |
| assert_eq!( |
| bundle.bootfs_files, |
| vec!(FileEntry { |
| source: Utf8PathBuf::from("path/to/source"), |
| destination: "path/to/destination".to_string() |
| }) |
| ); |
| assert_eq!( |
| bundle.config_data.get("package1").unwrap(), |
| &vec!(FileEntry { |
| source: Utf8PathBuf::from("path/to/source.json"), |
| destination: "config.json".to_string() |
| }) |
| ); |
| assert_eq!( |
| bundle.base_drivers[0], |
| DriverDetails { |
| package: Utf8PathBuf::from("path/to/driver"), |
| components: vec!( |
| Utf8PathBuf::from("path/to/1234"), |
| Utf8PathBuf::from("path/to/5678") |
| ) |
| } |
| ); |
| assert_eq!( |
| bundle.shell_commands.get("package1").unwrap(), |
| &BTreeSet::from([ |
| PackageInternalPathBuf::from("path/to/binary1"), |
| PackageInternalPathBuf::from("path/to/binary2"), |
| ]) |
| ); |
| } |
| |
| #[test] |
| fn test_assembly_config_wrapper_for_overrides() { |
| let json5 = r#" |
| { |
| platform: { |
| build_type: "eng", |
| }, |
| product: {}, |
| } |
| "#; |
| |
| let overrides = serde_json::json!({ |
| "media": { |
| "audio": { |
| "partial_stack": {} |
| } |
| } |
| }); |
| |
| let mut cursor = std::io::Cursor::new(json5); |
| let AssemblyConfigWrapperForOverrides { platform, product: _ } = |
| util::from_reader(&mut cursor).unwrap(); |
| |
| // serde_json and serde_json5 have an incompatible handling of how they |
| // serialize / deserialize enums. So this test validates both the |
| // value merging method but also that the problematic enum syntax is |
| // correctly parsed when bounced through a string as it's done in the |
| // product assembly binary itself. |
| |
| // 1. Merge to a 'value', not to the final type, as we need serde_json5 |
| // to do the parsing, not serde_json. |
| let merged_platform_value: serde_json::Value = |
| crate::try_merge_into(platform, overrides).unwrap(); |
| |
| // 2. Write the value out to a string, using pretty-printing so that |
| // line numbers and such are all sensical. |
| let merged_platform_string = serde_json::to_string_pretty(&merged_platform_value).unwrap(); |
| |
| // 3. Parse the string using serde_json5, so that enums are handled |
| // consistently. |
| let merged_platform: PlatformConfig = |
| serde_json5::from_str(&merged_platform_string).unwrap(); |
| |
| assert_eq!( |
| merged_platform.media, |
| PlatformMediaConfig { audio: Some(AudioConfig::PartialStack), ..Default::default() }, |
| ); |
| } |
| } |