Skip to content

feat: Make turbo prune Cargo-aware - #13281

Merged
anthonyshew merged 1 commit into
mainfrom
shew/cargo-prune
Jul 6, 2026
Merged

anthonyshew merged 1 commit into
mainfrom
shew/cargo-prune

Conversation

@anthonyshew

Copy link
Copy Markdown
Contributor

Why

Final functional PR of the Cargo workspace series. turbo prune <crate> must produce a self-contained, buildable Cargo workspace — kept crates, a lockfile subset that passes --locked, and a root manifest that Cargo will load. Like every PR in the series, the mechanism is a trait surface: Toolchain::prune_plan (what the pruned repo needs beyond the copied packages, as plain data) and prune_finalize (post-write polish).

What

  • Kept members from a Cargo.lock reachability walk (cargo_prune_lock in turborepo-lockfiles), not the package graph: the lockfile merges dev-dependency edges, so members reachable only through dev-deps — including cycle participants whose edges the graph drops — are retained; kept crates' manifests reference their paths. The pruned lock contains exactly the reachable closure
  • Root Cargo.toml rewritten with toml_edit (comments/formatting preserved): explicit members, filtered default-members, [workspace.dependencies] path entries to removed crates dropped
  • prune_finalize: reachability can't see Cargo's feature unification, so cargo metadata runs in the output (offline → networked fallback for git-patched workspaces on cold machines) letting Cargo minimally sync its own lockfile; failure downgrades to a warning
  • Docker layout: json layer = root manifest + kept crates' Cargo.tomls + pruned lock; sources in the full layer. rust-toolchain*/.cargo/config* carried over
  • Guards: root-anchored packages (the synthetic cargo package) are not pruneable targets; non-JS packages join turbo.json task pruning but never the JS lockfile subgraph/keys or package.json workspaces
  • JS prune_plan is explicitly empty, documented as known debt: prune's JS machinery is its native path; folding it in means restructuring a battle-tested command (same pattern as 7b/watch)

How

Flag off: JS-only pruning is untouched (the plan loop no-ops; full suite passes). Flag on, e2e-verified — the decisive test builds the pruned output with cargo build --locked and runs the binary. Four new e2e tests: buildable pruned workspace, docker layout, JS-only target emits no Cargo files, workspace-package guard. Unit coverage: lock-walk semantics (dev-reachable member retention, stale-root error), manifest rewrite (member explicitness, default-members filtering, path-dep dropping, comment preservation).

turbo prune <crate> now produces a self-contained, buildable Cargo
workspace, through a new Toolchain trait surface: prune_plan reports what
the pruned repository needs beyond the copied packages (extra members,
rewritten workspace files, config files to carry over), and prune_finalize
lets a toolchain polish its files once the output is written. JavaScript
declares nothing here, documented as known debt: the prune commands JS
machinery (lockfile subgraphing, workspace-file rewriting, patches) is its
native path, and folding it in means restructuring a battle-tested command.

The Cargo implementation:

- The kept-member set comes from a Cargo.lock reachability walk
  (turborepo_lockfiles::cargo_prune_lock), not the package graph: Cargo.lock
  merges dev-dependency edges, so members reachable only through
  dev-dependencies — including cycle participants whose edges the package
  graph drops — are retained, since kept crates manifests reference their
  paths. The lockfile is subset to exactly the reachable closure, so cargo
  build --locked succeeds in the pruned output.
- The root Cargo.toml is rewritten with toml_edit (comments and formatting
  preserved): explicit members, default-members filtered,
  [workspace.dependencies] path entries to removed crates dropped.
- Reachability pruning cannot see Cargos feature unification — shrinking
  the workspace can deactivate features that were the only reason some
  packages were locked. Rather than reimplement the resolver,
  prune_finalize runs cargo metadata in the output so Cargo minimally syncs
  its own lockfile: offline first (removals need no network), falling back
  to a networked sync for workspaces whose git patches are not in the local
  cargo cache. Failure downgrades to a warning — the superset lock still
  builds, it just is not --locked-clean.
- Docker layout: the json layer carries the root manifest, each kept
  crates Cargo.toml, and the pruned lock; sources go to the full layer.
- rust-toolchain and .cargo config files are carried over.

Guards and hygiene: a package anchored at the repository root (the
synthetic cargo package) is not a pruneable target; non-JS packages
participate in turbo.json task pruning but never in the JS lockfile
subgraph, package.json workspaces, or JS lockfile keys; pruning a JS-only
target in a mixed repo emits no Cargo files.

Covered by four new e2e tests, including building the pruned output with
cargo build --locked and running the pruned binary.
@anthonyshew
anthonyshew requested a review from a team as a code owner July 6, 2026 17:39
@anthonyshew
anthonyshew requested review from tknickman and removed request for a team July 6, 2026 17:39
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples-basic-web Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-designsystem-docs Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-gatsby-web Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-kitchensink-blog Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-nonmonorepo Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-svelte-web Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-tailwind-web Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
examples-vite-web Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
turbo-site Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm
turborepo-eve-agent Ready Ready Preview, Comment, Open in v0 Jul 6, 2026 5:40pm

default_members.retain(|entry| {
entry
.as_str()
.is_some_and(|dir| normalized_kept.contains(&normalize_dir(dir)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prune_root_manifest leaves [patch.*] entries with local path values pointing at pruned-away crates, producing a root Cargo.toml whose dangling patch path makes cargo build --locked fail at load in the pruned output.

Fix on Vercel

@anthonyshew
anthonyshew merged commit ddc584d into main Jul 6, 2026
42 checks passed
@anthonyshew
anthonyshew deleted the shew/cargo-prune branch July 6, 2026 17:49
anthonyshew added a commit that referenced this pull request Jul 6, 2026
## Why

The Cargo workspace series is functionally complete (#13227#13281).
This repo is its ideal first consumer: with crates as first-class
packages, every JS-side shim for Rust work becomes either a deletion or
an ordinary task dependency — and we catch real-world issues before
users do.

**Version requirement**: `futureFlags.experimentalCargoWorkspaces`
requires a turbo that knows the key. The published `2.10.4-canary.2`
does (verified: it parses the flag, executes `cargo build
--package=<crate>`, and derives closure hashing). Stable `2.10.3`
hard-errors on it — contributors on stable global turbo must `npm i -g
turbo@canary` (the error message is a clear parse error on the flag
line). CI resolves the latest 2.x release, which is the canary, so CI is
unaffected.

## What

- Root `turbo.json` opts in; `turbo#build` appends `version.txt` via
additive `$TURBO_DEFAULT$` (it's embedded with `include_str!` from
outside any crate dir)
- **Deleted**: `@turbo/cli`'s synthetic `rust-src` invalidation task,
hand-listed `target/` outputs, and cargo-wrapper scripts — native
`turbo#build` replaces them with derived closure hashing + deliverable
caching
- `@turbo/repository#build` → depends on `turborepo-napi#build`: a real
cross-toolchain edge scoped to the crate's closure instead of the global
any-Rust-changed bit; `CARGO_BUILD_JOBS` leaves the hash (parallelism
doesn't change artifacts)
- `lockfile-tests#check-lockfiles` and `@turbo/types` schema tasks
depend on the crate build tasks whose binaries they execute
- **Cleanup**: vestigial `turborepo-tests/helpers` package deleted
(`echo_args.rs` → `crates/turborepo/src/bin/`, Cargo auto-discovers it);
orphaned `turborepo-tests/integration/turbo.json`, dead `fixture-%`
Makefile target, and stale workspace globs removed

## How

All verified with the **released** `2.10.4-canary.2` against this
branch: `turbo run build --filter=turbo` executes `cargo build
--package=turbo` (59 crates in the derived input closure, `version.txt`
present); second run is FULL TURBO; `turbo run verify-schema` builds
`turborepo-schema-gen` through the cross-toolchain edge before
verifying; `turborepo-napi#build` derives its cdylib deliverables. The
pre-push hook for this very branch ran the canary against the dogfooded
config. Reviewers: `turbo build --filter=turbo --dry` on this branch
shows the full derived task definition.
anthonyshew pushed a commit that referenced this pull request Jul 6, 2026
## Release v2.10.4

> [!CAUTION]
> Versioned docs aliasing FAILED. [View
logs](https://github.com/vercel/turborepo/actions/runs/28819266360)

### Changes

- release(turborepo): 2.10.3 (#13222) (`02f4ed0`)
- ci: Retry Windows nextest aborts from transient 0xc0000142 spawn
failures (#13240) (`222ffff`)
- perf: Use mimalloc as the global allocator (#13237) (`c42a8a9`)
- perf: Parse yarn v1 lockfiles in a single pass (#13241) (`6207099`)
- ci: Fix cache outputs for Eve app (#13243) (`d8b556d`)
- perf: Stop materializing spans for the disabled daemon log layer
(#13244) (`623aadc`)
- fix: Keep ancestor-scoped bun dependencies resolvable after prune
renames (#13236) (`8658b66`)
- perf: Overlap external dependency hashing with package file hashing
(#13234) (`445b0c7`)
- perf: Build tracked repo index concurrently with package graph
(#13232) (`f4699c6`)
- perf: Release tokio runtime at exit so background DNS lookups never
stall the user (#13231) (`ae635bd`)
- perf: Overhaul pnpm lockfile parsing and dependency closure
computation (#13228) (`669676d`)
- feat: Add futureFlags.experimentalCargoWorkspaces flag (no-op)
(#13227) (`f546930`)
- feat: Introduce toolchain provider abstraction (#13235) (`f03addb`)
- perf: Parse Berry lockfiles in a single pass (#13242) (`6d36125`)
- fix: Apply input exclusion globs to the filesystem walk (#13224)
(`d3f9dd3`)
- fix: Stop root-directory packages from claiming every file in change
mapping (#13225) (`f80203a`)
- chore: Rename turborepo-repository napi package to @turbo/repository
(#13226) (`c1972b3`)
- perf: Hash git blobs with hardware-accelerated SHA-1 (#13245)
(`17ffa0c`)
- release(turborepo): 2.10.4-canary.1 (#13247) (`a31389e`)
- feat: Discover Cargo crates as packages (#13248) (`d813d12`)
- perf: Reuse per-package external dependency hashes in run summaries
(#13249) (`ef7ef06`)
- perf: Stat workspace turbo.json files concurrently during discovery
(#13251) (`57be8e5`)
- chore: Add tracing spans to workspace discovery and globwalk phases
(#13252) (`bd76946`)
- fix: Avoid non-reentrant libc calls in concurrent shutdown process
scans (#13256) (`bf2d827`)
- perf: Memoize resolved task definitions during engine construction
(#13257) (`9085289`)
- perf: Compute transitive closures and external dependency hashes
concurrently with run setup (#13250) (`b305fe2`)
- perf: Probe microfrontends configs in parallel (#13262) (`ea7c1f8`)
- feat: Execute Cargo crate tasks via cargo (#13261) (`ee81349`)
- perf: Pre-size engine task collections (#13265) (`17bc22e`)
- feat: Expose resolved experimentalCI task configuration in `turbo
query` (#13264) (`0f564e5`)
- feat: Derive input and output globs for Cargo tasks (#13263)
(`05b0e74`)
- perf: Parse large pnpm lockfile sections in parallel (#13266)
(`ac4155e`)
- feat: Hash Cargo external dependencies per-crate (#13267) (`71300aa`)
- ci: Install pnpm 10 in musl containers for Library Release (#13269)
(`5ba9f66`)
- ci: Force pnpm overwrite in Library Release musl containers (#13270)
(`560ee2f`)
- perf: Defer the untracked-scan barrier to first file-hash use (#13268)
(`96b0f5a`)
- ci: Fetch API-created commit before updating local ref (#13271)
(`4cbaf48`)
- release(library): 0.0.1-canary.22 (#13272) (`d2e0691`)
- perf: Remove lock and dispatch overhead from task hash precomputation
(#13273) (`dd6de07`)
- fix: Fall back to lockfile detection in @turbo/repository when package
manager is undeclared (#13275) (`8cf0e75`)
- test: Add end-to-end coverage for Cargo workspaces (#13274)
(`ce18f0a`)
- release(library): 0.0.1-canary.23 (#13276) (`8e3a59f`)
- release(turborepo): 2.10.4-canary.2 (#13278) (`7e02f94`)
- fix: Collapse nested package-manager fallback conditional (#13279)
(`af01fdf`)
- feat: Make turbo watch Cargo-aware (#13280) (`39d623e`)
- perf: Skip dependency-closure assembly for toolchains that derive
nothing (#13277) (`ff0d508`)
- feat: Make turbo prune Cargo-aware (#13281) (`ddc584d`)
- fix: Raise the open-file soft limit at startup (#13282) (`947b478`)
- fix: Stop flagging relative imports that resolve into node_modules in
boundaries (#13284) (`11a68c7`)
- perf: Evaluate simple include globs without wax compilation (#13285)
(`189897a`)
- chore: Dogfood native Cargo support in this repository (#13283)
(`42f067b`)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
anthonyshew pushed a commit that referenced this pull request Jul 7, 2026
## Release v2.10.5-canary.1

> [!CAUTION]
> Versioned docs aliasing FAILED. [View
logs](https://github.com/vercel/turborepo/actions/runs/28833246614)

### Changes

- perf: Remove lock and dispatch overhead from task hash precomputation
(#13273) (`dd6de07`)
- fix: Fall back to lockfile detection in @turbo/repository when package
manager is undeclared (#13275) (`8cf0e75`)
- test: Add end-to-end coverage for Cargo workspaces (#13274)
(`ce18f0a`)
- release(library): 0.0.1-canary.23 (#13276) (`8e3a59f`)
- release(turborepo): 2.10.4-canary.2 (#13278) (`7e02f94`)
- fix: Collapse nested package-manager fallback conditional (#13279)
(`af01fdf`)
- feat: Make turbo watch Cargo-aware (#13280) (`39d623e`)
- perf: Skip dependency-closure assembly for toolchains that derive
nothing (#13277) (`ff0d508`)
- feat: Make turbo prune Cargo-aware (#13281) (`ddc584d`)
- fix: Raise the open-file soft limit at startup (#13282) (`947b478`)
- fix: Stop flagging relative imports that resolve into node_modules in
boundaries (#13284) (`11a68c7`)
- perf: Evaluate simple include globs without wax compilation (#13285)
(`189897a`)
- chore: Dogfood native Cargo support in this repository (#13283)
(`42f067b`)
- release(turborepo): 2.10.4 (#13286) (`f2fce38`)
- ci: Remove dead sccache configuration (#13289) (`558df3f`)
- feat: Serve the Remote Cache as an sccache backend for Cargo tasks
(#13288) (`0d9803f`)
- fix: Reject output path traversal (#13290) (`733ccca`)
- fix: Disable the sccache proxy when remote cache use is off (#13291)
(`3249e22`)
- feat: Embed sccache so the Cargo compile cache needs no installation
(#13293) (`b6d0035`)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview – turborepo-eve-agent 793e5eb4 Deployed Jul 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant