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

nordisk/facett

xtask/src/door.rs

//! **`cargo xtask build [--variant <name>] [--list]` — the thin door.**
//!
//! THE LAW (workspace, 2026-09): *xtask is a THIN DOOR onto the build thing, never a
//! second build system.* This module owns no package name, no feature string, no
//! triple and no list of rows. It does four things, each through the engine the
//! recipe re-exports (`facett_build_thing::nornir_build_thing`):
//!
//! 1. **resolves** the ask against the CODE — [`facett_build_thing::declared`], never
//!    the emitted `.nornir/build-variants.toml` — with edda's shared
//!    `suite_cli::parse`/`resolve`, so the flags and refusals read the same in every
//!    repo;
//! 2. **builds** every cargo row IN THE PINNED `[build.toolchain]` IMAGE with
//!    `cargo_container::build_variant_in_container` over `DraupnirCargoRunner` —
//!    the forge's own argv (`cargo_args()`), `--locked --offline`, network none. There
//!    is no host road: "a build thing builds in a container or a KVM, never on the
//!    host" (see [`Road`]);
//! 3. **runs the typed stages** a `[[build.wasm]]` cell declares (wasm-bindgen, the
//!    component manifest) with edda's `wasm::run_stages`, IN the same pinned image
//!    (edda's `container_road::ContainerStages`, not the host-side
//!    `XtaskWasmStageRunner`), invoked from typed fields;
//! 4. **refuses BY NAME** every row cargo cannot build, with the door that does
//!    ([`facett_build_thing::door_for`]).
//!
//! ## What it replaced
//!
//! Until 2026-09-29 `build` was `suite_cli::run`, which loads the declaration with
//! `BuildVariantSet::discover_validated` — i.e. it READ THE EMITTED TOML, the
//! description, as authority — and answered a container row with "skipped (use the
//! image/vm/iso builders)". And `wrap-wasm` spelled its own
//! `cargo build --release --lib -p facet-wrapped --target wasm32-unknown-unknown`,
//! its own artifact path and its own `wasm-bindgen --target web --no-typescript`
//! beside a recipe that declared all three. See `.nornir/xtask-thin-door-audit.md`.

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

use facett_build_thing::nornir_build_thing::{
    self as nbt,
    suite_cli::{self, BuildArgs},
    variant::{BuildVariant, BuildVariantSet},
};

/// A resolved ask: the parsed flags, the delivery, and the rows it selects.
pub struct Resolved<'a> {
    pub args: BuildArgs,
    pub suite: &'a str,
    pub chosen: Vec<&'a BuildVariant>,
}

/// The refusal for one row cargo cannot build, naming it and its door.
pub fn refusal(v: &BuildVariant) -> Option<String> {
    facett_build_thing::door_for(v)
        .map(|door| format!("'{}' is kind '{}', not a cargo build — {door}", v.name, v.kind.as_str()))
}

/// **Resolve `argv` against `set`** — parse, refuse what this door does not do, and
/// select rows. Asking for ONE row cargo cannot build (without `--list`) is refused
/// here BY NAME, before anything compiles.
pub fn resolve<'a>(set: &'a BuildVariantSet, argv: &[String]) -> Result<Resolved<'a>, String> {
    let args = suite_cli::parse(argv).map_err(|e| format!("{e:#}"))?;
    if !args.builds.is_empty() {
        return Err(format!(
            "--build {}: this door builds facett's own rows; chaining a build thing behind an \
             edge is the forge's (`dwarves buildthing facett …`)",
            args.builds.join(" --build ")
        ));
    }
    if !args.params.is_empty() {
        return Err("--param supplies install-time inputs for iso/installer cells; facett \
                    declares none, and `build` materializes cargo rows"
            .to_string());
    }
    let (suite, chosen) = suite_cli::resolve(set, &args).map_err(|e| format!("{e:#}"))?;
    if !args.list && args.atom.is_some() && let Some(r) = chosen.first().and_then(|v| refusal(v)) {
        return Err(format!("refused: {r}"));
    }
    Ok(Resolved { args, suite, chosen })
}

/// The `--list` rendering: edda's shared lines, with every non-cargo row's
/// "(not a cargo build …)" line replaced by its NAMED door.
pub fn list_lines(r: &Resolved<'_>) -> Vec<String> {
    let mut lines = suite_cli::list_lines(r.suite, &r.chosen);
    // `list_lines` is a header, then exactly two lines per row, in `chosen` order.
    for (i, v) in r.chosen.iter().enumerate() {
        if let (Some(door), Some(line)) = (facett_build_thing::door_for(v), lines.get_mut(2 + 2 * i)) {
            *line = format!("      (not a cargo build — REFUSED here; {door})");
        }
    }
    lines
}

/// **The build environment, resolved and entered ONCE** — edda's
/// `nornir_build_thing::container_road::Road`: the pinned `[build.toolchain]` image,
/// the host paths it binds, and the engine. There is NO host fallback: a box that
/// cannot enter the image hears so by name, before the first row is touched.
///
/// It lived here (and its stage runner in `xtask/src/stages.rs`) until 2026-09-30,
/// when nornir and dwarves wanted the same road for their Pages demos; it moved to
/// edda whole, so the three repos build their wasm rows with one writer.
pub use nbt::container_road::Road;

/// **The declared toolchain, validated**, with the one run-time cap a caller may
/// apply (`CARGO_BUILD_JOBS` may only LOWER the container's cpu bound). Refuses BY
/// NAME: no `[build.toolchain]`, and every `CargoToolchain::validate` arm.
pub fn toolchain(set: &BuildVariantSet) -> Result<nbt::cargo_container::CargoToolchain, String> {
    nbt::container_road::toolchain(set).map_err(|e| format!("{e:#}"))
}

/// **Enter the road** — ask the engine for the PINNED reference; mint only when it
/// is absent (see `container_road::Road::enter`).
pub fn enter(set: &BuildVariantSet, root: &Path) -> Result<Road, String> {
    Road::enter(set, root).map_err(|e| format!("{e:#}"))
}

/// **Build ONE cargo row IN THE CONTAINER** — refused BY NAME with facett's door when
/// cargo cannot build it, else `container_road::build_row`: the forge's argv via
/// `build_variant_in_container`, then the row's `[[build.wasm]]` stages in the same
/// image (`container_road::ContainerStages`). Returns the files produced.
pub fn build_one(set: &BuildVariantSet, v: &BuildVariant, root: &Path, road: &Road) -> Result<Vec<PathBuf>, String> {
    if let Some(r) = refusal(v) {
        return Err(format!("refused: {r}"));
    }
    nbt::container_road::build_row(set, v, root, road).map_err(|e| format!("{e:#}"))
}

/// **The whole verb.** Returns the rows BUILT (or, with `--list`, resolved).
pub fn run(root: &Path, argv: &[String]) -> Result<Vec<String>, String> {
    let set = facett_build_thing::declared();
    // The code road validates what the toml road validated at load — every shape
    // rule and the on-disk inputs — so reading the code costs no check.
    set.validate_all_in(root).map_err(|e| format!("facett-build-thing is refused: {e:#}"))?;
    let r = resolve(&set, argv)?;
    if r.args.list {
        for l in list_lines(&r) {
            println!("{l}");
        }
        return Ok(r.chosen.iter().map(|v| v.name.clone()).collect());
    }
    let (cargo_rows, refused): (Vec<&BuildVariant>, Vec<&BuildVariant>) =
        r.chosen.iter().copied().partition(|v| refusal(v).is_none());
    if !refused.is_empty() {
        eprintln!("not cargo builds, REFUSED — each named with the door that does build it:");
        for v in &refused {
            eprintln!("  · {}", refusal(v).unwrap_or_default());
        }
    }
    if cargo_rows.is_empty() {
        return Err(format!(
            "delivery '{}': nothing was built — every selected row is refused above. That is not a green.",
            r.suite
        ));
    }
    // Entered ONCE, before the first row: a box that cannot enter it hears so here.
    let road = enter(&set, root)?;
    let mut built = Vec::new();
    for v in cargo_rows {
        for p in build_one(&set, v, root, &road)? {
            let n = std::fs::metadata(&p).map(|m| m.len()).unwrap_or(0);
            println!("  {} → {} ({n} bytes)", v.name, p.display());
        }
        built.push(v.name.clone());
    }
    Ok(built)
}