[[!meta date="2026-10-20 12:00"]]
[[!tag nix libnix cargo rust cargo+libnix]]
[[!series libnix]]
[[!summary cargo libnix 1.99.0+v1: rust 1.99, lld linking, large dependency graphs and build-rs-libnix 0.1.12]]

[[!title libnix rust-1.99.0+v1 release]]

[[!img posts/libnix/libnix-rust-logo.png class="noFancy" style="float: right" width="200px"]]

# 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:

```nix
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](https://github.com/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:

```bash
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](https://github.com/nixcloud/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](https://github.com/nixcloud/cargo/issues/18) at pinned revisions with the `cargo (libnix)` of the checkout and runs every binary:

```bash
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`.


<div class="alert alert-info" role="info">
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.
</div>
