Cargo already knows your workspace. Keep it authoritative.
Cargo-Rail turns Cargo's resolved graph into one monorepo engine. unify produces one dependency-coherence report and
mutation plan. plan emits typed affected scope for Cargo, nextest, Just, and CI. Verified compiler reuse survives
cargo clean. change and release turn reviewed changesets into version bumps, changelogs, dep-ordered publication,
and resumable exact-SHA releases. split and sync keep standalone crates tied to monorepo source and history.
One captured workspace. Explicit authority.
Rust monorepos usually acquire a tool for every symptom. Each tool reconstructs part of the same workspace, carries its own configuration, and makes decisions against a slightly different model.
Cargo-Rail consolidates those decisions in one local, Cargo-native engine:
| Fragmented workflow | Use | What changes |
|---|---|---|
| Dependency version, feature, unused-edge, inheritance, and MSRV analysis | cargo rail unify |
One reviewable and reversible graph-repair plan |
dorny/paths-filter, YAML path globs, and package-selection scripts |
cargo rail plan |
Typed affected scope derived from Git and the resolved Cargo graph |
Persisted target/ directories and local cache glue |
Verified compiler reuse | Exact reusable results remain available after cargo clean and across target directories within one physical source root |
release-plz, cargo-release, git-cliff, and publish-order scripts |
cargo rail change and cargo rail release |
Reviewed release intent carried through exact-SHA publication and recovery |
| Copybara or custom monorepo-to-crate scripts | cargo rail split and cargo rail sync |
Cargo-aware synchronization with source history and recovery evidence |
Cargo-Rail does not coordinate other tools behind the scenes. Its workflows derive decisions from the same captured Cargo and source authority.
flowchart TB
source["Git History + Cargo"] --> rail["Cargo-Rail"]
rail --> unify["unify<br/>coherent dependencies"]
rail --> plan["plan<br/>affected scope"]
rail --> cache["verified reuse<br/>compiler results"]
rail --> release["change / release<br/>exact-SHA publication"]
rail --> sync["split / sync<br/>crate repositories"]
No hosted service. No second build language. No hand-rolled crate maps.
Cargo, nextest, GitHub Actions, Just, and your CI workers remain in place. Cargo-Rail makes them do less work and consume fewer resources.
Install Cargo-Rail and inspect a real branch:
cargo install cargo-rail --locked
cargo rail plan --merge-base --explainplan is read-only. It does not execute selected work or edit tracked files.
Inspect the typed test scope, then pass its argument vector to nextest:
PLAN_JSON=$(cargo rail plan --merge-base -f json)
if [ "$(jq -r '.surfaces.test.enabled' <<<"$PLAN_JSON")" = "true" ]; then
CARGO_ARGS=()
while IFS= read -r argument; do
CARGO_ARGS+=("$argument")
done < <(jq -r '.surfaces.test.scope.cargo_args[]' <<<"$PLAN_JSON")
cargo nextest run -P commit --locked --config-file .config/nextest.toml "${CARGO_ARGS[@]}"
fiThe plan carries separate build and test scopes. Existing jobs can consume the same versioned contract through cargo-rail-action, JSON, or GitHub output; Cargo-Rail does not reinterpret Cargo or nextest arguments.
Dependency hygiene is not six unrelated lint problems. It is one graph-coherence problem.
cargo rail unify --check --explainOne pass detects and plans repairs for:
- dependency-version drift;
- hidden feature coupling;
- unused dependency edges;
- workspace-inheritance drift;
- MSRV mismatches; and
- optional host-owned pins for fragmented transitive features.
Graph-removing decisions carry compiler evidence. Cargo-Rail validates the resulting Cargo graph before applying lossless TOML edits.
Apply with a backup:
cargo rail unify --backupReview the resulting diff, or restore the latest backup:
cargo rail unify undoOne install. One configuration. One report. One explanation format. One rollback path.
That gives dependency changes one evidence model, review surface, and recovery path.
A path filter can tell you which directory changed. It cannot reliably tell you what the change affects.
Cargo-Rail interprets the change before selecting work:
changed source
→ semantic manifest and lockfile analysis
→ Cargo package ownership
→ reverse-dependency impact
→ build / test / docs / bench / infra / custom surfaces
→ surface-specific Cargo scope
→ typed Cargo arguments and CI gates
A formatting-only manifest edit can select no package work. A shared-library change can select its affected dependent closure. Infrastructure changes can gate repository-level jobs without pretending they belong to a crate.
When planning evidence is incomplete, Cargo-Rail widens the scope instead of guessing narrowly.
The same versioned plan drives:
- human explanations;
- JSON and GitHub output;
- surface gates; and
- typed Cargo package arguments.
There is only one implementation of “affected.”
Normal Cargo reuse is tied to the target directory. Remove that directory or run:
cargo cleanand that reuse is gone.
Cargo-Rail's local content-addressed store lives outside target/. Eligible compiler results remain available after
cargo clean and across target directories within the same physical source root. Cargo-Rail runs rustc with its
original arguments and binds the workspace root plus each unit's physical source namespace into the action. A moved or
independent checkout compiles cold and records its own exact variant instead of restoring path-bearing metadata from
another root.
Install the private user-wide launcher and authenticated cache worker once. Preview is read-only and exits 1 when setup or repair is pending:
cargo rail cache setup --check
cargo rail cache setup
cargo check --locked
cargo clean
cargo check --lockedThe same setup covers ordinary Cargo, nextest, Just, IDE, and CI invocations that use that Cargo home. Cargo freshness and incremental compilation remain the faster L0; eligible compiler work that L0 does not remove can use the verified local CAS as L1.
Cargo-Rail does not restore an old target directory or manufacture Cargo freshness. Before a hit, it revalidates:
- Cargo, rustc, rustdoc, sysroot, backend, host, and wrapper identity;
- the complete bounded source-directory topology and regular-file bytes;
- dependency artifacts;
- every compiler-visible environment name and value;
- rustc's selected-input containment proof;
- exact native-static search namespaces and generated inputs;
- on Linux linked actions, the default
ccdriver and its selected dependency-file-capable ELF linker, auxiliary tools, direct inputs, found and missing search candidates, exact dependency archives, and rustc-generated objects under the linked output directory; - action and result identity; and
- exact stored output bytes and regular-file modes.
The cache does not partition every compiler unit by the complete Cargo configuration or Cargo.lock. Output-neutral
changes such as warning policy, job count, build or target directory, network policy, registry settings, and unrelated
lockfile entries can reuse a verified result. Rust flags, features, dependency contents, target, linker, sysroot,
source topology, and compiler environment still change or reject reuse at their owning boundary.
A matching lookup is not enough. Incomplete or unsupported evidence produces a named bypass and runs normal Cargo. The pre-Clap compiler boundary rejects ambiguous wrapper roles and skips session/CAS acquisition for disabled, incremental, clippy, response-file, and other clearly unsupported invocations.
Metadata/rlib results, including metadata-only proc-macro producers, and exact native-static consumers use the direct
action protocol. On Linux, the installed default cc-selected ELF linker can also certify build-script executables,
proc-macro producer dylibs, ordinary final binaries, dylib, and cdylib outputs when it supplies GNU-compatible
dependency-file evidence. The pre-link action is only a candidate selector; a hit requires the complete witnessed
action and exact result pack. Native proc-macro
consumers remain cold because running a proc macro can observe ambient filesystem, environment, process, network,
clock, and randomness state that rustc does not certify.
Fast when proven. Normal Cargo when not.
CARGO_RAIL_CACHE=off cargo check --locked disables both L1 reads and writes for that process tree. The minimal launcher
directly executes the selected compiler chain without starting the cache worker or reading installation state.
Incremental, clippy, native proc-macro consumer, custom-target-layout, ambiguous-wrapper, custom-linker, and otherwise
unsupported invocations bypass before session or CAS acquisition. Existing workspace wrappers are preserved and
bypassed; conflicting global wrapper ownership makes setup fail.
The same one-time setup can persist an AWS S3, Azure Blob Storage, or Cloudflare R2 team authority beneath L1. Ordinary Cargo then uses a short-lived private coordinator to reuse credential resolution, the SDK client, and connections across rustc processes; it retains no build-result memory cache. Local hits make no remote request, and coordinator, remote, integrity, credential, or outage failures compile cold or use the qualified direct fallback. Repository configuration cannot select a destination. See Share compiler reuse across workspaces for URL, trust, read-only, and provider-qualification boundaries.
The transparent activation contract invalidates earlier pre-linked benchmark numbers. Its canonical five-sample
interleaved corpus measures intact-target overhead, cold-path overhead, and empty-target L1 against pinned
local sccache independently. A matrix cell qualifies only when all five samples pass the declared correctness and
coverage gates and warm L1 is strictly faster at both p50 and p95. Cargo-Rail makes no blanket superiority claim from a
failed cell. The exact correctness fixture remains independent of timing and verifies root isolation, environment
invalidation, diagnostic replay, output bytes, Cargo L0 behavior, cold publication, and warm reuse.
See Caching for the proof model and support matrix, and Benchmarking for the complete measurement scope and acceptance rules.
Most release automation tries to reconstruct intent after the code has already merged.
Cargo-Rail records it while the change is being reviewed:
cargo rail change add rail-core \
--bump minor \
--message "Added graph-aware release planning."The resulting .changes/*.md file lives beside the code change. Reviewers see the intended version bump and user-facing release note before merge—not weeks later when a release bot tries to infer them from commit messages.
CI can require release intent for every changed crate:
cargo rail change check --merge-base --requiredA release then carries that reviewed intent through the entire workflow:
code + reviewed .changes/*.md
│
▼
versions + changelogs
│
▼
exact release commit
│
▼
readiness on that SHA
│
▼
dependency-ordered publication
│
▼
registry observation + tags
│
▼
durable recovery state
Prepare a release pull request containing version and changelog updates:
cargo rail release run --all --bump auto --prAfter that exact release commit merges:
cargo rail release finalize --allCargo-Rail validates the release state, publishes crates in dependency order, observes registry results, and creates tags after publication.
Publication is authorized by the exact release commit—not by a moving branch head.
If publication is interrupted, the transaction is not left ambiguous:
cargo rail release status
cargo rail release resume <STATE>Changesets are not merely a nicer changelog format. They connect reviewed intent, workspace-aware versioning, publication order, the exact authorized SHA, and recoverable side effects.
Publishing a crate from a private monorepo usually creates another synchronization system—and eventually another source of truth.
Cargo-Rail keeps the split repository tied to its monorepo origin.
split extracts the relevant Git history and rewrites workspace-relative manifests:
cargo rail split run my-crate --check
cargo rail split run my-cratesync maps later commits in either direction:
cargo rail sync my-crate --to-remote
cargo rail sync my-crate --from-remoteInbound changes arrive on review branches. Synchronization uses Git's three-way merge, and manual conflicts are recorded in resumable receipts.
This is Cargo-aware crate synchronization, not a general-purpose repository-transformation language.
Cargo-Rail removes work in the order it appears:
unify
→ removes graph waste, hidden coupling, background resource consumption
plan
→ removes unaffected jobs
package scope
→ removes unaffected crates inside selected jobs
verified reuse
→ removes compiler invocations from the work that remains
These optimizations compound because they derive from the same captured source tree and resolved Cargo graph. The context is passed around... it's not re-computed.
Independent tools cannot compound as effectively: each optimization is bounded by its own approximation of the workspace.
Cargo-Rail treats speed as conditional and side effects as transactions:
- Plan before effect. Checks, explanations, schemas, and mutation previews expose decisions before effects.
- Widen instead of undertesting. Incomplete planning evidence selects more work rather than silently excluding required work.
- Run cold instead of trusting weak cache evidence. Unsupported or incomplete reuse cases execute normal Cargo.
- Revalidate before mutation. Snapshot-bound commands confirm their assumptions immediately before writing, publishing, or synchronizing.
- Preserve user-owned configuration. Manifest and configuration edits are lossless outside fields owned by the operation.
- Record recovery state. Backups, plans, receipts, and durable release or sync state remain available where recovery requires them.
Fast paths are used only when their proof boundary holds.
Each workflow is independently adoptable:
- Observe with
cargo rail plan --merge-base --explain. - Inspect each selected surface's typed
cargo_args. - Pass that argument vector to Cargo or nextest in existing jobs.
- Enforce read-only dependency and changeset checks.
- Apply graph repairs, release effects, or repository synchronization after the team trusts their plans and recovery boundaries.
Adopt a workflow when it deletes a second source of truth. Do not adopt it merely because Cargo-Rail implements it.
cargo install cargo-rail --lockedOr install a pre-built binary:
cargo binstall cargo-railPre-built archives, SHA-256 checksums, and signed provenance are published with GitHub Releases.
Release archives place Cargo-Rail's private compiler shim beside the CLI. Keep both files together when moving a manual installation; Cargo-Rail remains correct without the shim but falls back to the larger CLI executable for compiler-wrapper processes.
The current MSRV is published in
Cargo.toml.
- Planning
- Configuration reference
- Command reference
- Architecture
- Caching
- Share local compiler reuse across workspaces
- Benchmarking
- Troubleshooting and recovery
- Migrate from cargo-hakari
- Migrate from git-cliff or release-plz
- Examples
Cargo-Rail is licensed under MIT.