cargo-rail 0.2.0

Graph-aware testing, dependency unification, and crate extraction for Rust monorepos
Documentation

cargo-rail

Monorepo orchestration for Rust workspaces.

Crates.io License: MIT Rust 1.91+

Command Reference · Config Reference · Demo Videos

cargo install cargo-rail

Pre-built binaries available on the Releases Page.


TLDR

  • Only test what changed both locally and in CI: cargo rail test
  • Keep manifests clean and unified: cargo rail unify / cargo rail unify sync
  • Split + sync crates with full git history: cargo rail split / cargo rail sync
  • Release in dependency order with changelogs: cargo rail release
  • Minimal deps: 11 core dependencies; 77 resolved dependencies

Quick Start

Drop into any Rust workspace:

cargo install cargo-rail
cargo rail init                               # Initialize once
cargo rail config validate                    # Validate config
cargo rail unify --check                      # Dry-run 'unify'
cargo rail unify                              # Initial 'unify' automatically writes backup
cargo rail test                               # Auto-detect Nextest; native change-detection

See docs/commands.md for details.


Workflows

Change Detection & Testing

Graph-aware change detection. Only check/test/bench what's affected:

cargo rail affected                           # Show affected crates
cargo rail affected -f names-only             # Just names (for scripting)
cargo rail test                               # Run tests for affected crates
cargo rail test --all                         # Override and run the whole workspace

Wire it into your local workflow:

# scripts/check.sh (or justfile)
AFFECTED=$(cargo rail affected -f names-only 2>/dev/null || echo "")
if [ -z "$AFFECTED" ]; then
  cargo check --workspace --all-targets
else
  for crate in $AFFECTED; do FLAGS="$FLAGS -p $crate"; done
  cargo check $FLAGS --all-targets
fi

For CI, see cargo-rail-action.

Dependency Unification

Resolution-based [workspace.dependencies] management using Cargo's resolver:

cargo rail unify --check         # Preview changes (CI-safe, exits 1 if changes needed)
cargo rail unify                 # Apply changes
cargo rail unify --backup        # Apply with manifest backups
cargo rail unify undo            # Restore from a previous backup
cargo rail unify sync            # Re-detect targets and merge into rail.toml

What it does, per target triple (targets in rail.toml):

  • Unifies versions based on what Cargo actually resolved
  • Computes MSRV (dependencies) from the resolved graph ([workspace.package].rust-version)
  • Prunes dead features that are never enabled
  • Detects/removes unused deps (opt-in via config)
  • Optionally pins transitives (cargo-hakari or workspace-hack replacement)

Use unify sync in pre-commit hooks or CI to enforce a lean, consistent dependency graph or after adding new target triples to your CI matrix or .cargo/config.toml.

Split & Sync

Extract crates to standalone repos and keep them in sync, with deterministic SHAs.

Three modes: single crate to new repo, multiple crates to new repo, or multiple crates to a new workspace.

cargo rail split init crate                   # Configure split for crate/s
cargo rail split run crate --check            # Preview the split
cargo rail split run crate                    # Execute the split

cargo rail sync crate                         # Bidirectional sync
cargo rail sync crate --to-remote             # Monorepo -> split repo
cargo rail sync crate --from-remote           # Split repo -> monorepo (PR branch)

Split/sync behavior is driven by [crates.NAME.split] in rail.toml. 3-way conflict resolution is configurable in rail.toml once a split has been initialized. See docs/config.md

Release

Version bumping, changelog generation, tagging, and publishing in dependency order:

cargo rail release init crate                 # Configure release
cargo rail release check crate                # Fast validation
cargo rail release check crate --extended     # Dry-run + MSRV check
cargo rail release run crate --check          # Preview release plan
cargo rail release run crate --bump minor     # Execute release

Configure once in rail.toml:

[release]
tag_prefix = "v"
tag_format = "{crate}-{prefix}{version}"      # Adjust for standalone crate
require_clean = true

Demo Videos

I've tested across trusted Rust workspaces and recorded the command workflows end-to-end. All assets live under examples/.

https://github.com/user-attachments/assets/93f34633-aa0e-4cde-8723-c81f3f474bac

https://github.com/user-attachments/assets/520abf55-cf45-43af-8dc8-0eed0a58ce72

https://github.com/user-attachments/assets/31bf5ff5-7185-4e59-acaa-ea8edd3c6f48

https://github.com/user-attachments/assets/3520d254-e69c-460c-b894-eb126b42a1ea

https://github.com/user-attachments/assets/b9f56e77-de0a-42c1-b2ef-1a40bb24f5ac

https://github.com/user-attachments/assets/9c0b6df1-8539-44a0-9c82-a9fdca5e075c


Unification Results in Testing/Validation

cargo rail unify on a few different projects:

Repo Crates Deps Unified Member Edits
tikv 83 57 519
polars 33 2 13
meilisearch 19 46 210
helix 13 16 67
tokio 10 10 35
ripgrep 10 9 41
helix-db 6 16 44

Full demos and reports live under examples/ for most examples.


Potentially Replaces

Tool cargo-rail equivalent
cargo-hakari unify with pin_transitives = true
cargo-udeps / cargo-machete / cargo-shear unify with detect_unused = true / remove_unused = true
cargo-msrv (for dep-driven MSRV)* unify with msrv = true
cargo-release / release-plz release command
git-cliff Built-in changelog generation
Google's Copybara for Rust teams split + sync commands
Mountain of shell scripts test + affected + cargo-rail-action in CI

NOTE ON MSRV

The msrv=true configuration doesn't mean cargo-rail runs compilation checks. Instead, it computes the floor msrv for the workspace, and takes your own rust-version into consideration. This allows you to immediately determine the msrv for your workspace based on the dependencies you're using and your own rust-version.


Design Notes

I've built cargo-rail as a necessity for my own work and for that reason it's opinionated. I've explained the thought process behind it and touched on the plans for the future in this post: Coming Soon. I am absolutely hoping to get the community involved in improving the tooling here; contributions are welcome!

Supply-Chain Safety

This matters a great deal to me. I've tried to keep the tool itself lean; it deliberately avoids large meta-dependency graphs. Currently, cargo-rail depends on 11 core deps; 77 resolved in the release build.

Multi-Target Resolution

cargo-rail runs cargo metadata --filter-platform per target-triple and computes feature intersection, not union... with guardrails in place, obviously. I often build for 6-9 target-triples... so this was a requirement.

System Git > Gix

I've used the git binary directly for deterministic SHAs and proper history, no libgit2 / gitoxide. They felt a bit heavy for this use case and I'm assuming we all use git locally in some fashion anyway.

Lossless TOML

I've used toml_edit so that existing comments and layout are preserved. Manipulating TOML is not as straightforward as it sounds. Please, if you run into any problems... open an 'Issue' and I'll take a look... or submit a PR.

Contributions Welcome


Built by @loadingalias