cargo-rail 0.10.1

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

cargo-rail

A deterministic, cargo-native control plane for Rust monorepos: build/check/test/bench only what changes locally and in CI, unify the graph (deps/features), split/sync crates into new repos/monorepos, and automate releases with 14 core dependencies.

Crates.io docs.rs CI MSRV

Why

Documented impact on real repos (tokio, helix, meilisearch):

Metric Impact
CI Surface Execution 55% fewer surfaces run per merge
Weighted test/build Units 64% reduction in compute units
Dependencies Unified 78 across 47 crates (avg 1.7 per crate)
Undeclared features Fixed 141 silent bugs prevented
Tooling Consolidation 6-8 cargo plugins → 1 command
MSRV Computation Automatic from dependency graph

Compounding effects = massive time/cost savings:

  1. Change Detection (plan/run) reduces what runs — 55% fewer CI surfaces, 64% fewer compute units w/ shown determinism
  2. Dependency Unification (unify) reduces build graph complexity — cleaner deps, smaller build units, fewer rebuilds
  3. Smaller Build Graphs cargo-rail uses 14 core-deps for automatically removing unused dependencies, pruning truly dead features, unifying undeclared features, computing MSRV, splitting and synching crate/s to new, clean repos, and the entire release workflow w/ changelog generation. This results in fewer tools and less cargo-metadata fetches.

Before cargo-rail: Run 6 tools separately (hakari and/or workspace-hack, udeps, machete, shear, features-manager, msrv, sort, and more), each with different data/timing After cargo-rail: cargo rail unify --check in one metadata call

Quick Start

# install
cargo install cargo-rail

# generate config
cargo rail init

# dependency hygiene
cargo rail unify --check

# deterministic planning + execution
cargo rail plan --merge-base
cargo rail run --merge-base --profile ci
cargo rail plan --merge-base --explain

Pre-built binaries: GitHub Releases

Workflows

Change Planning (detection) + Execution (plan / run)

Problem: CICD wastes resources testing unchanged code. We either (1) test everything on every commit, or (2) build custom scripts that drift from local behavior.

Solution: One planner contract, used everywhere:

# Local: what would CI run if I pushed this branch?
cargo rail plan --merge-base

# CI: deterministic plan → selective execution
cargo rail plan --merge-base -f github  # outputs: build=true, test=false, docs=true, ...
cargo rail run --merge-base --profile ci  # runs ONLY what plan selected

How to use this effectively:

  1. Configure change detection rules in .config/rail.toml (infrastructure files, doc-only changes, custom, etc.)
  2. Run plan to see impact classification (which surfaces: build, test, bench, docs, infra, custom)
  3. Run run to execute only selected surfaces — locally or in CI - or wire to justfile, makefile, xtask, or shell scripts.
  4. Use --explain to understand any decision: "why did this run?" / "why was this skipped?"

Result: 55% fewer CI surface executions, 64% reduction in weighted compute units (validated on tokio/helix/meilisearch) See: Examples.

Dependency Unification (unify)

Problem: We all juggle 6+ cargo plugins for dependency hygiene (hakari, udeps, machete, shear, features-manager, msrv, sort, etc.). Each runs separately, on different data, with different CLI patterns... pulling the same cargo-metadata over and over. Undeclared features (borrowed from Cargo's resolver) break isolated builds silently.

Solution: cargo rail unify — one command, one metadata call, comprehensive analysis:

cargo rail unify --check    # preview all changes
cargo rail unify            # apply workspace-wide
cargo rail unify --explain  # understand each decision

What it does:

  • Unifies versions — writes to [workspace.dependencies], converts members to { workspace = true }
  • Fixes undeclared features — detects features borrowed via Cargo's unified resolution
  • Prunes dead features — removes features never enabled in resolved graph
  • Detects unused deps — flags dependencies not used anywhere
  • Computes MSRV — derives minimum Rust version from dependency graph
  • Replaces workspace-hack — enable pin_transitives for cargo-hakari equivalent
  • Configurable - we all have different ideas about what clean means, such as whether to remove unused dependencies or prune features or sort manifests - that's what your rail.toml file is for.

Validated impact on real repos:

Repository Crates Locked Deps Deps Unified Undeclared Features MSRV Computed
tokio-rs/tokio 10 224 9 19 1.85.0
helix-editor/helix 14 351 15 25 1.87.0
meilisearch/meilisearch 23 835 54 97 1.88.0
Aggregate 47 1,410 78 141

Config files and validation artifacts: Examples

Split + Sync (Google Copybara Replacement)

Problem: We need/want to publish crates from monorepos but want clean standalone repos with full git history. I wanted to build in a canonical dev monorepo, but release crates independently. Google'sCopybara requires so much and it's built w/ Java. Existing tools (git subtree, git-filter-repo) are one-way and manual. This offers us a bidirectional sync engine w/ 3-way merge conflict resolution in the event we need it; it never merges to main w/o a review PR for the canonical repo.

Solution: cargo rail split + cargo rail sync — bidirectional sync with 3-way conflict resolution:

# Extract crate to standalone repo with full git history
cargo rail split init crate/s  # configure once, automatically
cargo rail split run crate/s   # extract with history preserved

# Bidirectional sync
cargo rail sync crate/s --to-remote    # push monorepo changes to split repo
cargo rail sync crate/s --from-remote  # pull split repo changes (creates PR branch)

Three modes:

  • single: one crate → one repo (most common)
  • combined: multiple crates → one repo (shared utilities)
  • workspace: multiple crates → workspace structure (mirrors monorepo)

Built on system git (not libgit2) for deterministic SHAs and full git fidelity with less attack surface/weight in the graph.

Release Automation (release)

Release checks, versioning, changelogs, tags, dependency-order publish. Release_plz is great, but it's pulling in something like 500 deps to release our work. That's too much weight to carry around in the graph and too much attack surface in my world. I release cargo-rail with 1cargo-rail.

# Cut a new release
cargo rail release run cargo-rail --bump patch --yes  # swap 'patch', 'minor', or 'major' as needed
git push origin main --follow-tags   # follow up

This gives me a clean changelog, tags, crates.io / Github release.

GitHub Actions Integration

For CICD integration, use cargo-rail-action — a thin transport over cargo rail plan -f github that handles installation, checksum verification, and output publishing for job gating. It will make output cleaner and more readable.

The action keeps CI behavior aligned with local plan + run workflows.

Config

  • cargo rail init generates .config/rail.toml.
  • cargo rail config sync updates .config/rail.toml with latest defaults from new releases installed.
  • cargo rail config validate validates .config/rail.toml for when you're unsure.

By default, on cargo rail init, the rail.toml file is written to the .config/ directory. It's created if it doesn't exist. However, you can move/change it to the workspace root if you prefer it there. You can also 'unhide' the file if you'd like. Reference

Full documentation:

Migration Guides

Tested & Proven On Large Repos - THIS NEEDS WORK! It's thin.

All core workflows (plan/run, unify, split, sync, release) validated on production repos:

Repository Crates Locked Deps Validation Coverage
tokio-rs/tokio 10 224 Plan/run (5 merges), unify, split, sync, release
helix-editor/helix 14 351 Plan/run (5 merges), unify, split, sync, release
meilisearch/meilisearch 23 835 Plan/run (5 merges), unify, split, sync, release

How to Validate:

Validation isn't a single test — it's a protocol by design:

  1. Reproducibility: Every command in docs/large-repo-validation.md runs on forked repos with real merge history
  2. Metrics collection: Automated scripts measure execution reduction, surface accuracy, plan duration, unify impact
  3. Quality audit: Heuristics flag potential false positives/negatives for human review
  4. Real-world scenarios: Tests run on actual merge commits and real dependency graphs, not synthetic fixtures

Change detection results (15 merge scenarios):

  • 55% execution reduction rate (surfaces)
  • 64% weighted reduction rate (compute units)
  • Average plan duration: 632ms
  • Quality audit: 2 potential false-negatives, 1 potential false-positive (flagged for review)

Unify results (3 repos, 47 crates):

  • 78 dependencies unified
  • 141 undeclared features fixed (silent bugs prevented)
  • 2 dead features pruned
  • MSRV computed for all repos (1.85.0 - 1.88.0)

Full protocol and raw artifacts: examples/README.md

Examples - THIS NEEDS WORK! It's thin.

Each workflow includes working config files and reproducible command sequences:

All examples run on real repos (tokio, helix, meilisearch) and include:

  • Rail config (.config/rail.toml)
  • Command sequence (step-by-step)
  • Expected outputs
  • Validation artifacts

Getting Help

License

Licensed under MIT.