use std::{ffi::OsString, path::PathBuf, process};
use anyhow::Result;
use clap::{ArgGroup, Parser};
use vmrunner::{
GuestInitBuildOptions, ensure_guest_init_with_options, ensure_root_qcow2,
guest_init_path_for_root_qcow2, install_guest_init_blob,
};
#[derive(Debug, Parser)]
#[command(
name = "build-krun-init-blob",
about = "Build libkrun's guest init outside the vmrunner dependency graph.",
after_help = "Environment fallbacks:\n LIBKRUN_DIR\n KRUN_INIT_GUEST_TARGET\n KRUN_INIT_TARGET_DIR\n KRUN_INIT_CARGO_TOOLCHAIN\n CC_LINUX\n VMRUNNER_LINUX_SYSROOT\n\nBy default, vmrunner extracts a Linux compiler sysroot from --root-qcow2 and builds guest init with clang --sysroot.",
group(
ArgGroup::new("destination")
.required(true)
.multiple(false)
.args(["root_qcow2", "output"])
)
)]
struct Args {
#[arg(
long = "libkrun-dir",
value_name = "PATH",
help = "libkrun checkout containing the krun-init-blob package"
)]
libkrun_dir: Option<PathBuf>,
#[arg(
long = "target",
value_name = "TARGET",
help = "Linux guest init rustup target matching the host arch, for example aarch64-unknown-linux-gnu"
)]
guest_target: Option<String>,
#[arg(
long = "root-qcow2",
value_name = "PATH",
help = "root qcow2 path or URL; writes sibling .NAME.init.krun"
)]
root_qcow2: Option<PathBuf>,
#[arg(
long,
value_name = "PATH",
help = "explicit output path for the built init binary"
)]
output: Option<PathBuf>,
#[arg(
long = "cc-linux",
value_name = "COMMAND",
help = "compiler command for guest init C code"
)]
cc_linux: Option<String>,
#[arg(
long = "target-dir",
value_name = "PATH",
help = "Cargo target dir for the standalone init build"
)]
cargo_target_dir: Option<PathBuf>,
#[arg(
long = "cargo-toolchain",
value_name = "T",
help = "rustup toolchain passed as +T before cargo build"
)]
cargo_toolchain: Option<OsString>,
}
fn main() {
if let Err(error) = run() {
eprintln!("{error:#}");
process::exit(1);
}
}
fn run() -> Result<()> {
let args = Args::parse();
let mut options = GuestInitBuildOptions::default();
if let Some(guest_target) = args.guest_target {
options = options.guest_target(guest_target);
}
if let Some(libkrun_dir) = args.libkrun_dir {
options = options.libkrun_dir(libkrun_dir);
}
if let Some(cc_linux) = args.cc_linux {
options = options.cc_linux(cc_linux);
}
if let Some(cargo_target_dir) = args.cargo_target_dir {
options = options.cargo_target_dir(cargo_target_dir);
}
if let Some(cargo_toolchain) = args.cargo_toolchain {
options = options.cargo_toolchain(cargo_toolchain);
}
let output_path = if let Some(root_qcow2) = args.root_qcow2 {
let root_qcow2_path = ensure_root_qcow2(root_qcow2)?;
ensure_guest_init_with_options(&root_qcow2_path, options)?;
guest_init_path_for_root_qcow2(root_qcow2_path)
} else if let Some(output_path) = args.output {
install_guest_init_blob(&output_path, options)?;
output_path
} else {
unreachable!("clap requires --root-qcow2 or --output")
};
println!("installed guest init: {}", output_path.display());
Ok(())
}
#[cfg(test)]
mod tests {
use super::Args;
use clap::CommandFactory;
#[test]
fn clap_definition_is_valid() {
Args::command().debug_assert();
}
}