Skip to content

Commit 485c65c

Browse files
committed
cargo-rail: feat(unify): add sync subcommand to re-detect and sync targets
- Adds 'cargo rail unify sync' command - Scans workspace for target triples and replaces rail.toml targets - Excludes all rail.toml locations from detection (avoids circular detection) - Preserves all other config (comments, unify settings, release settings) - Includes --check mode for preview - Updates README with improved docs and CI/CD workflow examples - Adds cargo-binstall metadata for pre-built binary downloads - Adds 7 integration tests
1 parent 3a24e86 commit 485c65c

File tree

13 files changed

+808
-85
lines changed

13 files changed

+808
-85
lines changed

Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ license = "MIT"
77
authors = ["loadingalias"]
88
description = "Graph-aware testing, dependency unification, and crate extraction for Rust monorepos"
99
repository = "https://github.com/loadingalias/cargo-rail"
10-
homepage = "https://loadingalias.github.io/cargo-rail"
1110
documentation = "https://docs.rs/cargo-rail"
1211
categories = [
1312
"development-tools::cargo-plugins",
@@ -39,3 +38,18 @@ tempfile = "3.23.0"
3938
[[test]]
4039
name = "integration"
4140
path = "tests/integration/mod.rs"
41+
42+
# cargo-binstall metadata for pre-built binary downloads
43+
# See: https://github.com/cargo-bins/cargo-binstall
44+
[package.metadata.binstall]
45+
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.tar.gz"
46+
pkg-fmt = "tgz"
47+
48+
# Windows uses .zip instead of .tar.gz
49+
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
50+
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.zip"
51+
pkg-fmt = "zip"
52+
53+
[package.metadata.binstall.overrides.aarch64-pc-windows-msvc]
54+
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.zip"
55+
pkg-fmt = "zip"

README.md

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@
66
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
77
[![Rust 1.91+](https://img.shields.io/badge/rust-1.91%2B-orange.svg)](https://www.rust-lang.org)
88

9-
[Commands](docs/commands.md) · [Config](docs/config.md) · [Examples](examples/) · [Crates.io](https://crates.io/crates/cargo-rail)
9+
[Command Reference](docs/commands.md) · [Config Reference](docs/config.md) · [Demo Videos](examples/)
1010

1111
```bash
1212
cargo install cargo-rail
1313
```
1414

15-
Pre-built binaries available on the [Releases page](https://github.com/loadingalias/cargo-rail/releases).
15+
Pre-built binaries available on the [Releases Page](https://github.com/loadingalias/cargo-rail/releases).
1616

1717
---
1818

1919
## TLDR
2020

2121
- **Only test what changed both locally and in CI**: `cargo rail test`
22-
- **Keep manifests clean and unified**: `cargo rail unify`
22+
- **Keep manifests clean and unified**: `cargo rail unify` / `cargo rail unify sync`
2323
- **Split + sync crates with full git history**: `cargo rail split` / `cargo rail sync`
2424
- **Release in dependency order with changelogs**: `cargo rail release`
25-
26-
Pure dev tooling. No workspace-hack crate. 11 direct dependencies.
25+
- **Minimal deps**: 11 core dependencies; 77 resolved dependencies
2726

2827
---
2928

@@ -33,54 +32,66 @@ Drop into any Rust workspace:
3332

3433
```bash
3534
cargo install cargo-rail
36-
cargo rail unify --check
37-
cargo rail unify --backup
38-
cargo rail test
39-
```
40-
41-
Optional configuration (auto-detected location):
42-
43-
```bash
44-
cargo rail init # Generates .config/rail.toml by default
45-
cargo rail config validate # Sanity-check config
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
4640
```
4741

48-
See [docs/commands.md](docs/commands.md)
42+
See [docs/commands.md](docs/commands.md) for details.
4943

5044
---
5145

5246
## Workflows
5347

5448
### Change Detection & Testing
5549

56-
Only run tests for crates affected by your changes, with nextest auto-detection:
50+
Graph-aware change detection. Only check/test/bench what's affected:
51+
52+
```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
57+
```
58+
59+
Wire it into your local workflow:
5760

5861
```bash
59-
cargo rail affected # Show affected crates
60-
cargo rail test # Run tests for affected crates
61-
cargo rail test --all # Override and run the whole workspace
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
6270
```
6371

6472
For CI, see [`cargo-rail-action`](https://github.com/loadingalias/cargo-rail-action).
6573

6674
### Dependency Unification
6775

68-
Resolution-based `[workspace.dependencies]` management using Cargos resolver:
76+
Resolution-based `[workspace.dependencies]` management using Cargo's resolver:
6977

7078
```bash
71-
cargo rail unify --check # Preview changes (CI-safe)
79+
cargo rail unify --check # Preview changes (CI-safe, exits 1 if changes needed)
7280
cargo rail unify # Apply changes
73-
cargo rail unify --backup # Apply with manifest backups; backup is automatic on the first 'unify' run
74-
cargo rail unify undo # Restore from a previous backup; default last
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
7584
```
7685

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

7988
- **Unifies versions** based on what Cargo actually resolved
80-
- **Computes MSRV** from the resolved graph (`[workspace.package].rust-version`)
89+
- **Computes MSRV (dependencies)** from the resolved graph (`[workspace.package].rust-version`)
8190
- **Prunes dead features** that are never enabled
8291
- **Detects/removes unused deps** (opt-in via config)
83-
- **Optionally pins transitives** (workspace-hack replacement)
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`.
8495

8596
### Split & Sync
8697

@@ -89,81 +100,81 @@ Extract crates to standalone repos and keep them in sync, with deterministic SHA
89100
Three modes: single crate to new repo, multiple crates to new repo, or multiple crates to a new workspace.
90101

91102
```bash
92-
cargo rail split init crate # Configure split for crate/s
93-
cargo rail split run crate --check # Preview the split
94-
cargo rail split run crate # Execute the split
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
95106

96-
cargo rail sync crate # Bidirectional sync
97-
cargo rail sync crate --to-remote # Monorepo -> split repo
98-
cargo rail sync crate --from-remote # Split repo -> monorepo (PR branch)
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)
99110
```
100111

101-
Split/sync behavior is driven by `[crates.NAME.split]` in `rail.toml`. See [docs/config.md](docs/config.md)
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)
102113

103114
### Release
104115

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

107118
```bash
108-
cargo rail release init crate # Configure release
109-
cargo rail release check crate # Fast validation
110-
cargo rail release check crate --extended # Dry-run + MSRV check
111-
cargo rail release run crate --check # Preview release plan
112-
cargo rail release run crate --bump minor # Execute release
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
113124
```
114125

115126
Configure once in `rail.toml`:
116127

117128
```toml
118129
[release]
119130
tag_prefix = "v"
120-
tag_format = "{crate}-{prefix}{version}" # Adjust for standalone crate
131+
tag_format = "{crate}-{prefix}{version}" # Adjust for standalone crate
121132
require_clean = true
122133
```
123134

124135
---
125136

126-
## Example Videos
137+
## Demo Videos
127138

128-
Real workspaces, recorded command workflows end-to-end. All assets live under [`examples/`](examples/).
139+
I've tested across trusted Rust workspaces and recorded the command workflows end-to-end. All assets live under [`examples/`](examples/).
129140

130141
<details>
131-
<summary><strong>Unify: ripgrep</strong> - baseline unification in a widely trusted workspace</summary>
142+
<summary><strong>ripgrep</strong> - baseline unification in a widely trusted workspace</summary>
132143

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

135146
</details>
136147

137148
<details>
138-
<summary><strong>Unify: tokio</strong> - conservative settings for a core ecosystem library</summary>
149+
<summary><strong>tokio</strong> - conservative settings for a core ecosystem library</summary>
139150

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

142153
</details>
143154

144155
<details>
145-
<summary><strong>Unify: polars</strong> - large workspace with <code>pin_transitives</code> (cargo-hakari replacement)</summary>
156+
<summary><strong>polars</strong> - large workspace with <code>pin_transitives</code> (cargo-hakari replacement)</summary>
146157

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

149160
</details>
150161

151162
<details>
152-
<summary><strong>Unify: helix-db</strong> - unification on a growing project</summary>
163+
<summary><strong>helix-db</strong> - unification on a growing project</summary>
153164

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

156167
</details>
157168

158169
<details>
159-
<summary><strong>Split: ruff</strong> - extracting crates with full git history</summary>
170+
<summary><strong>ruff</strong> - extracting crates with full git history</summary>
160171

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

163174
</details>
164175

165176
<details>
166-
<summary><strong>Release: tikv</strong> - version bumping, changelog, and publishing in dependency order</summary>
177+
<summary><strong>tikv</strong> - version bumping, changelog, release, and publishing in dep order</summary>
167178

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

@@ -173,9 +184,9 @@ https://github.com/user-attachments/assets/9c0b6df1-8539-44a0-9c82-a9fdca5e075c
173184

174185
## Unification Results in Testing/Validation
175186

176-
`cargo rail unify` on trusted Rust monorepos:
187+
`cargo rail unify` on a few different projects:
177188

178-
| Repository | Crates | Deps Unified | Member Edits |
189+
| Repo | Crates | Deps Unified | Member Edits |
179190
|------------|--------|--------------|--------------|
180191
| [tikv](https://github.com/tikv/tikv) | 83 | 57 | 519 |
181192
| [polars](https://github.com/pola-rs/polars) | 33 | 2 | 13 |
@@ -185,43 +196,47 @@ https://github.com/user-attachments/assets/9c0b6df1-8539-44a0-9c82-a9fdca5e075c
185196
| [ripgrep](https://github.com/BurntSushi/ripgrep) | 10 | 9 | 41 |
186197
| [helix-db](https://github.com/helixdb/helix-db) | 6 | 16 | 44 |
187198

188-
Full demos and reports live under [`examples/`](examples/).
199+
Full demos and reports live under [`examples/`](examples/) for most examples.
189200

190201
---
191202

192-
## Could Replace
203+
### Potentially Replaces
193204

194205
| Tool | cargo-rail equivalent |
195206
|------|-----------------------|
196-
| cargo-hakari | `unify` with `pin_transitives = true` |
197-
| cargo-udeps, cargo-machete, cargo-shear | `unify` with `detect_unused = true` / `remove_unused = true` |
198-
| cargo-msrv (for dep-driven MSRV) | `unify` with `msrv = true` |
199-
| cargo-release, release-plz | `release` command |
200-
| git-cliff | Built-in changelog generation |
201-
| Google's Copybara for Rust teams | `split` + `sync` commands |
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 |
202213
| Mountain of shell scripts | `test` + `affected` + [`cargo-rail-action`](https://github.com/loadingalias/cargo-rail-action) in CI |
203214

215+
#### NOTE ON MSRV
216+
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`.
218+
204219
---
205220

206221
## Design Notes
207222

208-
I've built cargo-rail as a necessity for my own work. I've explained the reasoning behind it and touched on the plans for the future in this post: [Rust Monorepo Tooling](http://loadingalias.com/blog). I would love contributions; I'd love to iron out the details and workflows that we all use daily but that are clunky.
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!
209224

210225
**Supply-Chain Safety**
211226

212-
This matters a great deal to me. I've tried to keep the tool itself lean; avoids large meta-dependency graphs. Currently, cargo-rail depends on 11 core deps; 77 resolved in the release build.
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.
213228

214229
**Multi-Target Resolution**
215230

216-
Runs `cargo metadata --filter-platform` per target and computes feature intersection, not union. I often build for 7-9 target-triples; this was a requirement.
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.
217232

218233
**System Git > Gix**
219234

220-
I've used the `git` binary directly for deterministic SHAs and proper history, no libgit2/gitoxide. They felt a bit heavy for the use case and we all use Git locally in some fashion.
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.
221236

222237
**Lossless TOML**
223238

224-
Uses `toml_edit` so existing comments and layout are preserved. Manipulating TOML is honestly not as easy as it sounds. Please, if you run into any problems... open an 'Issue' and I'll take a look.
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.
225240

226241
**Contributions Welcome**
227242

docs/commands.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,19 @@ Usage: cargo rail unify [OPTIONS] [COMMAND]
150150
151151
Commands:
152152
undo Restore manifests from a previous backup
153+
sync Re-detect and sync targets to rail.toml
153154
help Print this message or the help of the given subcommand(s)
154155
155156
Options:
156157
-q, --quiet
157158
Suppress progress messages (for CI/automation)
158159
159-
-c, --check
160-
Dry-run mode: preview changes without modifying files
161-
162160
--json
163161
Output in JSON format (shorthand for -f json)
164162
163+
-c, --check
164+
Dry-run mode: preview changes without modifying files
165+
165166
-f, --format <FORMAT>
166167
Output format
167168
@@ -200,6 +201,8 @@ Examples:
200201
cargo rail unify --show-diff # Show manifest changes
201202
cargo rail unify undo # Restore from backup
202203
cargo rail unify undo --list # List available backups
204+
cargo rail unify sync --check # Preview target sync
205+
cargo rail unify sync # Re-detect and merge targets into rail.toml
203206
```
204207

205208
---
@@ -222,6 +225,34 @@ Options:
222225

223226
---
224227

228+
### cargo rail unify sync
229+
230+
```
231+
Re-detect and sync targets to rail.toml
232+
233+
Scans workspace for target triples (rust-toolchain.toml, .cargo/config.toml, etc.) and merges any new targets into your rail.toml configuration. Preserves existing targets and all other settings.
234+
235+
Usage: cargo rail unify sync [OPTIONS]
236+
237+
Options:
238+
-c, --check
239+
Preview changes without modifying rail.toml
240+
241+
-q, --quiet
242+
Suppress progress messages (for CI/automation)
243+
244+
--json
245+
Output in JSON format (shorthand for -f json)
246+
247+
-h, --help
248+
Print help (see a summary with '-h')
249+
250+
-V, --version
251+
Print version
252+
```
253+
254+
---
255+
225256
## cargo rail init
226257

227258
```

0 commit comments

Comments
 (0)