summaryrefslogtreecommitdiff
path: root/src/cli.rs
blob: ffa9bbad37fabb3d04e7570ab868263950bbb4ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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<ArtifactManifest> {
    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)
    }
}