nordisk/heimdall
xtask/src/main.rs
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! `cargo xtask ci [--quick|--nightly]`: Heimdall's CI verb for the dwarves CI forge
//! (nordisk/dwarves pipelines/ci-main.runa.ron). Heimdall is a defensive sandbox; the
//! verb only runs scripts/ci.sh, which builds and tests Heimdall itself. All logic is in
//! the script; `--quick` and `--nightly` run the same script.
use std::path::{Path, PathBuf};
use std::process::{Command, ExitCode};
/// The program and arguments `cargo xtask <args>` runs from `root`, or why not.
fn plan(args: &[String], root: &Path) -> Result<(PathBuf, Vec<String>), String> {
const USAGE: &str = "usage: cargo xtask ci [--quick|--nightly]";
match args.split_first() {
None => Err(USAGE.into()),
Some((verb, flags)) if verb == "ci" => {
if let Some(bad) = flags
.iter()
.find(|f| !matches!(f.as_str(), "--quick" | "--nightly"))
{
return Err(format!("ci takes no `{bad}`; {USAGE}"));
}
let script = root.join("scripts").join("ci.sh");
Ok((PathBuf::from("sh"), vec![script.display().to_string()]))
}
Some((verb, _)) => Err(format!("no verb `{verb}`; {USAGE}")),
}
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let root = manifest.parent().unwrap_or(manifest);
match plan(&args, root) {
Ok((program, argv)) => match Command::new(&program)
.args(&argv)
.current_dir(root)
.status()
{
Ok(s) if s.success() => ExitCode::SUCCESS,
Ok(s) => {
eprintln!("xtask: {} {} ended {s}", program.display(), argv.join(" "));
ExitCode::FAILURE
}
Err(e) => {
eprintln!("xtask: {} did not start: {e}", program.display());
ExitCode::FAILURE
}
},
Err(why) => {
eprintln!("xtask: {why}");
ExitCode::from(2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| x.to_string()).collect()
}
#[test]
fn ci_and_its_quick_and_nightly_forms_all_run_scripts_ci_sh() {
let root = Path::new("/r");
for a in [&["ci"][..], &["ci", "--quick"], &["ci", "--nightly"]] {
assert_eq!(
plan(&s(a), root),
Ok((PathBuf::from("sh"), s(&["/r/scripts/ci.sh"]))),
"{a:?}"
);
}
}
#[test]
fn an_unknown_verb_or_flag_is_refused_by_name() {
let root = Path::new("/r");
assert!(plan(&s(&[]), root).unwrap_err().contains("usage"));
assert!(plan(&s(&["build"]), root).unwrap_err().contains("`build`"));
assert!(plan(&s(&["ci", "--fast"]), root)
.unwrap_err()
.contains("`--fast`"));
}
}