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

nordisk/facett

xtask/tests/one_dependency.rs

//! **xtask has ONE dependency: the recipe** (THE LAW: "xtask is a THIN DOOR onto
//! the build thing … ONE dependency in xtask/Cargo.toml: the repo's own recipe").
//!
//! Until 2026-09-30 it had twelve, then eleven: the verbs that build nothing
//! (publish-pip, publish-holger, verify-install, streamlit, test-demo, demo-wasm,
//! deploy-pages, panel-wasm) pulled draupnir, zip, sha2, base64, 4× znippy,
//! nornir-pages, nornir-xtask-support and facett-wasm-demo. They moved to
//! `facett-ops` (`cargo ops <verb>`); see `.nornir/xtask-thin-door-audit.md` §7.
//!
//! Held twice, because the two readers fail differently: the MANIFEST (every
//! `[dependencies]` / `[target.*.dependencies]` table — a target-gated second dep is
//! still a second dep), and cargo's own resolved graph (`cargo tree -p xtask -e normal
//! --depth 1`, the owner's measurement), which also catches a dep smuggled in by
//! some other spelling.

use std::path::PathBuf;

fn root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}

/// The one dependency the law allows.
const THE_RECIPE: &str = "facett-build-thing";

/// Every normal dependency the manifest names, across every normal-deps table.
fn manifest_deps() -> Vec<String> {
    parse_deps(&std::fs::read_to_string(root().join("xtask/Cargo.toml")).unwrap())
}

/// The normal dependencies a manifest text names.
fn parse_deps(text: &str) -> Vec<String> {
    let mut deps = Vec::new();
    let mut in_normal = false;
    for line in text.lines() {
        let t = line.trim();
        if t.starts_with('[') {
            let h = t.trim_matches(|c| c == '[' || c == ']');
            // `[dependencies]`, `[target.'cfg(…)'.dependencies]`; not dev/build.
            in_normal = h == "dependencies" || (h.starts_with("target.") && h.ends_with(".dependencies"));
            // `[dependencies.foo]` spells one dep as a table.
            if let Some(name) = h.strip_prefix("dependencies.") {
                deps.push(name.to_string());
            }
            continue;
        }
        if in_normal && !t.is_empty() && !t.starts_with('#') && let Some((name, _)) = t.split_once('=') {
            deps.push(name.trim().to_string());
        }
    }
    deps
}

#[test]
fn the_manifest_names_the_recipe_and_nothing_else() {
    let deps = manifest_deps();
    assert_eq!(
        deps,
        vec![THE_RECIPE.to_string()],
        "xtask/Cargo.toml must name ONE normal dependency, the recipe. A verb that needs \
         more is not a build — it belongs in facett-ops (`cargo ops <verb>`)."
    );
}

#[test]
fn cargo_tree_shows_exactly_one_normal_dependency() {
    let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
    let out = std::process::Command::new(cargo)
        .current_dir(root())
        .args(["tree", "-p", "xtask", "-e", "normal", "--depth", "1", "--prefix", "depth", "--offline"])
        .output()
        .expect("spawn cargo tree");
    let text = String::from_utf8_lossy(&out.stdout);
    assert!(out.status.success(), "cargo tree failed:\n{}", String::from_utf8_lossy(&out.stderr));
    // `--prefix depth`: `0xtask v…` then one `1<name> v…` line per direct dep.
    let direct: Vec<&str> = text
        .lines()
        .filter_map(|l| l.strip_prefix('1'))
        .map(|l| l.split_whitespace().next().unwrap_or(""))
        .collect();
    assert_eq!(direct, vec![THE_RECIPE], "cargo tree -p xtask -e normal --depth 1:\n{text}");
}

/// The red half: the parser actually sees a second dep, in each spelling a manifest
/// can use — a guard that could not go red would hold nothing.
#[test]
fn a_second_dependency_would_be_seen() {
    let parse = parse_deps;
    assert_eq!(parse("[dependencies]\na = \"1\"\nb = \"1\"\n[dev-dependencies]\nc = \"1\"\n"), ["a", "b"]);
    assert_eq!(parse("[dependencies]\na = \"1\"\n[target.'cfg(unix)'.dependencies]\nz = \"1\"\n"), ["a", "z"]);
    assert_eq!(parse("[dependencies]\na = \"1\"\n[dependencies.q]\nversion = \"1\"\n"), ["a", "q"]);
}