Skip to content

Repository files navigation

Cargo-Rail

Cargo already knows your workspace. Stop teaching it to ten other tools.

Cargo-Rail turns Cargo's resolved graph into one monorepo engine. unify replaces the dep-hygiene stack. plan and run execute only affected work. 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.

Keep Cargo. Delete the Rest.

Crates.io CI MSRV

One binary, not ten partial workspace models

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 replaces that collection with one local, Cargo-native engine:

Retire Use What changes
cargo-hakari, cargo-udeps, cargo-shear, cargo-machete, feature auditors, workspace-inheritance checks, and MSRV scripts cargo rail unify One reviewable and reversible graph-repair plan
dorny/paths-filter, YAML path globs, and package-selection scripts cargo rail plan and cargo rail run Affected work derived from Git and the resolved Cargo graph
Persisted target/ directories and local cache glue Verified compiler reuse Exact reusable results remain available across target directories in one source root and after cargo clean
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 those tools behind the scenes. It replaces the duplicated workspace models they require and shares the same contextual authority.

flowchart TB
    source["Git History + Cargo"] --> rail["Cargo-Rail"]

    rail --> unify["unify<br/>coherent dependencies"]
    rail --> plan["plan / run<br/>affected execution"]
    rail --> cache["verified reuse<br/>compiler results"]
    rail --> release["change / release<br/>exact-SHA publication"]
    rail --> sync["split / sync<br/>crate repositories"]
Loading

No hosted service. No second build language. No hand-rolled crate maps.

Cargo, nextest, GitHub Actions, your task runner, and your runners remain in place. Cargo-Rail just makes them do less work and consume fewer resources.

Proof First; Then Migrate

Install Cargo-Rail and inspect a real branch:

cargo install cargo-rail --locked
cargo rail plan --merge-base --explain

plan is read-only. It does not execute selected work or edit tracked files.

Preview the exact commands Cargo-Rail would run:

cargo rail run --merge-base --dry-run --print-cmd --explain

Then keep the rest of your toolchain and execute only affected CI work:

cargo rail run --merge-base --profile ci

The built-in ci profile runs selected build and test actions. Existing jobs can consume the same versioned plan through cargo-rail-action, JSON, or GitHub output.

One unify command, not a dependency-tool stack

Dependency hygiene is not six unrelated lint problems. It is one graph-coherence problem.

cargo rail unify --check --explain

One pass detects and plans repairs for:

  • dependency-version drift;
  • hidden feature coupling;
  • unused dependency edges;
  • workspace-inheritance drift;
  • MSRV mismatches; and
  • transitive feature unification normally handled through a generated workspace-hack crate.

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 --backup

Review the resulting diff, or restore the latest backup:

cargo rail unify undo

One install. One configuration. One report. One explanation format. One rollback path.

That is the real replacement for a directory full of Cargo plugins and maintenance scripts.

Affected CI is a graph query, not a path glob

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
  → validated commands and CI outputs

A formatting-only manifest edit can select no package work. A shared-library change can select its affected dependent closure. Infrastructure changes can select repository-level actions 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;
  • dry-run command previews;
  • direct execution; and
  • durable decision receipts.

There is only one implementation of “affected.”

Compiler reuse that survives cargo clean

Normal Cargo reuse is tied to the target directory. Remove that directory or run:

cargo clean

and 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 in the same physical source root. A different checkout uses an independent cache session because Rust metadata can embed the source root; Cargo-Rail does not claim unsafe cross-checkout portability.

On an eligible invocation, this sequence can still reuse exact compiler output:

cargo rail run --all --action build --explain

cargo clean

cargo rail run --all --action build --explain

Cargo-Rail does not restore an old target directory or manufacture Cargo freshness. Before a hit, it revalidates the relevant:

  • Cargo, rustc, rustdoc, sysroot, backend, host, and wrapper identity;
  • source and manifest inputs;
  • dependency artifacts;
  • observed environment reads;
  • action and result identity; and
  • exact stored output bytes.

The cache does not partition every compiler unit by the complete Cargo configuration or Cargo.lock. Within one physical source root, 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, and observed compiler inputs 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.

Fast when proven. Normal Cargo when not.

The retained v4 Linux and Windows x86-64 qualifications each measured one accepted same-host sample per lane:

Host Workload Cargo-Rail warm Native Cargo cold sccache 0.16.0 Warm vs Cargo Warm vs sccache
Linux Check 3.848 s 9.866 s 4.735 s 61.0% faster 18.7% faster
Linux Release build 6.055 s 12.445 s 7.918 s 51.3% faster 23.5% faster
Windows Check 9.744 s 19.500 s 12.847 s 50.0% faster 24.2% faster
Windows Release build 13.579 s 23.150 s 17.202 s 41.3% faster 21.1% faster

Linux process-tree accounting measured 25.3% less CPU and 66.1% less peak RSS than sccache for check, and 20.4% less CPU and 63.1% less peak RSS for release build. Windows retains wall-time comparisons but withholds specialist CPU and RSS claims because the detached sccache server is outside that host's complete accounting boundary.

Each complete interleaved run accepted all 20 planned lane samples with zero rejections and zero false hits. On both hosts and workloads, the measured cold-publication premium was smaller than the saving from one warm reuse. These are bounded point measurements for one machine and fixture, not percentile, distribution, or universal workspace claims.

See Caching for the proof model, support matrix, raw methodology, and current qualification boundaries.

Release intent belongs in the pull request

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 --required

A 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 --pr

After that exact release commit merges:

cargo rail release finalize --all

Cargo-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.

Split a crate without creating a second history

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-crate

sync maps later commits in either direction:

cargo rail sync my-crate --to-remote
cargo rail sync my-crate --from-remote

Inbound 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.

The savings compound

Cargo-Rail removes work in the order it appears:

unify
  → removes graph waste, hidden coupling, background resource consumption

plan
  → removes unaffected actions

package scope
  → removes unaffected crates inside selected actions

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.

Conservative where correctness matters

Cargo-Rail treats speed as conditional and side effects as transactions:

  • Plan before effect. Checks, explanations, schemas, and dry runs expose decisions before execution or mutation.
  • 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.
  • Keep execution explicit. Repository actions are validated direct-process argument vectors, not an embedded shell language.

Fast paths are used only when their proof boundary holds.

Adopt the deletion, not the entire product

Each workflow is independently adoptable:

  1. Observe with cargo rail plan --merge-base --explain.
  2. Compare with cargo rail run --dry-run --print-cmd.
  3. Delegate affected scope to existing jobs.
  4. Enforce read-only dependency and changeset checks.
  5. 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.

Installation

cargo install cargo-rail --locked

Or install a pre-built binary:

cargo binstall cargo-rail

Pre-built archives, SHA-256 checksums, and signed provenance are published with GitHub Releases.

The current MSRV is published in Cargo.toml.

Documentation

Project

Cargo-Rail is licensed under MIT.

Releases

Packages

Used by

Contributors

Languages