13 oct 2026
this is a short status update on cargo (libnix) covering
what changed since the libnix-1.87.0+v3 release. with
libnix-1.87.0+v4 the nix backend is no longer limited to
cargo build: tests, cargo run and
cargo install work as well, so a normal day of rust
development can happen in nix mode.
the code can be found here:
https://github.com/nixcloud/cargo/blob/master-libnix/README.md
cargo test works in nix mode: unit, integration and doc
tests, with the same output as vanilla cargo.
the test harnesses and examples are built in nix like every other
unit, from target/<profile>/nix-test with GC roots of
their own, so cargo build and cargo test don’t
delete each other’s store paths. the tests themselves run outside of
nix, like with vanilla cargo:
OUT_DIR and rustc-env of the build scripts
are read from the storeCARGO_BIN_EXE_<name> points at the binary in the
store, and integration tests find the binaries of their package next to
themselves (snapbox, assert_cmd)rustdoc and linker the units were
built with, so the host needs no toolchainenv!("CARGO_MANIFEST_DIR") points at the sources in the
store, which are read-only: fixtures can be read, not writtencargo run runs binaries and examples with the arguments,
exit code, working directory, OUT_DIR and
rustc-env of vanilla cargo.
target/debug/<bin> is a symlink into the store, so
std::env::current_exe() is the store path.
installed binaries stay in the nix store, since they link against
store paths (glibc, the libraries from
Cargo.dependencies.nix). each one is protected by an
indirect GC root and ~/.cargo/bin links to it through that
root:
~/.cargo/
├── .crates.toml / .crates2.json unchanged, cargo install --list works
├── nix/install/
│ └── sd-1.0.0-<hash>/
│ └── sd -> /nix/store/…-sd-1_0_0-bin-…
└── bin/
└── sd -> ../nix/install/sd-1.0.0-<hash>/sd/sd-<hash>
crates.io packages are fetched into the store like any other
dependency. upgrading to another version replaces the root, and
cargo uninstall removes the links and deletes the binary
from the store, unless something else still uses it:
Removing /home/joachim/.cargo/bin/sd
Removed 3 store path(s), 2.4MiB
crate sources and crate builds of an install are left in the store
for nix-collect-garbage, like
~/.cargo/registry they are shared with other builds.
before v4 only the workspace’s own units were rooted, so a
nix-collect-garbage removed every dependency build and
every crate tarball and the next build started over (#4).
now the generated build system lists every unit output, crate
tarball, the toolchain and the external dependencies, and that list is
rooted in target/<profile>/nix/gc. after a successful
build, cargo deletes what the previous build used and the new one
doesn’t, through the nix-daemon, unless another project or a running
process still uses it:
Finished 16 built, 0 fetched, 0 fresh, 0 failed, 0 skipped in 80.6s
Removed 15 unused store path(s), 431.9MiB
each selection of root units (cargo build,
cargo run --bin a, cargo test -p x, other
features) has a slot of its own, so building a subset doesn’t delete
what the full build uses. the 8 most recently built selections are
kept.
cargo clean works in nix mode as well: it removes the
target directory (or a profile) and deletes the store paths its builds
rooted, --dry-run counts them.
failures used to be printed as they happened and got interleaved with the output of other builds. now they are collected and printed after all builds are done, each under a header naming the crate and its derivation:
──── error 1/1: sd (bin) (sd-1_0_0-bin-a45bb84d6cc05f0a) ───────────────────────
error failed to build `sd (bin)` in phase `buildPhase`: PermanentFailure
two fixes, both found with RustPython:
~/.cargo/git/checkouts
(vendored sources, source replacement) no longer crash the
generatortest.nix builds the projects of #18 at pinned
revisions with the cargo (libnix) of the checkout: it
generates each build system in nix (IFD), builds it with rustc/cargo
1.87 and runs every binary with --version.
tests-libnix/run.sh builds them one by one with
--keep-going and writes a RESULTS.md.
23 of 31 projects build and run, among them nushell, rust-analyzer, RustPython, ripgrep, bat, atuin, vaultwarden, tokio and axum. the other 8 (codex, leptos, mdBook, sniffnet, starship, typst, union, wasmtime) need a rustc newer than 1.87 at those revisions, that’s a toolchain limitation and not a libnix one.
flake.nix: cargo-libnix-git-head-via-ifd
(the working tree) and cargo-libnix-git-revision-via-ifd (a
pinned revision) build cargo (libnix) from source via
IFDlibnix-1.87.0+v4 release as its
default cargoshell.nix and how
to export the build system for your own projectthe generated nix code still contains one absolute path: the path of
the .nix file of each build script run ends up in an error
message. so a different output directory rebuilds the build script runs
and everything depending on them. you notice it with
cargo install, which builds in a new temporary directory
every time, and with the IFD builds, which rebuild after every change to
cargo. a fix is planned.
the next big topic is faster iteration (#3): computing a minimal file list per unit from rustc’s dep-info, so nix only rebuilds a root crate when one of its source files changed, and not when someone edits the README.md.
with v4 cargo (libnix) covers build,
test, run, install and
clean, keeps its store paths alive as long as they are
needed and cleans up after itself. it is still slower than vanilla
cargo, but it is now usable for a normal day of rust development.
if you like this work, your support is needed!