use std::path::PathBuf; use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; use crate::build::BuildExecutor; use crate::compare::compare; use crate::hasher::{HasherInstaller, ProcessRunner as HasherProcessRunner}; use crate::initramfs::{MakeInitrdBuilder, ProcessRunner as InitramfsProcessRunner}; use crate::manifest::ArtifactManifest; use crate::{BuildPlan, ImageSpec}; #[derive(Debug, Parser)] #[command(name = "alt-controller-image", about = "Native ALT image builder")] pub struct Cli { #[command(subcommand)] pub command: Command, } #[derive(Debug, Subcommand)] pub enum Command { /// Validate an image specification and render its build intent. Plan { #[arg(long)] spec: PathBuf, }, /// Build an image from a specification. Build { #[arg(long)] spec: PathBuf, #[arg(long)] workspace: PathBuf, #[arg(long)] output: PathBuf, }, /// Inspect a built artifact. Inspect { #[arg(long)] artifact: PathBuf, }, /// Compare two built artifacts semantically. Compare { #[arg(long)] left: PathBuf, #[arg(long)] right: PathBuf, }, } pub fn run(cli: Cli) -> Result<()> { match cli.command { Command::Plan { spec } => { let plan = BuildPlan::compile(ImageSpec::load(&spec)?)?; println!("target: {}", plan.spec().target.name); println!("architecture: {}", plan.spec().target.architecture.as_str()); println!("format: tar"); println!("stages:"); for (index, stage) in plan.stages().iter().enumerate() { println!(" {}. {stage}", index + 1); } Ok(()) } Command::Build { spec, workspace, output, } => { let plan = BuildPlan::compile(ImageSpec::load(&spec)?)?; let apt_config = spec .parent() .unwrap_or_else(|| std::path::Path::new(".")) .join("apt.conf"); let installer = HasherInstaller::new(HasherProcessRunner); let initramfs = MakeInitrdBuilder::new(InitramfsProcessRunner); let mut executor = BuildExecutor::new(installer, initramfs); let result = executor.execute(&plan, workspace, apt_config, output)?; println!("artifact: {}", result.artifact().display()); println!("manifest: {}", result.manifest().display()); if let Some(initrd) = result.initrd() { println!( "initrd: {} sha256:{}", initrd.initrd_path().display(), initrd.sha256() ); } Ok(()) } Command::Inspect { artifact } => { let manifest = load_artifact(&artifact)?; println!("{}", toml::to_string_pretty(&manifest)?); Ok(()) } Command::Compare { left, right } => { let left = load_artifact(&left)?; let right = load_artifact(&right)?; let report = compare(&left, &right); print!("{}", report.render()); if report.is_equivalent() { Ok(()) } else { bail!("artifacts differ semantically") } } } } fn load_artifact(path: &std::path::Path) -> Result { if matches!( path.extension().and_then(|extension| extension.to_str()), Some("toml") ) { return ArtifactManifest::load(path); } // A completed native build writes richer package and initramfs facts beside // its tarball. Prefer that project-owned semantic record while retaining // direct legacy-tar inspection when no companion is available. let companion = ArtifactManifest::path_beside(path)?; if companion.is_file() { ArtifactManifest::load(companion) } else { ArtifactManifest::collect_archive(path) } }