bazelbuild / bazelbuild/rules_rust

cargo_build_script omits `--remap-path-prefix`, making build script output path-dependent

Open
#4,201 0 comments 0 reactions 0 assignees View on GitHub
needs-triage
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.