//! 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")
}