#!/usr/bin/env bash # Format-checks every Cargo workspace root in the repo, from any directory. # `cargo fmt --all` reaches only ONE of them. Reasoning: context/test-evidence/ # tooling.md, "The two-workspace fmt gate" -- including why `-p` scoped by `--manifest-path` is the only # spelling that is neither blind nor boundary-crossing (improvise-b4ks). set -uo pipefail usage() { cat >&2 <<'EOF' usage: fmt-workspaces.sh [--list] [--fix] [repo-root] repo-root the tree to scan; defaults to the directory above this script --list print each workspace root and its members, then exit --fix rewrite the offending files instead of reporting them exit 0 every workspace root is formatted exit 1 rustfmt did not certify a root clean: a file differs from rustfmt's output, or rustfmt refused to parse one. Either way a FILE IS NAMED and the tree is what is wrong. exit 2 the gate did not run: usage error, no cargo, no rustfmt, cargo metadata failed, no [workspace] root found, or a [package] that no root has as a member. NO verdict was produced. No SOURCE file is named; the unreached-package case names its Cargo.toml. The split is "did the gate run", not "is every exit-1 cause a diff". That is narrower than it sounds and it is deliberate -- see the note on rc=1 below. Exit 2 exists because `cargo fmt -p opfs-contract -- --check` from the repo root exits 1 with `not a member of the workspace`, having checked nothing and named nothing, which a caller reading $? cannot tell from a real failure. Never collapse 2 into 1. EOF } # `[workspace]` at the start of a line, in a Cargo.toml that is not vendored # build output. `harness/opfs-contract` carries an empty one, so it is its own # root and the outer `--all` never reaches it. # # The find is RELATIVE to the tree being scanned: an absolute `-path` filter # matches the ancestors too, and this repo is itself checked out under # `.claude/worktrees/` in every agent worktree, where that excluded everything. manifests_with() { local root="$1" section="$2" ( cd "$root" && find . -name Cargo.toml \ -not -path './target/*' -not -path '*/target/*' \ -not -path './.git/*' \ -not -path '*/node_modules/*' \ -not -path './.claude/*' ) \ | sort \ | while IFS= read -r rel; do if grep -q "^\\[$section\\]" "$root/${rel#./}"; then printf '%s\n' "$root/${rel#./}" fi done } workspace_roots() { manifests_with "$1" workspace; } # The members of ONE workspace, named by cargo rather than parsed out of the # manifest: a `members = [...]` list may carry globs and path dependencies. members_of() { local manifest="$1" meta meta="$(cargo metadata --manifest-path "$manifest" --no-deps --format-version 1 2>/dev/null)" || return 1 printf '%s' "$meta" | python3 -c ' import json, sys doc = json.load(sys.stdin) rows = [p["name"] + "\t" + p["manifest_path"] for p in doc["packages"]] if not rows: sys.exit(1) print("\n".join(rows)) ' || return 1 } main() { local list=0 mode=--check root="" while [ $# -gt 0 ]; do case "$1" in --list) list=1 ;; --fix) mode=--fix ;; -h|--help) usage; return 2 ;; -*) printf 'fmt-workspaces: unknown option %s\n' "$1" >&2; usage; return 2 ;; *) if [ -n "$root" ]; then printf 'fmt-workspaces: at most one repo-root, got %s and %s\n' "$root" "$1" >&2 return 2 fi root="$1" ;; esac shift done if [ -z "$root" ]; then root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; fi if [ ! -d "$root" ]; then printf 'fmt-workspaces: not a directory: %s\n' "$root" >&2 return 2 fi command -v cargo >/dev/null 2>&1 || { echo 'fmt-workspaces: cargo is not on PATH' >&2; return 2; } # Without this, a missing rustfmt component exits 1 from `cargo fmt` and # reads as "your code is misformatted" -- the very confusion this exists for. cargo fmt --version >/dev/null 2>&1 || { echo 'fmt-workspaces: cargo fmt is unavailable (rustfmt component missing?)' >&2 return 2; } local -a roots=() while IFS= read -r m; do [ -n "$m" ] && roots+=("$m"); done < <(workspace_roots "$root") if [ ${#roots[@]} -eq 0 ]; then printf 'fmt-workspaces: no [workspace] root under %s\n' "$root" >&2 return 2 fi # Pass 1: every root's members, before anything is formatted, so that a # package NO root reaches is exit 2 rather than silently never checked. local rc=0 manifest member i m local -a members=() covered=() for i in "${!roots[@]}"; do local rows rows="$(members_of "${roots[$i]}")" || rows="" if [ -z "$rows" ]; then printf 'fmt-workspaces: cargo metadata failed for %s\n' "${roots[$i]}" >&2 return 2 fi members[$i]="$(printf '%s\n' "$rows" | cut -f1 | tr '\n' ' ')" while IFS= read -r m; do covered+=("$m"); done < <(printf '%s\n' "$rows" | cut -f2) done local orphans orphans="$(manifests_with "$root" package | python3 -c ' import os, sys covered = {os.path.realpath(p) for p in sys.argv[1:]} for line in sys.stdin: m = line.rstrip("\n") if m and os.path.realpath(m) not in covered: print(m) ' "${covered[@]}")" if [ -n "$orphans" ]; then printf 'fmt-workspaces: no workspace root reaches these packages, so nothing checks them:\n%s\n' \ "$orphans" >&2 return 2 fi for i in "${!roots[@]}"; do manifest="${roots[$i]}" local -a pkgs=() read -r -a pkgs <<< "${members[$i]}" if [ "$list" = 1 ]; then printf '%s: %s\n' "$manifest" "${pkgs[*]}" continue fi # `-p` for every member, scoped by this root's own manifest. NOT `--all`, # which walks PAST a nested workspace boundary and reports the outer tree's # files under the inner root's name; and not the bare manifest-path form, # which silently checks only the root package and misses every member. # `|| rc=1` deliberately does NOT try to separate a diff from a source # rustfmt could not parse: both exit 1 and both NAME A FILE, so both are # facts about the tree. Measured: an unclosed delimiter exits 1, and an # unknown rustfmt.toml option is only a warning (exit 0). What exit 2 is # reserved for is "the gate did not run", and every such cause is checked # ABOVE this loop. Reasoning: context/test-evidence/tooling.md. local -a flags=() for member in "${pkgs[@]}"; do flags+=(-p "$member"); done if [ "$mode" = --fix ]; then cargo fmt --manifest-path "$manifest" "${flags[@]}" || rc=1 else cargo fmt --manifest-path "$manifest" "${flags[@]}" -- --check || rc=1 fi done return $rc } main "$@"