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

nordisk/monetize

xtask/src/clippy.rs

//! `cargo xtask clippy` — the lint gate: every crate monetize builds,
//! `--all-targets`, `-D warnings`.
//!
//! | question | answer |
//! |---|---|
//! | what does it lint? | the workspace, then each DETACHED crate (a `crates/*/Cargo.toml` with a `[workspace]` table of its own: monetize-admin, monetize-fast, monetize-build-thing) by `--manifest-path` |
//! | why the detached ones apart? | `--workspace` does not reach them, so a lint there was never seen: 2026-10-10 the toolchain bump to 1.97.1 left 117 in the workspace and 12 more in the detached three, unseen because nothing ran this |
//! | what does it need? | the sibling checkouts beside this one (`../edda`, `../Skidbladnir`, `../holger`), as every build of monetize does, and clippy for the pinned toolchain (`rustup component add clippy --toolchain 1.97.1`) |
//! | why not a hosted CI job? | a hosted runner has none of the private siblings, and a job that skips when they are absent is a check that can never fail. The gate is this verb; whoever builds monetize runs it |
//!
//! The fix for a lint is a fix in the code. A targeted `#[allow]` is for
//! generated code only (the `tonic` include modules carry theirs); a crate-
//! or workspace-wide allow turns this gate off without saying so.

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

/// The crates under `crates/` that are workspaces of their own, sorted.
///
/// Found rather than listed, so a new detached crate is gated the day it lands
/// instead of the day somebody remembers to add it here.
pub fn detached_crates(repo_root: &Path) -> Vec<PathBuf> {
    let mut out: Vec<PathBuf> = std::fs::read_dir(repo_root.join("crates"))
        .into_iter()
        .flatten()
        .flatten()
        .map(|e| e.path().join("Cargo.toml"))
        .filter(|m| std::fs::read_to_string(m).is_ok_and(|t| t.lines().any(|l| l.trim() == "[workspace]")))
        .collect();
    out.sort();
    out
}

/// The clippy runs, in order: the workspace first, then each detached crate.
pub fn runs(repo_root: &Path) -> Vec<Vec<String>> {
    let tail = ["--all-targets", "--", "-D", "warnings"].map(String::from);
    let mut runs = vec![["clippy", "--workspace"].map(String::from).into_iter().chain(tail.clone()).collect()];
    for m in detached_crates(repo_root) {
        runs.push(["clippy".to_string(), "--manifest-path".to_string(), m.display().to_string()].into_iter().chain(tail.clone()).collect());
    }
    runs
}

/// Runs every clippy run, all of them even after a red one, and names the red.
pub fn run(repo_root: &Path) -> Result<usize, String> {
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
    let all = runs(repo_root);
    let mut red = Vec::new();
    for args in &all {
        eprintln!("\n── cargo {}", args.join(" "));
        let ok = std::process::Command::new(&cargo)
            .args(args)
            .current_dir(repo_root)
            .status()
            .map_err(|e| format!("`{cargo}` could not start: {e}"))?
            .success();
        if !ok {
            red.push(args[1..args.len() - 4].join(" "));
        }
    }
    if red.is_empty() {
        Ok(all.len())
    } else {
        Err(format!("clippy is red in: {}", red.join("; ")))
    }
}