anthropics / anthropics/cargo-nix-plugin

CARGO_MANIFEST_DIR is set to the per-build directory, making crates that use env!("CARGO_MANIFEST_DIR") non-reproducible

Aperta
#23 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Rust
Stelle
56
Fork
17
Merge medio
12h 50m
PR unite (30g)
2

Descrizione

## Summary

`build-rust-crate` sets `CARGO_MANIFEST_DIR` to the build's current working directory:

- `builder/src/build_rust_crate/configure.rs:264` — `let cwd = std::env::current_dir()`
- `builder/src/build_rust_crate/configure.rs:300` — `("CARGO_MANIFEST_DIR".into(), cwd)`

Unless the Nix sandbox is enabled with a fixed `sandbox-build-dir`, that directory is unique per build (e.g. `/nix/var/nix/builds/nix--`). Any crate that expands `env!("CARGO_MANIFEST_DIR")` therefore embeds a different string on every build, which changes the crate's **SVH**. Two builds of the *same* derivation produce rlibs that consumers cannot use interchangeably.

Because store paths are input-addressed, Nix cannot detect this: the path, the `-C metadata` hash, the artifact filename and `crate-metadata.json` are all identical. Only the compiled bytes differ.

## Impact

A binary cache accumulates mutually inconsistent artifacts. Once a consumer is cached against one build of such a crate and the cache serves a different build of it, every downstream compile fails with:

```
error[E0463]: can't find crate for `datafusion`
--> src/query/exec.rs:10:5
10 | use datafusion::error::{DataFusionError, Result as DfResult};
| ^^^^^^^^^^ can't find crate
```

The error names the *consumer* (`datafusion`), never the crate that actually diverged (`datafusion-common`), and emits no note. This is very hard to diagnose: the rlib is present, correctly named, and individually valid, so it looks like a missing dependency rather than a fingerprint mismatch. `RUSTC_LOG=rustc_metadata=debug` reveals the real reason:

```
resolving dep `datafusion`->`datafusion_common` hash: `11777f18…` extra filename: `-b0520d2bcd`
Rejecting via hash: expected 11777f18… got 1c395f19…
```

It also silently poisons caches: the artifacts are signed and structurally complete, so nothing flags them as bad, and the failure only appears in a *later* build that mixes generations.

## Reproduction

`datafusion-common` 54.1.0 is a real-world example — `src/test_util.rs:306` does `let dir = env!("CARGO_MANIFEST_DIR");` to locate test data.

```
$ nix build /nix/store/-rust_datafusion-common-54.1.0.drv^lib --rebuild
error: derivation '/nix/store/-rust_datafusion-common-54.1.0.drv' may not be
deterministic: output "/nix/store/-rust_datafusion-common-54.1.0-lib" differs
```

Diffing the two outputs, `lib.rmeta` differs in **exactly 16 bytes** (the SVH), and the object files contain the build directory:

```
build A: /nix/var/nix/builds/nix-86834-218531466/datafusion-common-54.1.0
build B: /nix/var/nix/builds/nix-8433-2030001221/datafusion-common-54.1.0
```

Note that the plugin already passes `--remap-path-prefix==/` to rustc, so the intent to normalize build paths is there — but `--remap-path-prefix` only rewrites spans and debug info, not the string value expanded by `env!`.

## Suggested fix

Set `CARGO_MANIFEST_DIR` to a stable, build-independent path rather than the raw cwd — for example the same normalized value already used for `--remap-path-prefix`, or a constant such as `/build/-`. Anything that does not vary between builds of one derivation would do.

Worth checking the other absolute paths derived from `current_dir()` in the same file for the same issue (`configure.rs:115` `abs_out_dir`, `:478`, `:481`), since `OUT_DIR` is commonly embedded via `include!(concat!(env!("OUT_DIR"), …))`.

## Workaround

Either enable the Nix sandbox so builds get a constant `sandbox-build-dir` (not an option for us — our CI runners are containerized and the sandbox is disabled there), or patch the offending crate per-project:

```nix
datafusion-common = _: {
postPatch = ''
substituteInPlace src/test_util.rs \
--replace-fail 'env!("CARGO_MANIFEST_DIR")' '"/datafusion-common"'
'';
};
```

This makes the crate reproducible (`--rebuild` passes), but it is per-crate whack-a-mole — every crate using this common pattern needs its own patch.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.