use std::path::PathBuf;
use clap::Args;
use consortium_builder::pipeline::{
self, PipelineConfig, bitbake::BitbakeAction, tee::TeeToolchain,
};
use crate::reporter::StickyReporter;
#[derive(Args)]
pub struct BuildArgs {
#[arg(long, default_value = "Consortium.toml")]
manifest: PathBuf,
#[arg(long, default_value = "dist")]
dist: PathBuf,
#[arg(long, value_name = "DIR")]
linux: PathBuf,
#[arg(long)]
release: bool,
#[arg(long)]
offline: bool,
#[arg(long)]
skip_tee_sign: bool,
#[arg(long)]
skip_remoteproc_check: bool,
#[arg(long, value_name = "DIR")]
bitbake_files_dir: Option<PathBuf>,
#[arg(long, value_name = "RECIPE")]
bitbake_recipe: Option<String>,
#[arg(long, value_name = "DIR")]
bitbake_build_dir: Option<PathBuf>,
#[arg(long)]
bitbake_dry_run: bool,
}
fn resolve_bitbake(args: &BuildArgs) -> Result<Vec<BitbakeAction>, String> {
let mut actions = Vec::new();
if let Some(dir) = &args.bitbake_files_dir {
actions.push(BitbakeAction::CopyIntoRecipe {
recipe_files_dir: dir.clone(),
});
}
match (&args.bitbake_recipe, &args.bitbake_build_dir) {
(Some(recipe), Some(build_dir)) => actions.push(BitbakeAction::InvokeBitbake {
build_dir: build_dir.clone(),
recipe: recipe.clone(),
dry_run: args.bitbake_dry_run,
}),
(Some(_), None) => return Err("--bitbake-recipe requires --bitbake-build-dir".to_string()),
(None, Some(_)) => return Err("--bitbake-build-dir requires --bitbake-recipe".to_string()),
(None, None) if args.bitbake_dry_run => {
return Err("--bitbake-dry-run requires --bitbake-recipe".to_string());
}
(None, None) => {}
}
Ok(actions)
}
pub async fn run(args: BuildArgs) -> Result<(), String> {
let session = pipeline::load_session(&args.manifest)
.await
.map_err(|e| e.to_string())?;
let manifest_dir = args
.manifest
.parent()
.map(std::path::Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let hmi =
pipeline::derive_hmi_configuration(&session, &manifest_dir).map_err(|e| e.to_string())?;
let tee_toolchain = TeeToolchain::from_env();
let bitbake = resolve_bitbake(&args)?;
let config = PipelineConfig {
manifest_path: args.manifest.clone(),
dist_root: args.dist.clone(),
linux: args.linux.clone(),
release: args.release,
offline: args.offline,
hmi,
tee_toolchain,
sign_tee: !args.skip_tee_sign,
strict_remoteproc: !args.skip_remoteproc_check,
bitbake,
};
let plan = pipeline::plan(&session, &config)
.await
.map_err(|e| e.to_string())?;
println!(
"Building {} for chip {}",
args.manifest.display(),
session.database.name
);
for fw in &plan.firmware {
println!(" firmware: {} ({})", fw.core, fw.config.target);
}
if plan.hmi.is_some() {
let source = session
.config
.hmi
.as_ref()
.map(|h| h.source.as_str())
.unwrap_or("-");
println!(" hmi: {source}");
}
if plan.ta.is_some() {
let signed = if args.skip_tee_sign {
" (unsigned: --skip-tee-sign)"
} else {
""
};
println!(" tee: trusted application{signed}");
}
println!(" app: {}", plan.application.config.target);
if args.offline {
println!(" cargo: --offline (local registry cache only)");
}
if args.skip_remoteproc_check {
println!(
" dts: --skip-remoteproc-check (missing remoteproc nodes are warnings, not errors)"
);
}
let reporter = StickyReporter::new();
let dist = match pipeline::run_with_reporter(config, &reporter).await {
Ok(dist) => {
reporter.finish_success("Build complete");
dist
}
Err(error) => {
reporter.finish_failure("Build failed");
return Err(error.to_string());
}
};
println!("Assembled dist tree at {}", dist.display());
Ok(())
}