bazelbuild / bazelbuild/rules_rust
cargo_build_script omits `--remap-path-prefix`, making build script output path-dependent
- Dominant language
- Starlark
- Stars
- 843
- Forks
- 651
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 15
Description
**Description**
Build scripts that invoke `rustc` bake the exec root into their output, because `cargo_build_script` does not include `--remap-path-prefix` in the `CARGO_ENCODED_RUSTFLAGS` it passes to them.
`rustix`'s `build.rs` probes for compiler features by running `rustc` with the source on stdin and `--emit=metadata`. Since the source arrives on stdin, the working directory is the only path input `rustc` has, and it folds that into the emitted metadata. The probe writes to `$OUT_DIR`, a declared build-script output, which `rules_rust` hashes into the cache key of every dependent crate.
Current behaviour: the same source built at two different absolute paths produces different build-script output, and therefore different cache keys for the whole dependent chain.
Expected behaviour: build-script rustc invocations are path-independent, as every other rustc invocation in the ruleset already is. `construct_arguments()` (rust/private/rustc.bzl:928) applies `--remap-path-prefix` to all of them, under the comment "For determinism to help with build distribution and such". Build scripts reach `rustc` indirectly, via `CARGO_ENCODED_RUSTFLAGS` rather than `construct_arguments()`, so they never inherited it. `rustdoc` is the only other exception and is documented as such.
**Reproduction steps**
Stand-alone repo: https://github.com/cpcwood/bazel-rules-rust-rustix-cache-bust-demo
The CI pipeline shows the proof, but you can run it locally too:
```
nix develop -c ./minimal-repro.sh # no Bazel
nix develop -c ./prove.sh # four Bazel workspaces
```
`minimal-repro.sh` isolates the mechanism to `rustc` and two directories.
`prove.sh` builds a two-crate workspace (`mylib` → `rustix`, `myapp` → `mylib`) unpatched and patched, at a short and a long path, each with an explicit `--output_base`, because the exec root, not the workspace path, is what reaches `rustc`.
**Additional context**
Probe output (`$OUT_DIR/rustix_test_can_compile`):
```
unpatched e27190bc… (short path)
unpatched 01079d73… (long path) differ
patched 7e887e12… (both paths) identical
```
Actions genuinely re-executed on the second path (`cacheHit: false` in the execution log):
```
unpatched 3 Rustc actions: rustix, //:mylib, //:myapp
patched 0
```
The whole dependent chain re-executes, not just `rustix`.
Two things that keep this hidden most of the time:
- The `CargoBuildScriptRun` action's own cache key is path-independent, so on a warm cache the build script is served as a hit and the divergent bytes never propagate. It only bites when the build script genuinely executes at a path that has not populated the cache. `prove.sh` forces that condition explicitly.
- Bazel's default output base is an MD5 of the workspace path, so a CI job that always checks out to the same path never sees it.
**Impact**
Intermittent cache misses on unchanged source wherever a cache is shared across environments with different checkout paths, a developer's machine and CI being the common case. When it hits, the entire dependent chain of any crate whose build script shells out to `rustc` rebuilds.
Not a blocker, and no upgrade is prevented. Affected users can patch it locally via `single_version_override`, which is what the reproduction repo does.
**Bazel and rules_rust version**
- Bazel 8.4.2
- rules_rust 0.73.0 (from BCR)
- rustix 1.1.4, Rust toolchain 1.97.1, edition 2021
- Reproduced on `aarch64-darwin` and `ubuntu-latest`. Hashes are platform-specific.
**PR**
I've made a PR to fix this here: https://github.com/bazelbuild/rules_rust/pull/4202
Contributor guide
Research direction
Start with construct_arguments() in rust/private/rustc.bzl around line 928, then trace how cargo_build_script assembles CARGO_ENCODED_RUSTFLAGS. Run minimal-repro.sh and prove.sh from the linked reproduction repository to observe the path-dependent output. Done means build-script rustc output and the dependent cache remain identical across the tested paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100