Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

xtask/src/ci.rs

//! `cargo xtask ci`: dwarves' own CI plan, and the two constellation-wide verbs.
//!
//! * `cargo xtask ci [--quick|--nightly] [--list] [--only <prefix>]`: dwarves' lock
//!   check, its workspace tests, its build with holger in it (`build:holger`) and (full
//!   and nightly) its declared rows;
//! * `cargo xtask ci lockcheck --siblings <dir> [--repo <name>]... [--single <crate>]...`:
//!   THE LOCK CHECK over a constellation of checked-out heads. Every `Cargo.lock` of
//!   every checkout under `<dir>` is asked `cargo metadata --locked` in its own
//!   directory with the sibling patches on, and no crate may appear at two versions
//!   where that is refused (korp-installer by name, or a sibling's crate beside a
//!   registry copy of it). It lives here because dwarves is the forge: it is the
//!   repo that looks at the whole constellation at once;
//! * `cargo xtask ci status --runs <dir> [--out <file>]`: the status page over every
//!   receipt below `<dir>`.
//!
//! The runner and every step are nornir-xtask-support's `ci` and `lockcheck`; this
//! file only lists dwarves' steps.

use std::path::{Path, PathBuf};

use nornir_xtask_support::ci::{self, Mode, Plan, Step};
use nornir_xtask_support::lockcheck;

fn root() -> PathBuf {
    // xtask/ is one level below the workspace root.
    Path::new(env!("CARGO_MANIFEST_DIR")).parent().map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from("."))
}

/// dwarves' plan.
pub fn plan(root: &Path) -> Plan {
    Plan {
        repo: "dwarves".into(),
        root: root.to_path_buf(),
        steps: vec![
            lockcheck::step(root),
            Step::cargo("test", &["test", "--workspace", "--locked"]),
            // dwarves WITH holger in it, against the holger checkout beside it (on the forge:
            // holger main's head). The default features link no holger, so without this step a
            // holger change that breaks dwarves (Holger 3.0.0 asked for 3.0 where dwarves and
            // nornir asked for 2.0) shows first in a media lane. `embed-holger` also builds
            // nornir's embedded holger; `sign-step` is the media forge's feature. Not
            // `--locked`: this step answers whether dwarves BUILDS against holger's head (as the
            // media forge's `cargo run` does); a lock that trails a sibling is lockcheck's red.
            Step::cargo(
                "build:holger",
                &["build", "-p", "dwarves", "--features", "embed-holger,rehearse,sign-step", "--bin", "dwarves"],
            ),
            Step::cargo("rows:ORIGINAL", &["xtask", "build", "--suite", "ORIGINAL"]).from(Mode::Full),
        ],
    }
}

fn value(args: &[String], flag: &str) -> Option<String> {
    args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1)).cloned()
}

fn values(args: &[String], flag: &str) -> Vec<String> {
    let mut out = Vec::new();
    let mut it = args.iter();
    while let Some(a) = it.next() {
        if a == flag {
            if let Some(v) = it.next() {
                out.push(v.clone());
            }
        }
    }
    out
}

/// `cargo xtask ci lockcheck`: a receipt like every other ci run, repo `constellation`.
fn lockcheck_verb(args: &[String]) -> i32 {
    let dir = value(args, "--siblings")
        .map(PathBuf::from)
        .unwrap_or_else(|| root().parent().map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from("..")));
    let dir = std::path::absolute(&dir).unwrap_or(dir);
    let named = values(args, "--repo");
    let single = lockcheck::single_from_args(args);
    let d = dir.clone();
    let step = Step::in_process("lockcheck", move |w| {
        let single: Vec<&str> = single.iter().map(String::as_str).collect();
        for repo in lockcheck::repos_under(&d, &named) {
            let head = ci::head(&repo);
            let _ = writeln!(w, "head {} {head}", repo.file_name().map(|n| n.to_string_lossy()).unwrap_or_default());
        }
        let red: Vec<String> =
            lockcheck::check_constellation(&d, &named, &single, w)?.iter().filter(|f| f.is_red()).map(|f| f.to_string()).collect();
        if red.is_empty() {
            Ok(None)
        } else {
            Err(format!("{} red finding(s): {}", red.len(), red.join("; ")).into())
        }
    });
    let plan = Plan { repo: "constellation".into(), root: dir, steps: vec![step] };
    ci::main(&plan, &[])
}

use std::io::Write as _;

fn status_verb(args: &[String]) -> i32 {
    let Some(runs) = value(args, "--runs") else {
        eprintln!("usage: cargo xtask ci status --runs <dir> [--out <file>]");
        return 2;
    };
    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
    let md = ci::status_md(Path::new(&runs), now);
    match value(args, "--out") {
        Some(out) => match std::fs::write(&out, &md) {
            Ok(()) => {
                println!("ci status: {out}");
                0
            }
            Err(e) => {
                eprintln!("ci status: write {out}: {e}");
                1
            }
        },
        None => {
            print!("{md}");
            0
        }
    }
}

/// `cargo xtask ci ...`.
pub fn run(args: &[String]) -> i32 {
    match args.first().map(String::as_str) {
        Some("lockcheck") => lockcheck_verb(&args[1..]),
        Some("status") => status_verb(&args[1..]),
        _ => ci::main(&plan(&root()), args),
    }
}