Drop-in Cargo fork
Cargo that cleans up after itself
Every git worktree of a project shares one build directory, which stays under a size limit on its own. There is nothing to configure.
Disk used
Build time
$ curl -fsSL https://huacnlee.github.io/cargo/install.sh | sh
> irm https://huacnlee.github.io/cargo/install.ps1 | iex
Installs over ~/.cargo/bin/cargo and keeps the rustup proxy as cargo.rustup. cargo +nightly and rust-toolchain.toml keep working. Run the script with --uninstall to put the proxy back.
Install once, stay current. At most once a day, after a command, cargo checks for a new release in the background and installs it: checksum verified, renamed into place, nothing printed. The last result is in ~/.cargo/.fork-update.log. Set CARGO_FORK_AUTO_UPDATE=0 to turn it off; it never runs in CI.
Every new worktree costs less
- Cargo
- This fork
Total disk
Total build time
Each worktree is a fresh checkout building one binary. Cargo gives every worktree its own target directory and compiles all dependencies again. The fork compiles them once and reuses them, so later worktrees only build the workspace crates.
Projects share dependencies too
What a cleanup looks like
After a build, at most once an hour, Cargo removes what no checkout has used for a week, then the least recently used units until the project fits. It prints one line when it frees something.
$ cargo build Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.89s Cleaned 680 stale build units, 10.1GiB freed (build.gc)
What changes
Your projects, Cargo.toml and .cargo/config.toml stay as they are.
- One build directory per project. Intermediate files go to
~/.cargo/targets/<name>-<hash>/, where the hash comes from the repository's.gitdirectory. Every worktree lands there wherever it is created, reuses compiled dependencies, and only builds its own crates. Binaries still appear in./target. - Fixed directories.
./targetand~/.cargo/targetscannot be moved:--target-dir,CARGO_TARGET_DIR,build.target-dirandbuild.build-dirare ignored with a warning, so no flag, env var or tool can scatter build output elsewhere. - Dependencies shared by every project. Registry and git dependencies build once in
~/.cargo/targets/shared-deps/and are reused by any project that needs the same version, features and profile, like Go's build cache.cargo check, clippy, rust-analyzer andcargo docuse the same builds, so a check after a build only looks at your own crates. - Your own crates shared by worktrees too. A workspace crate is keyed by the content of its files instead of the checkout it is in, so a new worktree of the same commit reuses every crate another worktree built, and switching back to a branch reuses what it built before. Editing a test or a binary rebuilds only that target. Crates with a build script, and crates that read their own path (
env!("CARGO_MANIFEST_DIR")), still build per worktree. Worktrees also share rustc's incremental caches, so the first edit in a new worktree is incremental too. For this rustc records.as the compile directory (-Zremap-cwd-prefix=.; on stable throughRUSTC_BOOTSTRAP=1with no#![feature]allowed), so start a debugger from the workspace root. - A size limit. Each project keeps to twice its largest build of the last day, and its units unused for a day are removed. Shared dependencies are kept for 7 days and keep to 50 GiB; a project no build has used for 7 days is removed whole. The units an edit replaces are removed right away, unless another worktree uses them. Older builds of a shared dependency that was rebuilt for new dependencies go after a day, and so do stale test directories in
CARGO_TARGET_TMPDIRand binaries in./targetwhose build unit is gone. Nothing a running build has locked is removed. - No duplicate object files on macOS. Debuggers and backtraces read the objects inside each rlib, so the loose copies beside it are removed.
- Worktrees build at the same time. Fine-grained locking is on, so parallel agents do not queue on one lock. When two builds need the same dependency at once, one compiles it and the other reuses the result.
- Less debug info in dev builds. Line tables for your crates, none for dependencies. Backtraces keep file and line. A
debugsetting in your profile still wins. - Metadata stored once on nightly. An rlib no longer carries a second copy of its
.rmetaon a nightly rustc, about 30% off the shared dependencies. - Updates itself. Once a day at most, a background check installs the latest release, so you never rerun the installer.
CARGO_FORK_AUTO_UPDATE=0turns it off. - Cranelift on nightly.
cargo buildcompiles your crates with Cranelift and runs the compiler front end on up to 8 threads. Tests and release builds keep LLVM. The installer adds the Cranelift component when you have a nightly toolchain.
Change a default
# ~/.cargo/config.toml [build.gc] max-size = "auto" # or "15 GiB", "none" shared-max-size = "50 GiB" max-unused = "1 day" shared-max-unused = "7 days" frequency = "1 hour" # or "always", "never" enabled = true
Set CARGO_SHARED_DEPS=0 to keep dependencies in the project's build directory, CARGO_CHECK_SHARED_DEPS=0 to only check them in cargo check, CARGO_SHARE_WORKSPACE_UNITS=0 to build your crates per worktree, CARGO_DEV_CRANELIFT=0 to keep LLVM on nightly, and CARGO_RUSTC_THREADS=1 for a single-threaded front end.