20 oct 2026
this is a short status update on cargo (libnix) covering
what changed since the libnix-1.87.0+v4 release.
libnix-1.99.0+v1 is the first release of a new line: the
libnix patch stack moved from rust 1.87.0 to rust 1.99.0, the current
stable toolchain. rust 1.87 is getting old, and more and more projects
need a newer rustc, so the nix backend has to follow upstream.
the code can be found here:
https://github.com/nixcloud/cargo/blob/master-libnix/README.md
the 34 libnix commits of the 1.87 line were squashed into one patch
and ported onto upstream’s rust-1.99.0 branch (cargo
0.100.0). upstream changed quite a bit in between:
src/cargo/* to
src/*CompileMode was split into UserIntent and
a smaller CompileModeload_overlaid lost its yanked
whitelistGlobalContext is Sync now, so libnix’s
state in it became Mutex/OnceLockthe generated build system pins rustc/cargo 1.99.0 from fenix, still
without rust-src (which would end up in the runtime closure
of every binary).
$ cargo --version
cargo 1.99.0 +libnix
a nice side effect: cargo 1.87 (libnix) could already drive rustc 1.99, since cargo is only a driver. so the bootstrap needed no intermediate compiler, the 1.87 release generated the first build system of the 1.99 line.
since 1.90 rustc links with its own bundled lld on x86_64-linux
(-fuse-ld=lld -B<sysroot>/gcc-ld). that bypasses the
ld wrapper of nix’s cc, which adds the rpaths for the -L
directories. the binaries build fine, but miss their libraries at
runtime:
$ cargo --version
cargo: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory
the units are now built with a wrapped
rustc/rustdoc that passes
-C linker-features=-lld, so they link through nix’s cc/ld
wrapper again:
rustc_1_99_vanilla_pin = pkgs.symlinkJoin {
name = "rustc-1.99.0-nix-ld";
paths = [ toolchain_1_99_vanilla_pin.rustc ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
for b in rustc rustdoc; do wrapProgram $out/bin/$b --add-flags "-C linker-features=-lld"; done
'';
};if you import derivations/default.nix from your own nix
code, pass such a wrapped rustc as well, the README’s
shell.nix shows how.
xai-org/grok-build has ~1600 units and found two scaling problems:
builtins.genericClosure now,
which visits every unit onceArgument list too long). they are passed as a file now
(passAsFile)with both fixes grok-build builds in nix mode:
1605 units, 0 failed.
in nix mode a build script run is a derivation of its own, and the
units built after it can’t ask cargo anything. so the
cargo: directives printed by build.rs are
parsed inside that derivation by build-rs-libnix,
which writes the result to $out/nix
(rustc-arguments, rustc-propagated-arguments,
environment-variables) for the dependent derivations to
read.
0.1.12 adds cargo:rustc-link-arg, which
tikv-jemalloc-sys uses (-pthread) and which
used to fail with “not implemented”:
$ build-rs-libnix --script-output build_script_build.out --out-dir out
$ cat out/rustc-arguments
-l 'static=jemalloc_pic' -C link-arg='-pthread'
for a while it was vendored into cargo, now it lives in its own
repository again and is fetched by commit and hash from the generated
build_rs_libnix.nix. the 1.87 and the 1.99 line pin the
same version, the cargo: protocol didn’t change between
them. every build script run depends on it, so changing the pin rebuilds
all build script runs and everything after them; fixes go to
build-rs-libnix first and each branch bumps its pin in one step.
the unused crates.io dependency on build-rs-libnix in cargo’s
Cargo.toml is gone as well.
cargo_build_caller.nix used a hardcoded
project_root = ../../.., which only works with the default
target directory. it is computed relative to the actual target directory
now, so CARGO_TARGET_DIR works.
the branches follow upstream’s naming now, one maintenance branch per toolchain:
| branch | what it is |
|---|---|
master-libnix |
the main line, currently rust 1.99.0 |
rust-1.99.0-libnix |
the 1.99 line |
rust-1.87.0-libnix |
the 1.87 line (libnix-1.87.0+v4 and later fixes) |
releases are tags named after the toolchain plus a libnix revision,
libnix-1.99.0+v1, and stay valid no matter what happens to
the branches. the 1.87 line got build-rs-libnix 0.1.12 too, so a
libnix-1.87.0+v5 is possible.
cargo-libnix-1_99_0_plus_v1 is the default package and
in the dev shellbuildCargoBin (the IFD builds
cargo-libnix-git-head-via-ifd and
cargo-libnix-git-revision-via-ifd) takes the generator and
its rustc as arguments: v1 by default, v4 for the pinned 1.87 revision.
the substituteInPlace hack, which patched the v4 output to
rustc 1.99 during the bootstrap, is gonetest.nix builds the projects of #18 at pinned
revisions with the cargo (libnix) of the checkout and runs
every binary:
all 31 projects build and run with rustc 1.99, among them nushell, rust-analyzer, RustPython, ripgrep, bat, atuin, vaultwarden, typst, wasmtime, tokio, axum and grok-build (new). changes to the list:
E0365 in
hir-def)#![feature])the 1.87 line passes 30 of 31 with build-rs-libnix 0.1.12.
build-rs-libnix 0.1.12 ignores the cargo:: syntax (rust
>= 1.77) with a warning, and some directives aren’t implemented yet
(rustc-flags,
rustc-link-arg-{bin,bins,tests,examples,cdylib}, generic
metadata). a scan of the build script outputs of all 31 test projects
found 7 crates printing cargo:: lines, so far all of them
harmless (rustc-check-cfg, rerun-if-changed,
an env var already set in Cargo.dependencies.nix, a crate
compiled out on stable). but that’s luck: a crate setting a cfg, a
library or an env!() variable only via cargo::
builds wrong in nix mode. more crates will switch as they raise their
rust-version.
cargo:: syntax and the
missing directives, ideally by porting upstream’s own parser from
custom_build.rs, released together as
libnix-1.99.0+v2 and libnix-1.87.0+v5with v1 cargo (libnix) follows upstream to rust 1.99.0.
linking works again with the new rustc, large projects like grok-build
evaluate and build in nix mode, and all 31 regression test projects
pass. the 1.87 line stays available as
rust-1.87.0-libnix.
if you like this work, your support is needed!