blob: 8415cc7d40cb4b6d4e235e4df01e327e85d0bed1 [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.
use anyhow::{bail, Context, Result};
use assembly_tool::Tool;
use camino::{Utf8Path, Utf8PathBuf};
/// Builder for compiling a component out of cml shards.
pub struct ComponentBuilder {
/// The name of the component.
name: String,
/// A list of component manifest shards to be merged into the final
/// component manifest. This keeps the order that shards are provided to
/// it.
manifest_shards: Vec<Utf8PathBuf>,
}
impl ComponentBuilder {
/// Construct a new ComponentBuilder that uses the cmc |tool|.
pub fn new(name: impl Into<String>) -> Self {
ComponentBuilder { name: name.into(), manifest_shards: Vec::default() }
}
/// Add a CML shard or the primary CML file for this component.
pub fn add_shard(&mut self, path: impl AsRef<Utf8Path>) -> Result<&mut Self> {
let path = path.as_ref().to_path_buf();
// Validate that the shard hasn't be previously added.
if self.manifest_shards.contains(&path) {
bail!("Component shard path {} already added", path);
}
// Add the shard.
self.manifest_shards.push(path);
Ok(self)
}
/// Build the component.
pub fn build(
self,
outdir: impl AsRef<Utf8Path>,
cmc_tool: &dyn Tool,
include_path: impl AsRef<Utf8Path>,
) -> Result<Utf8PathBuf> {
// Write all generated files in a subdir with the name of the package.
let outdir = outdir.as_ref().join(&self.name);
let cmlfile = outdir.join(format!("{}.cml", &self.name));
let mut args = vec!["merge".to_owned(), "--output".to_owned(), cmlfile.to_string()];
args.extend(self.manifest_shards.iter().map(Utf8PathBuf::to_string));
cmc_tool
.run(&args)
.with_context(|| format!("Failed to run cmc merge with shards {args:?}"))?;
let cmfile = outdir.join(format!("{}.cm", &self.name));
let args = vec![
"compile".into(),
"--features=allow_long_names".into(),
"--features=dictionaries".into(),
"--includeroot".into(),
include_path.as_ref().to_string(),
"--includepath".into(),
include_path.as_ref().to_string(),
"--config-package-path".into(),
format!("meta/{}.cvf", &self.name),
"-o".into(),
cmfile.to_string(),
cmlfile.to_string(),
];
cmc_tool
.run(&args)
.with_context(|| format!("Failed to run cmc compile with args {args:?}"))?;
Ok(cmfile)
}
}
#[cfg(test)]
mod tests {
use crate::ComponentBuilder;
use assembly_tool::testing::FakeToolProvider;
use assembly_tool::{ToolCommandLog, ToolProvider};
use camino::Utf8Path;
use serde_json::json;
use tempfile::TempDir;
#[test]
fn add_shard_with_duplicates_returns_err() {
let mut builder = ComponentBuilder::new("foo");
builder.add_shard("foobar").unwrap();
let result = builder.add_shard("foobar");
assert!(result.is_err());
}
#[test]
fn build_with_shards_compiles_component() {
let tmpdir = TempDir::new().unwrap();
let outdir = Utf8Path::from_path(tmpdir.path()).unwrap();
let shard_path_1 = outdir.join("shard1.cml");
let shard_path_2 = outdir.join("shard2.cml");
let shard_path_3 = outdir.join("shard3.cml");
let tools = FakeToolProvider::default();
let mut builder = ComponentBuilder::new("test");
builder.add_shard(&shard_path_1).unwrap();
builder.add_shard(&shard_path_2).unwrap().add_shard(&shard_path_3).unwrap();
let expected_commands: ToolCommandLog = serde_json::from_value(json!({
"commands": [
{
"tool": "./host_x64/cmc",
"args": [
"merge",
"--output",
outdir.join("test").join("test.cml").to_string(),
shard_path_1.to_string(),
shard_path_2.to_string(),
shard_path_3.to_string(),
]
},
{
"tool": "./host_x64/cmc",
"args": [
"compile",
"--features=allow_long_names",
"--features=dictionaries",
"--includeroot",
"include/path",
"--includepath",
"include/path",
"--config-package-path",
"meta/test.cvf",
"-o",
outdir.join("test").join("test.cm").to_string(),
outdir.join("test").join("test.cml").to_string(),
]
}
]
}))
.unwrap();
let result = builder.build(outdir, tools.get_tool("cmc").unwrap().as_ref(), "include/path");
assert!(result.is_ok());
assert_eq!(&expected_commands, tools.log());
}
}