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

nordisk/dwarves

xtask/src/main.rs

//! dwarves xtask — build automation for the `dwarves-gui` live web demo.
//!
//! Pure Rust, zero shell (Brokkr's Software Criteria + the zero-shell charter): the
//! gh-pages FLOW itself — compile the demo crate to release wasm, `wasm-bindgen` it
//! into `dist/`, write the cache-busted static `index.html` + Codeberg-Pages meta,
//! then force-push `dist/` to an orphan `pages` branch — is the near-byte-identical
//! block that four repos (nornir/holger/dwarves/facett) grew, so it now lives ONCE in
//! the `nornir-pages` edda leaf (LAW #5, DRY). This crate keeps only the dwarves knobs
//! (the [`PagesConfig`]) and the thin verb dispatcher; the ~457-LOC `demo.rs` copy is
//! gone. The `dwarves` CLI is still never touched.
//!
//! - `cargo xtask build --suite ORIGINAL [--variant <v>] [--list]` — **THE build verb.**
//!   (`--atom` is the deprecated spelling of `--variant`; it still works.)
//!   Its entire body is [`nornir_build_thing::suite_cli::run`]: this file names no
//!   variant, no feature string and no bin, so what gets built comes from
//!   `.nornir/build-variants.toml` and the declaration cannot drift from the build.
//!   dwarves ships ONE delivery (`ORIGINAL`), the same shape korp, nornir, modgunn
//!   and holger take.
//! - `cargo xtask demo-wasm`    — build the declared `dwarves-gui-wasm` row IN THE PINNED
//!   IMAGE (edda `nornir_pages::demo_wasm` → `nornir_build_thing::container_road`:
//!   cargo + its wasm-bindgen stage, never host cargo or host wasm-bindgen) into the
//!   row's dist, plus the static page. ⚠ dwarves declares NO such row today (the set is
//!   the six-row matrix), so the verb is REFUSED BY NAME until the owner decides how the
//!   Pages demo row enters the declaration — there is no host fallback.
//! - `cargo xtask deploy-pages` — run `demo-wasm`, then force-push `dist/` to the
//!   `pages` branch → <https://nordisk.codeberg.page/dwarves>.
//! - `cargo xtask-docs docs`    — render dwarves' real `.nornir/*.md` → the typeset
//!   SVG book the Manual pane embeds (behind the `docs` feature; see [`mod@docs`]).

/// **`build-history`** — seal / verify / regenerate the `znippy-build-thing`
/// package: step 3 of the release order, after the build and before the release.
mod build_history;
mod ci;
// The diagram generator lives in the LIB half of this crate so its rendering can
// be tested (`xtask/tests/build_flow_svg.rs`); a bin's modules cannot be reached
// from a test, and the one thing this diagram must prove is that its two
// verdicts do not render alike.
use xtask::build_flow_svg;
#[cfg(feature = "docs")]
mod docs;

use nornir_pages::PagesConfig;

/// The declared row the Pages demo is built from (`dwarves-gui`, wasm32, with its
/// `[[build.wasm]]` bindgen cell). What the row builds is the declaration's.
const DEMO_VARIANT: &str = "dwarves-gui-wasm";

/// dwarves' declaration, validated — the same load `cargo xtask build` does.
fn declared(root: &std::path::Path) -> Result<nornir_build_thing::BuildVariantSet, String> {
    nornir_build_thing::BuildVariantSet::discover_validated(root)
        .map_err(|e| format!("{e:#}"))?
        .ok_or_else(|| format!("{} declares no build-variant set", root.display()))
}

/// The dwarves-specific gh-pages config — the 100% of divergence from the shared flow.
/// Every knob is exactly the value the old inline `demo.rs` hard-coded.
fn cfg() -> PagesConfig {
    PagesConfig {
        // The cargo crate that owns the wasm binary.
        demo_crate: "dwarves-gui",
        // The eframe canvas id the wasm entry (`dwarves-gui/src/main.rs::start` →
        // `WebDemo::new`) and the `index.html` loader agree on.
        canvas_id: "demo_canvas",
        // The exported JS state-hook name (`dwarves-gui/src/main.rs::dwarves_state`),
        // lifted onto `window` for a headless drive / the browser console.
        state_export: "__dwarves_state",
        // The `<title>` (its first word is also the loading-spinner label → "dwarves").
        title: "dwarves — live web demo",
        description: "dwarves: the CI/CD forge view (pipeline roster + run listing), in your browser — the live in-browser demo (facett-rendered, sample fixture, interim).",
        // The live URL for the deploy banner.
        pages_url: "https://nordisk.codeberg.page/dwarves",
        // The host pinned in `.domains`.
        pages_host: "nordisk.codeberg.page",
        // The branch Codeberg Pages serves from.
        pages_branch: "pages",
        // The env var naming the push remote (unset → `origin`, which is
        // `ssh://git@git.gunnar.rs:2222/nordisk/dwarves.git`).
        remote_env: "DWARVES_PAGES_REMOTE",
        // The human label in the squashed deploy commit message.
        commit_label: "dwarves live web demo",
        // Read by nothing since edda's host road left (2026-09-30): the row's
        // features are the declaration's.
        extra_build_args: &[],
        // The workspace root = the parent of the `xtask` crate dir. The leaf can't read
        // the caller's compile-time manifest dir, so we compute it here (same value the
        // old inline `workspace_root()` produced).
        workspace_root: std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .expect("xtask dir has a parent (the workspace root)")
            .to_path_buf(),
    }
}

/// Regenerate the **embedded** korp-installer UEFI stub: compile the
/// `korp-installer-efistub` fixture for `x86_64-unknown-uefi` ONCE (a dev-box step —
/// needs the target here, NOT at dwarves run/boot time) and copy the produced
/// `bootx64.efi` into `dwarves-gui/src/embedded/korp-installer-bootx64.efi`, which
/// `boot_run.rs` embeds via `include_bytes!`. Run after changing the efistub source so
/// the committed blob (and thus every mastered installer ISO) stays current. This is the
/// self-contained charter: the toolchain is needed HERE (once), never on the box that
/// masters or boots the ISO.
fn gen_installer_stub(workspace_root: &std::path::Path) -> Result<std::path::PathBuf, String> {
    gen_efistub(
        workspace_root,
        "korp-installer-efistub",
        "korp-installer-bootx64.efi",
    )
}

/// Regenerate the **embedded** `korp-start` second-boot stub: compile the
/// `korp-start-efistub` fixture for `x86_64-unknown-uefi` ONCE and copy its `bootx64.efi`
/// into `dwarves-gui/src/embedded/korp-start-bootx64.efi`. The dwarves reboot-into-korp
/// flow lays THESE bytes on the installed disk's FAT ESP (`EFI/BOOT/BOOTX64.EFI`) so the
/// second boot needs zero host toolchain — same self-contained charter as the installer
/// stub. Run after changing the `korp-start-efistub` source.
fn gen_start_stub(workspace_root: &std::path::Path) -> Result<std::path::PathBuf, String> {
    gen_efistub(
        workspace_root,
        "korp-start-efistub",
        "korp-start-bootx64.efi",
    )
}

/// Regenerate the **embedded** `korp-live` run-from-RAM stub (the live-ISO deployment):
/// compile the `korp-live-efistub` fixture for `x86_64-unknown-uefi` and copy its
/// `bootx64.efi` into `dwarves-gui/src/embedded/korp-live-bootx64.efi`. The live-boot flow
/// lays THESE bytes on a bootable ISO's ESP so the ISO runs korp straight from RAM (no
/// install) — same self-contained charter as the other stubs.
fn gen_live_stub(workspace_root: &std::path::Path) -> Result<std::path::PathBuf, String> {
    gen_efistub(workspace_root, "korp-live-efistub", "korp-live-bootx64.efi")
}

/// Regenerate the **embedded PRODUCTION first-stage loader** —
/// `dwarves-gui/src/embedded/korp-boot-stage1.efi`, the real `EFI/BOOT/BOOTX64.EFI`.
///
/// This is deliberately NOT [`gen_efistub`]: that helper only knows how to reach
/// `korp-installer/tests/fixtures/{name}`, and the production loader is *not* a test
/// fixture — it lives at `korp-installer/boot/korp-boot-efi`. Rather than teach the
/// fixture helper a second path shape, this verb rides the crate's own host-side seam
/// [`korp_installer::firststage::build_first_stage`], which carries the FAT
/// `rustup target add x86_64-unknown-uefi` preflight error (never a raw `E0463`
/// "can't find crate for `core`") and the "is it really a PE?" check.
///
/// The loader is what makes the second boot a REAL chainload: it finds its own ESP via
/// `EFI_LOADED_IMAGE_PROTOCOL` (no NVRAM `Boot####`), reads `\korp\cmdline`, `LoadImage`s
/// `\vmlinuz` on a device path rooted at that ESP, installs the cmdline as UCS-2
/// `LoadOptions` and `StartImage`s — printing `KORP-BOOT: HANDOFF -> kernel` immediately
/// before it hands over. `boot_run.rs` embeds these bytes via `include_bytes!` and lays
/// them on the installed disk's ESP, so the appliance needs zero host toolchain.
fn gen_boot_stage1(workspace_root: &std::path::Path) -> Result<std::path::PathBuf, String> {
    // Keep the UEFI artefacts out of the loader's own source tree AND off any tmpfs
    // (a shared /tmp filling up breaks every concurrent build) — dwarves' own target/.
    let scratch = workspace_root.join("target/korp-boot-stage1");
    std::fs::create_dir_all(&scratch).map_err(|e| format!("create {}: {e}", scratch.display()))?;
    let built =
        korp_installer::firststage::build_first_stage(&scratch).map_err(|e| format!("{e:#}"))?;
    let bytes = std::fs::read(&built).map_err(|e| format!("read {}: {e}", built.display()))?;
    if !korp_installer::firststage::is_uefi_pe(&bytes) {
        return Err(format!(
            "{} is not a UEFI PE — the firmware would refuse it silently",
            built.display()
        ));
    }
    let dst = workspace_root.join("dwarves-gui/src/embedded/korp-boot-stage1.efi");
    std::fs::create_dir_all(dst.parent().unwrap())
        .map_err(|e| format!("create embedded dir: {e}"))?;
    std::fs::write(&dst, &bytes).map_err(|e| format!("write {}: {e}", dst.display()))?;
    Ok(dst)
}

/// Compile a sibling `tunnr/korp-installer` (edda before 2026-09-26) UEFI efistub fixture for
/// `x86_64-unknown-uefi` and copy its `bootx64.efi` into the dwarves embedded dir under
/// `dst_name` — the one seam both `gen-installer-stub` and `gen-start-stub` ride. The Rust
/// COMPILER is needed HERE (once, dev-box), never on the box that masters or boots.
fn gen_efistub(
    workspace_root: &std::path::Path,
    fixture_dir: &str,
    dst_name: &str,
) -> Result<std::path::PathBuf, String> {
    let fixture = workspace_root.join(format!(
        "../tunnr/crates/korp-installer/tests/fixtures/{fixture_dir}/Cargo.toml"
    ));
    if !fixture.is_file() {
        return Err(format!(
            "efistub fixture not found at {} — need the sibling tunnr checkout",
            fixture.display()
        ));
    }
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
    let status = std::process::Command::new(&cargo)
        .args([
            "build",
            "--release",
            "--target",
            "x86_64-unknown-uefi",
            "--manifest-path",
        ])
        .arg(&fixture)
        .status()
        .map_err(|e| format!("spawn cargo for the efistub: {e}"))?;
    if !status.success() {
        return Err(
            "compiling the efistub failed — run `rustup target add x86_64-unknown-uefi` on \
             THIS dev box (the toolchain is only needed here, once)"
                .to_string(),
        );
    }
    let built = fixture
        .parent()
        .unwrap()
        .join("target/x86_64-unknown-uefi/release/bootx64.efi");
    let dst = workspace_root
        .join("dwarves-gui/src/embedded")
        .join(dst_name);
    std::fs::create_dir_all(dst.parent().unwrap())
        .map_err(|e| format!("create embedded dir: {e}"))?;
    std::fs::copy(&built, &dst)
        .map_err(|e| format!("copy {} → {}: {e}", built.display(), dst.display()))?;
    Ok(dst)
}

/// `cargo xtask build …` — resolve `(suite, variant)` against the declaration and build.
///
/// # Why this links `nornir-build-thing` and does NOT shell `dwarves buildthing`
///
/// `dwarves buildthing <product>` is the FORGE front-end onto the very same
/// [`nornir_build_thing::suite_cli::run`], so there is exactly ONE implementation of
/// the build logic (LAW 5) with two thin doors onto it. Which door belongs here is
/// decided by a fact special to this repo: **`dwarves` is the product being built.**
/// An xtask that shelled the `dwarves` binary would need dwarves already built and
/// installed in order to build dwarves — a bootstrap knot with no base case, and one
/// that would make a fresh clone unbuildable. Linking the dep-light leaf has no such
/// problem and no `dwarves` process anywhere in the loop.
///
/// The leaf is also the only legal choice constellation-wide: `nornir` may not gain a
/// crate edge on the forge (`nornir/tests/no_dwarves_edge.rs`), so if the xtasks were
/// to LINK anything it could not be dwarves. Keeping all five repos on the same door
/// is what makes `cargo xtask build --suite ORIGINAL` mean one thing everywhere.
fn run_build(args: &[String]) -> i32 {
    let root = cfg().workspace_root;
    let parsed = match nornir_build_thing::suite_cli::parse(args) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("dwarves xtask build: {e:#}");
            return 2;
        }
    };
    match nornir_build_thing::suite_cli::run(&root, &parsed) {
        Ok(built) => {
            // `--list` RESOLVED and printed; it built nothing. Saying "built" there
            // would be exactly the small lie this whole pass is about.
            if !parsed.list && !built.is_empty() {
                println!("dwarves xtask build: built {}", built.join(", "));
            }
            0
        }
        Err(e) => {
            eprintln!("dwarves xtask build FAILED: {e:#}");
            1
        }
    }
}

fn main() {
    let argv: Vec<String> = std::env::args().skip(1).collect();
    let verb = std::env::args().nth(1);
    match verb.as_deref() {
        Some("build") => std::process::exit(run_build(&argv[1..])),
        Some("ci") => std::process::exit(ci::run(&argv[1..])),
        Some("build-history") => std::process::exit(build_history::run(&argv[1..])),
        // `cargo xtask open-crates` — the open crates through `dwarves release` (A28 M27).
        Some("open-crates") => std::process::exit(xtask::open_crates::run(&cfg().workspace_root, &argv[1..])),
        // `cargo xtask build-flow-svg` — the audited build flow, drawn from the
        // data model in `build_flow_svg::STEPS` (each box carries the file:line
        // it was read at). Same idiom as gunnar's `cargo xtask svg`.
        Some("build-flow-svg") => match build_flow_svg::run(&cfg().workspace_root) {
            Ok(dst) => {
                println!("\n✔ build-flow-svg: {}", dst.display());
                std::process::exit(0)
            }
            Err(e) => {
                eprintln!("build-flow-svg: {e}");
                std::process::exit(1)
            }
        },
        Some("demo-wasm") => match declared(&cfg().workspace_root).and_then(|set| {
            nornir_pages::demo_wasm(&cfg(), &set, DEMO_VARIANT).map_err(|e| e.to_string())
        }) {
            Ok(dist) => {
                println!(
                    "\n✔ demo-wasm: dwarves-gui wasm bundle → {}",
                    dist.display()
                );
            }
            Err(e) => {
                eprintln!("\n✗ demo-wasm failed: {e}");
                std::process::exit(1);
            }
        },
        Some("deploy-pages") => match declared(&cfg().workspace_root).and_then(|set| {
            nornir_pages::deploy_pages(&cfg(), &set, DEMO_VARIANT).map_err(|e| e.to_string())
        }) {
            Ok(sha) => {
                println!("\n✔ deploy-pages: dwarves-gui published to Codeberg Pages (pages commit {sha})");
            }
            Err(e) => {
                eprintln!("\n✗ deploy-pages failed: {e}");
                std::process::exit(1);
            }
        },
        Some("gen-installer-stub") => match gen_installer_stub(&cfg().workspace_root) {
            Ok(dst) => println!(
                "\n✔ gen-installer-stub: embedded korp-installer bootx64.efi → {}",
                dst.display()
            ),
            Err(e) => {
                eprintln!("\n✗ gen-installer-stub failed: {e}");
                std::process::exit(1);
            }
        },
        Some("gen-start-stub") => match gen_start_stub(&cfg().workspace_root) {
            Ok(dst) => println!(
                "\n✔ gen-start-stub: embedded korp-start bootx64.efi → {}",
                dst.display()
            ),
            Err(e) => {
                eprintln!("\n✗ gen-start-stub failed: {e}");
                std::process::exit(1);
            }
        },
        Some("gen-boot-stage1") => match gen_boot_stage1(&cfg().workspace_root) {
            Ok(dst) => println!(
                "\n✔ gen-boot-stage1: embedded PRODUCTION first-stage korp-boot → {}",
                dst.display()
            ),
            Err(e) => {
                eprintln!("\n✗ gen-boot-stage1 failed: {e}");
                std::process::exit(1);
            }
        },
        Some("gen-live-stub") => match gen_live_stub(&cfg().workspace_root) {
            Ok(dst) => println!(
                "\n✔ gen-live-stub: embedded korp-live bootx64.efi → {}",
                dst.display()
            ),
            Err(e) => {
                eprintln!("\n✗ gen-live-stub failed: {e}");
                std::process::exit(1);
            }
        },
        Some("docs") => {
            #[cfg(feature = "docs")]
            {
                docs::run();
            }
            #[cfg(not(feature = "docs"))]
            {
                eprintln!(
                    "xtask: the `docs` verb needs the `docs` feature (nornir's typst \
                     renderer). Run it via the dedicated alias:\n\n    cargo xtask-docs docs\n"
                );
                std::process::exit(2);
            }
        }
        other => {
            if let Some(v) = other {
                eprintln!("xtask: unknown verb `{v}`");
            }
            eprintln!("dwarves xtask — build automation for the live web demo");
            eprintln!();
            eprintln!("usage: cargo xtask <verb>");
            eprintln!();
            eprintln!("verbs:");
            eprintln!("  build          THE build verb — resolve (suite, variant) against");
            eprintln!("                 .nornir/build-variants.toml and build it:");
            eprintln!(
                "                   cargo xtask build --suite ORIGINAL [--variant <v>] [--list]"
            );
            eprintln!("  ci             the CI plan: cargo xtask ci [--quick|--nightly] [--list];");
            eprintln!("                 cargo xtask ci lockcheck --siblings <dir> (THE LOCK CHECK);");
            eprintln!("                 cargo xtask ci status --runs <dir> [--out STATUS.md]");
            eprintln!("  build-history  seal / verify / regenerate the znippy-build-thing");
            eprintln!("                 package — step 3, after the build and BEFORE release:");
            eprintln!(
                "                   cargo xtask build-history seal --package h.znippy <bin>…"
            );
            eprintln!(
                "                   cargo xtask build-history verify --package h.znippy <bin>…"
            );
            eprintln!(
                "                   cargo xtask build-history show --package h.znippy [--recipe k]"
            );
            eprintln!("  open-crates    the open crates a lone clone of Holger needs, on crates.io");
            eprintln!("                 through `dwarves release` (a PLAN unless named):");
            eprintln!("                   cargo xtask open-crates [--siblings <dir>] [--live-dry-run | --execute]");
            eprintln!("  gen-installer-stub  rebuild the EMBEDDED korp-installer bootx64.efi");
            eprintln!("                 (dev-box only; the mastered ISO needs no toolchain)");
            eprintln!("  gen-start-stub  rebuild the EMBEDDED korp-start bootx64.efi (2nd-boot");
            eprintln!("                 start-app; dev-box only, the installed disk needs none)");
            eprintln!("  gen-boot-stage1 rebuild the EMBEDDED PRODUCTION first-stage loader");
            eprintln!("                 (korp-boot: EFI/BOOT/BOOTX64.EFI → chainloads \\vmlinuz)");
            eprintln!("  gen-live-stub   rebuild the EMBEDDED korp-live bootx64.efi (live-ISO)");
            eprintln!("  demo-wasm      build the dwarves-gui release wasm bundle into dist/");
            eprintln!("  deploy-pages   build, then force-push dist/ to the `pages` branch");
            eprintln!("  docs           render .nornir/*.md → the typeset SVG book the Manual");
            eprintln!("                 pane embeds (needs `cargo xtask-docs docs`)");
            std::process::exit(2);
        }
    }
}