Skip to content

Commit 9130377

Browse files
committed
cargo-rail: README update
1 parent e17df42 commit 9130377

File tree

1 file changed

+151
-161
lines changed

1 file changed

+151
-161
lines changed

README.md

Lines changed: 151 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
1-
# cargo-rail
1+
<p align="center">
2+
<img src="https://socialify.git.ci/loadingalias/cargo-rail/image?font=Jost&language=1&name=1&owner=1&pattern=Solid&theme=Auto" alt="cargo-rail" width="640" height="320" />
3+
</p>
4+
5+
<h3 align="center">Graph-aware monorepo tooling for Rust</h3>
6+
7+
<p align="center">
8+
<strong>Test only what changed. Keep dependencies unified. Split crates cleanly. Release in order.</strong>
9+
</p>
10+
11+
<p align="center">
12+
<a href="https://crates.io/crates/cargo-rail"><img src="https://img.shields.io/crates/v/cargo-rail.svg" alt="Crates.io"></a>
13+
<a href="https://crates.io/crates/cargo-rail"><img src="https://img.shields.io/crates/d/cargo-rail.svg" alt="Downloads"></a>
14+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
15+
<a href="https://www.rust-lang.org"><img src="https://img.shields.io/badge/rust-1.91%2B-orange.svg" alt="Rust 1.91+"></a>
16+
</p>
17+
18+
<p align="center">
19+
<a href="#install">Install</a> •
20+
<a href="#why-cargo-rail">Why</a> •
21+
<a href="#quick-start">Quick Start</a> •
22+
<a href="#workflows">Workflows</a> •
23+
<a href="#real-world-results">Results</a> •
24+
<a href="https://github.com/loadingalias/cargo-rail-action">GitHub Action</a>
25+
</p>
226

3-
**Monorepo orchestration for Rust workspaces.**
27+
---
428

5-
[![Crates.io](https://img.shields.io/crates/v/cargo-rail.svg)](https://crates.io/crates/cargo-rail)
6-
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7-
[![Rust 1.91+](https://img.shields.io/badge/rust-1.91%2B-orange.svg)](https://www.rust-lang.org)
29+
## What It Does
830

9-
[Command Reference](docs/commands.md) · [Config Reference](docs/config.md) · [Demo Videos](examples/)
31+
cargo-rail is a single tool that replaces a fragmented ecosystem:
1032

11-
```bash
12-
cargo install cargo-rail
13-
```
33+
| Problem | Before | After |
34+
|---------|--------|-------|
35+
| **Build graph drift** | `cargo-hakari`, workspace-hack crates | `cargo rail unify` |
36+
| **Unused deps** | `cargo-udeps`, `cargo-machete`, `cargo-shear` | `cargo rail unify` |
37+
| **MSRV guessing** | `cargo-msrv`, compile-and-fail loops | `cargo rail unify` |
38+
| **CI waste** | `paths-filter` + 1k LoC shell | `cargo rail test` |
39+
| **Crate extraction** | `git subtree`, Copybara (Java) | `cargo rail split` |
40+
| **Release chaos** | `release-plz` (600+ deps), `git-cliff` | `cargo rail release` |
1441

15-
Pre-built binaries available on the [Releases Page](https://github.com/loadingalias/cargo-rail/releases).
42+
**11 core dependencies. 77 resolved. One config file.**
1643

1744
---
1845

19-
## TLDR
46+
## Install
47+
48+
```bash
49+
cargo install cargo-rail
50+
```
2051

21-
- **Only test what changed both locally and in CI**: `cargo rail test`
22-
- **Keep manifests clean and unified**: `cargo rail unify` / `cargo rail unify sync`
23-
- **Split + sync crates with full git history**: `cargo rail split` / `cargo rail sync`
24-
- **Release in dependency order with changelogs**: `cargo rail release`
25-
- **Minimal deps**: 11 core dependencies; 77 resolved dependencies
52+
Pre-built binaries: [Releases](https://github.com/loadingalias/cargo-rail/releases)`cargo binstall cargo-rail`
2653

2754
---
2855

@@ -31,215 +58,178 @@ Pre-built binaries available on the [Releases Page](https://github.com/loadingal
3158
Drop into any Rust workspace:
3259

3360
```bash
34-
cargo install cargo-rail
35-
cargo rail init # Initialize once
36-
cargo rail config validate # Validate config
37-
cargo rail unify --check # Dry-run 'unify'
38-
cargo rail unify # Initial 'unify' automatically writes backup
39-
cargo rail test # Auto-detect Nextest; native change-detection
61+
cargo rail init # Generate .config/rail.toml
62+
cargo rail unify --check # Preview dependency unification
63+
cargo rail unify # Apply (auto-backups on first run)
64+
cargo rail test # Test only affected crates
4065
```
4166

42-
See [docs/commands.md](docs/commands.md) for details.
67+
That's it. Your workspace is now unified, and you're only testing what changed.
68+
69+
---
70+
71+
## Why cargo-rail
72+
73+
Most Rust monorepo tools solve one problem and introduce their own complexity. You end up with:
74+
75+
- 5+ config files across different tools
76+
- Each tool parses `cargo metadata` differently
77+
- No single source of truth for your dependency graph
78+
- Shell scripts gluing everything together
79+
80+
cargo-rail takes a different approach:
81+
82+
- **One tool, one config**`rail.toml` describes everything
83+
- **Resolution-based** — Uses what Cargo actually resolved, not syntax parsing
84+
- **Multi-target aware** — Runs `cargo metadata --filter-platform` per target in parallel
85+
- **Minimal footprint** — 11 core deps vs 200-600+ in competing toolchains
4386

4487
---
4588

4689
## Workflows
4790

48-
### Change Detection & Testing
91+
### Change Detection
4992

50-
Graph-aware change detection. Only check/test/bench what's affected:
93+
Graph-aware. Only test what's affected by your changes:
5194

5295
```bash
53-
cargo rail affected # Show affected crates
54-
cargo rail affected -f names-only # Just names (for scripting)
55-
cargo rail test # Run tests for affected crates
56-
cargo rail test --all # Override and run the whole workspace
96+
cargo rail affected # Show affected crates
97+
cargo rail affected -f names-only # Just names (for scripting)
98+
cargo rail test # Run tests for affected crates only
99+
cargo rail test --all # Override: test everything
57100
```
58101

59-
Wire it into your local workflow:
102+
**For CI:** Use [`cargo-rail-action`](https://github.com/loadingalias/cargo-rail-action):
60103

61-
```bash
62-
# scripts/check.sh (or justfile)
63-
AFFECTED=$(cargo rail affected -f names-only 2>/dev/null || echo "")
64-
if [ -z "$AFFECTED" ]; then
65-
cargo check --workspace --all-targets
66-
else
67-
for crate in $AFFECTED; do FLAGS="$FLAGS -p $crate"; done
68-
cargo check $FLAGS --all-targets
69-
fi
70-
```
104+
```yaml
105+
- uses: loadingalias/cargo-rail-action@v1
106+
id: rail
107+
with:
108+
since: ${{ github.event.before }}
71109

72-
For CI, see [`cargo-rail-action`](https://github.com/loadingalias/cargo-rail-action).
110+
- run: cargo nextest run -p ${{ steps.rail.outputs.crates }}
111+
if: steps.rail.outputs.docs-only != 'true'
112+
```
73113
74114
### Dependency Unification
75115
76-
Resolution-based `[workspace.dependencies]` management using Cargo's resolver:
116+
Keep your workspace lean. Per target triple:
77117
78118
```bash
79-
cargo rail unify --check # Preview changes (CI-safe, exits 1 if changes needed)
80-
cargo rail unify # Apply changes
81-
cargo rail unify --backup # Apply with manifest backups
82-
cargo rail unify undo # Restore from a previous backup
83-
cargo rail unify sync # Re-detect targets and merge into rail.toml
119+
cargo rail unify --check # Preview (CI-safe, exits 1 if changes needed)
120+
cargo rail unify # Apply changes
121+
cargo rail unify sync # Re-detect targets after adding new platforms
122+
cargo rail unify undo # Restore from backup
84123
```
85124

86-
What it does, per target triple (`targets` in `rail.toml`):
125+
What it does:
87126

88-
- **Unifies versions** based on what Cargo actually resolved
89-
- **Computes MSRV (dependencies)** from the resolved graph (`[workspace.package].rust-version`)
127+
- **Unifies versions** based on Cargo's resolver output
128+
- **Computes MSRV** from the dependency graph
90129
- **Prunes dead features** that are never enabled
91-
- **Detects/removes unused deps** (opt-in via config)
92-
- **Optionally pins transitives** (`cargo-hakari` or workspace-hack replacement)
93-
94-
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`.
130+
- **Detects unused deps** (opt-in)
131+
- **Pins transitives** — replaces `cargo-hakari` without a workspace-hack crate
95132

96133
### Split & Sync
97134

98-
Extract crates to standalone repos and keep them in sync, with deterministic SHAs.
99-
100-
Three modes: single crate to new repo, multiple crates to new repo, or multiple crates to a new workspace.
135+
Extract crates with full git history. Keep them in sync:
101136

102137
```bash
103-
cargo rail split init crate # Configure split for crate/s
104-
cargo rail split run crate --check # Preview the split
105-
cargo rail split run crate # Execute the split
106-
107-
cargo rail sync crate # Bidirectional sync
108-
cargo rail sync crate --to-remote # Monorepo -> split repo
109-
cargo rail sync crate --from-remote # Split repo -> monorepo (PR branch)
138+
cargo rail split init my_crate # Configure
139+
cargo rail split run my_crate # Extract with history
140+
cargo rail sync my_crate # Bidirectional sync
141+
cargo rail sync my_crate --to-remote # Push to split repo
142+
cargo rail sync my_crate --from-remote # Pull (creates PR branch)
110143
```
111144

112-
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](docs/config.md)
145+
Three modes: single crate → new repo, multiple crates → new repo, or multiple crates → new workspace.
113146

114147
### Release
115148

116-
Version bumping, changelog generation, tagging, and publishing in dependency order:
149+
Dependency-order publishing with changelogs:
117150

118151
```bash
119-
cargo rail release init crate # Configure release
120-
cargo rail release check crate # Fast validation
121-
cargo rail release check crate --extended # Dry-run + MSRV check
122-
cargo rail release run crate --check # Preview release plan
123-
cargo rail release run crate --bump minor # Execute release
124-
```
125-
126-
Configure once in `rail.toml`:
127-
128-
```toml
129-
[release]
130-
tag_prefix = "v"
131-
tag_format = "{crate}-{prefix}{version}" # Adjust for standalone crate
132-
require_clean = true
152+
cargo rail release init my_crate # Configure
153+
cargo rail release check my_crate # Validate
154+
cargo rail release run my_crate --bump minor # Execute
133155
```
134156

135157
---
136158

137-
## Demo Videos
138-
139-
I've tested across trusted Rust workspaces and recorded the command workflows end-to-end. All assets live under [`examples/`](examples/).
140-
141-
<details>
142-
<summary><strong>ripgrep</strong> - baseline unification in a widely trusted workspace</summary>
159+
## Real-World Results
143160

144-
https://github.com/user-attachments/assets/93f34633-aa0e-4cde-8723-c81f3f474bac
161+
Tested on production Rust workspaces:
145162

146-
</details>
163+
| Repo | Members | Deps Unified | Notes |
164+
|------|---------|--------------|-------|
165+
| **[tikv](https://github.com/loadingalias/tikv)** | 72 | 61 | Largest stress test |
166+
| **[meilisearch](https://github.com/loadingalias/meilisearch)** | 19 | 46 | Significant unification |
167+
| **[helix-db](https://github.com/loadingalias/helix-db)** | 6 | 18 | Growing project |
168+
| **[helix](https://github.com/loadingalias/helix)** | 12 | 16 | Editor workspace |
169+
| **[tokio](https://github.com/loadingalias/tokio)** | 10 | 10 | Core ecosystem |
170+
| **[ripgrep](https://github.com/loadingalias/ripgrep)** | 10 | 9 | CLI baseline |
171+
| **[polars](https://github.com/loadingalias/polars)** | 8 | 2 | Already clean |
172+
| **[ruff](https://github.com/loadingalias/ruff)** | 43 | 0 | Already unified |
173+
| **[codex](https://github.com/loadingalias/codex)** | 49 | 0 | Already unified |
147174

148-
<details>
149-
<summary><strong>tokio</strong> - conservative settings for a core ecosystem library</summary>
175+
Each link above points to a fork with cargo-rail configured. Clone and compare.
150176

151-
https://github.com/user-attachments/assets/520abf55-cf45-43af-8dc8-0eed0a58ce72
177+
**Demo videos:** [`examples/`](examples/)
152178

153-
</details>
154-
155-
<details>
156-
<summary><strong>polars</strong> - large workspace with <code>pin_transitives</code> (cargo-hakari replacement)</summary>
157-
158-
https://github.com/user-attachments/assets/31bf5ff5-7185-4e59-acaa-ea8edd3c6f48
179+
---
159180

160-
</details>
181+
## Migrating from cargo-hakari?
161182

162-
<details>
163-
<summary><strong>helix-db</strong> - unification on a growing project</summary>
183+
5 minutes:
164184

165-
https://github.com/user-attachments/assets/3520d254-e69c-460c-b894-eb126b42a1ea
185+
```bash
186+
git checkout -b migrate-to-rail
187+
rm -rf crates/workspace-hack # Remove the hack crate
188+
cargo rail init
189+
# Edit rail.toml: set pin_transitives = true
190+
cargo rail unify
191+
cargo check --workspace
192+
```
166193

167-
</details>
194+
Full guide: [docs/migrate-hakari.md](docs/migrate-hakari.md)
168195

169-
<details>
170-
<summary><strong>ruff</strong> - extracting crates with full git history</summary>
196+
---
171197

172-
https://github.com/user-attachments/assets/b9f56e77-de0a-42c1-b2ef-1a40bb24f5ac
198+
## Design Decisions
173199

174-
</details>
200+
**Multi-Target Resolution** — Most tools run `cargo metadata` once. cargo-rail runs it per target in parallel, computes feature *intersections* (not unions). If something is marked unused, it's unused across all your targets.
175201

176-
<details>
177-
<summary><strong>tikv</strong> - version bumping, changelog, release, and publishing in dep order</summary>
202+
**System Git** — Uses your `git` binary directly. No `libgit2`, no `gitoxide`. Real git, real history, deterministic SHAs.
178203

179-
https://github.com/user-attachments/assets/9c0b6df1-8539-44a0-9c82-a9fdca5e075c
204+
**Lossless TOML** — Uses `toml_edit` to preserve comments and formatting. Your manifests stay readable.
180205

181-
</details>
206+
**Supply-Chain Safety** — 11 core dependencies. I built the release workflow specifically because I was uncomfortable with 600+ deps for release automation.
182207

183208
---
184209

185-
## Unification Results in Testing/Validation
210+
## Documentation
186211

187-
`cargo rail unify` on a few different projects:
188-
189-
| Repo | Crates | Deps Unified | Member Edits |
190-
|------------|--------|--------------|--------------|
191-
| [tikv](https://github.com/tikv/tikv) | 83 | 57 | 519 |
192-
| [polars](https://github.com/pola-rs/polars) | 33 | 2 | 13 |
193-
| [meilisearch](https://github.com/meilisearch/meilisearch) | 19 | 46 | 210 |
194-
| [helix](https://github.com/helix-editor/helix) | 13 | 16 | 67 |
195-
| [tokio](https://github.com/tokio-rs/tokio) | 10 | 10 | 35 |
196-
| [ripgrep](https://github.com/BurntSushi/ripgrep) | 10 | 9 | 41 |
197-
| [helix-db](https://github.com/helixdb/helix-db) | 6 | 16 | 44 |
198-
199-
Full demos and reports live under [`examples/`](examples/) for most examples.
212+
- [Command Reference](docs/commands.md)
213+
- [Config Reference](docs/config.md)
214+
- [Migration Guide](docs/migrate-hakari.md)
215+
- [Demo Videos](examples/)
200216

201217
---
202218

203-
### Potentially Replaces
204-
205-
| Tool | cargo-rail equivalent |
206-
|------|-----------------------|
207-
| `cargo-hakari` | `unify` with `pin_transitives = true` |
208-
| `cargo-udeps` / `cargo-machete` / `cargo-shear` | `unify` with `detect_unused = true` / `remove_unused = true` |
209-
| `cargo-msrv` (for dep-driven MSRV)* | `unify` with `msrv = true` |
210-
| `cargo-release` / `release-plz` | `release` command |
211-
| `git-cliff` | Built-in changelog generation |
212-
| Google's `Copybara` for Rust teams | `split` + `sync` commands |
213-
| Mountain of shell scripts | `test` + `affected` + [`cargo-rail-action`](https://github.com/loadingalias/cargo-rail-action) in CI |
219+
## Contributing
214220

215-
#### NOTE ON MSRV
221+
Issues, PRs, and feedback welcome. This is built for the Rust community.
216222

217-
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`.
223+
If cargo-rail helps your workflow, consider [starring the repo](https://github.com/loadingalias/cargo-rail) — it helps others find it.
218224

219225
---
220226

221-
## Design Notes
222-
223-
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](http://loadingalias.com/blog/). I am absolutely hoping to get the community involved in improving the tooling here; contributions are welcome!
224-
225-
**Supply-Chain Safety**
226-
227-
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.
228-
229-
**Multi-Target Resolution**
230-
231-
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.
232-
233-
**System Git > Gix**
234-
235-
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.
236-
237-
**Lossless TOML**
238-
239-
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.
240-
241-
**Contributions Welcome**
242-
243-
---
227+
<p align="center">
228+
<a href="https://github.com/loadingalias/cargo-rail">GitHub</a> •
229+
<a href="https://crates.io/crates/cargo-rail">Crates.io</a> •
230+
<a href="https://github.com/loadingalias/cargo-rail-action">GitHub Action</a>
231+
</p>
244232

245-
<sub>Built by [@loadingalias](https://github.com/loadingalias)</sub>
233+
<p align="center">
234+
<sub>Built by <a href="https://github.com/loadingalias">@loadingalias</a></sub>
235+
</p>

0 commit comments

Comments
 (0)