lastlog.de/blog
  • timeline
  • about
  • roadmap
  • websocket
  • drafts
  • special pages
  • login
prev. article
libnix

libnix rust-1.99.0+v1 release

20 oct 2026

nixlibnixcargorustcargo+libnix

motivation

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

improvements

rust 1.99.0

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:

  • the sources moved from src/cargo/* to src/*
  • CompileMode was split into UserIntent and a smaller CompileMode
  • the build plan was removed
  • collections use FxHash, load_overlaid lost its yanked whitelist
  • GlobalContext is Sync now, so libnix’s state in it became Mutex/OnceLock

the 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.

linking with rustc >= 1.90

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.

large dependency graphs

xai-org/grok-build has ~1600 units and found two scaling problems:

  • evaluation: the library closure of each unit was computed by a plain recursion, which visits every path through the dependency graph. that grows exponentially with its depth, minutes to hours of evaluation. it uses builtins.genericClosure now, which visits every unit once
  • building: the store paths of all libraries of a unit were passed inline to the builder, and hundreds of them exceed the 128 KiB limit of a single argument (Argument list too long). they are passed as a file now (passAsFile)

with both fixes grok-build builds in nix mode:

CARGO_BACKEND=nix cargo build -p xai-grok-pager-bin --release

1605 units, 0 failed.

build-rs-libnix 0.1.12

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.

project_root and CARGO_TARGET_DIR

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.

branches

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.

flake

  • cargo-libnix-1_99_0_plus_v1 is the default package and in the dev shell
  • buildCargoBin (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 gone

regression tests

test.nix builds the projects of #18 at pinned revisions with the cargo (libnix) of the checkout and runs every binary:

nix-build test.nix -A grok-build.smoke
tests-libnix/run.sh

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:

  • rust-analyzer moved to its release 2026-10-19, the old pin doesn’t compile with rustc 1.99 (E0365 in hir-def)
  • union was removed, it needs a nightly rustc (#![feature])
  • codex, leptos, mdBook, sniffnet, starship, typst and wasmtime are still pinned to their last release before they needed a rustc newer than 1.87, they can move to newer revisions now

the 1.87 line passes 30 of 31 with build-rs-libnix 0.1.12.

known issue

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.

outlook

  • build-rs-libnix 0.1.13: the 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+v5
  • content-addressed derivations for the build script runs, so a parser update only rebuilds the crates whose parsed output actually changed
  • faster iteration (#3) is still the next big topic

summary

with 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!

  • consider a star at https://github.com/nixcloud/cargo/stargazers to show your support.
  • comments are welcome in https://discourse.nixos.org/t/libnix-feedback/75193
  • if you’re interested in supporting this development, please reach out to me, see https://lastlog.de/cv for details.
article source