jacquardSnapshot

← snapshot

3605 bytes
//! Repository automation, invoked as `cargo xtask <command>`.
//!
//! Everything here is a control, not a convenience. `boundary` is the
//! graph half of the privacy argument — the rendezvous plane can never
//! transitively reach a content-plane crate; `lint-layout` is the independent
//! backstop for the module-layout rule that `clippy::mod_module_files` is
//! supposed to enforce but has known gaps in (rust-clippy#8123).

use std::path::Path;
use std::process::{Command, ExitCode};

use anyhow::{Context as _, Result, bail};
use clap::{Parser, Subcommand};

mod boundary;
mod layout;

#[derive(Parser, Debug)]
#[command(name = "xtask", about = "jacquard repository automation")]
struct Cli {
    #[command(subcommand)]
    command: Cmd,
}

#[derive(Subcommand, Debug)]
enum Cmd {
    /// Prove the trust-plane rules hold over the dependency graph.
    Boundary,
    /// Enforce module layout: `foo.rs` + `foo/`, never `foo/mod.rs`.
    LintLayout,
    /// Run the full local gate: layout, boundary, fmt, clippy, test.
    Ci,
}

fn main() -> ExitCode {
    let cli = Cli::parse();
    let result = match cli.command {
        Cmd::Boundary => boundary::run(),
        Cmd::LintLayout => layout::run(),
        Cmd::Ci => ci(),
    };

    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("xtask: {e:#}");
            ExitCode::FAILURE
        }
    }
}

fn ci() -> Result<()> {
    layout::run()?;
    boundary::run()?;

    // Nightly applies the import-grouping options in rustfmt.toml; stable
    // formats everything else correctly and warns that it is ignoring them.
    // Prefer nightly when it is installed so local runs match CI exactly.
    //
    // `+toolchain` is a rustup directive, not a cargo argument, so it has to go
    // through the rustup shim rather than the `CARGO` binary.
    if nightly_available() {
        run(
            "rustup",
            &["run", "nightly", "cargo", "fmt", "--all", "--check"],
        )?;
    } else {
        eprintln!(
            "xtask: nightly not installed; `cargo fmt` will skip the import-grouping options. \
             Install it with `rustup toolchain install nightly` to match CI exactly."
        );
        cargo(&["fmt", "--all", "--check"])?;
    }

    cargo(&[
        "clippy",
        "--workspace",
        "--all-targets",
        "--",
        "-D",
        "warnings",
    ])?;
    cargo(&["test", "--workspace"])?;
    Ok(())
}

fn nightly_available() -> bool {
    Command::new("rustup")
        .args(["run", "nightly", "cargo", "--version"])
        .output()
        .is_ok_and(|o| o.status.success())
}

fn cargo(args: &[&str]) -> Result<()> {
    run(env!("CARGO"), args)
}

fn run(program: &str, args: &[&str]) -> Result<()> {
    eprintln!("xtask: {} {}", short(program), args.join(" "));
    let status = Command::new(program)
        .args(args)
        .current_dir(workspace_root()?)
        .status()
        .with_context(|| format!("failed to spawn `{} {}`", short(program), args.join(" ")))?;
    if !status.success() {
        bail!("`{} {}` failed", short(program), args.join(" "));
    }
    Ok(())
}

/// Trims an absolute program path down to its file name for log lines.
fn short(program: &str) -> &str {
    Path::new(program)
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or(program)
}

fn workspace_root() -> Result<&'static Path> {
    // `xtask/` is always a direct child of the workspace root.
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .context("xtask manifest has no parent directory")
}