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

nordisk/dwarves

ci/verbs/src/lib.rs

//! The CI runner's own verbs (`ci/runner.sh`), detached from dwarves' workspace:
//!
//! * `ci-verbs 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. One receipt like every other ci run, repo `constellation`;
//! * `ci-verbs status --runs <dir> [--out <file>]`: the status page over every receipt
//!   below `<dir>`;
//! * `ci-verbs scorecard --checkout <dir> [--out <dir>]`: the OpenSSF Scorecard of a checkout
//!   in local mode, as JSON and a Markdown summary ([`scorecard`]);
//! * `ci-verbs openssf-badge --badge <repo>=<project>... [--checkout <dir>] [--out <dir>]`:
//!   a checked copy of the checkout's OpenSSF Best Practices badge, which the forge
//!   publishes in nordisk/ci-status for gunnar.rs to draw from its own origin ([`badge`]);
//! * `ci-verbs fuzz --checkout <dir>`: the nightly fuzz run of a cargo-fuzz crate, red on a
//!   crash ([`fuzz`]);
//! * `ci-verbs coverage --checkout <dir>`: the nightly cargo llvm-cov numbers ([`coverage`]);
//! * `ci-verbs deps-update --checkout <dir> --author ..`: the weekly dependency update
//!   proposal ([`deps`]);
//! * `ci-verbs bench-readme --checkout <dir> --host <ssh host> ..`: the weekly bench on the
//!   bench host and the README speed tables regenerated from it, proposed as a `bench/` branch
//!   the forge merges, red on a regression ([`bench`]).
//!
//! dwarves' `cargo xtask ci lockcheck|status` are a KNOWN TWIN of these two verbs, kept
//! for developers until the xtask hands over to this crate. The runner and every step are
//! nornir-xtask-support's.

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

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

pub mod badge;
pub mod bench;
pub mod coverage;
pub mod deps;
pub mod fuzz;
pub mod scorecard;

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

/// Every value after a `flag`.
pub 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
}

/// `lockcheck`: `--siblings` else `default_siblings`.
pub fn lockcheck_verb(args: &[String], default_siblings: &Path) -> i32 {
    let dir = value(args, "--siblings").map(PathBuf::from).unwrap_or_else(|| default_siblings.to_path_buf());
    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, &[])
}

/// `status`.
pub fn status_verb(args: &[String]) -> i32 {
    let Some(runs) = value(args, "--runs") else {
        eprintln!("usage: ci-verbs 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
        }
    }
}

/// Dispatch: `lockcheck ...` | `status ...`.
pub fn run(args: &[String], default_siblings: &Path) -> i32 {
    match args.first().map(String::as_str) {
        Some("lockcheck") => lockcheck_verb(&args[1..], default_siblings),
        Some("status") => status_verb(&args[1..]),
        Some("scorecard") => scorecard::verb(&args[1..]),
        Some("openssf-badge") => badge::verb(&args[1..]),
        Some("fuzz") => fuzz::verb(&args[1..]),
        Some("coverage") => coverage::verb(&args[1..]),
        Some("deps-update") => deps::verb(&args[1..]),
        Some("bench-readme") => bench::verb(&args[1..]),
        _ => {
            eprintln!("usage: ci-verbs lockcheck --siblings <dir> [--repo <name>]... [--single <crate>]... | status --runs <dir> [--out <file>] | scorecard --checkout <dir> [--out <dir>] [--bin <scorecard>] | openssf-badge --badge <repo>=<project>... [--checkout <dir>] [--out <dir>] | fuzz --checkout <dir> [--private <sibling>] [--seconds <n>] | coverage --checkout <dir> [--package <p>]... [--min <pct>] | deps-update --checkout <dir> --author \"<name> <email>\" | bench-readme --checkout <dir> --host <ssh host> --seeds <dir> --machine <name> --threshold <pct> --merge <branch> --author \"<name> <email>\"");
            2
        }
    }
}