bazelbuild / bazelbuild/rules_rust
cargo_build_script: `DEP_<links>_<KEY>` values pointing into OUT_DIR reach dependent build scripts with an unresolved `${out_dir}` token
- Dominant language
- Starlark
- Stars
- 843
- Forks
- 651
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 15
Description
### Summary
A `links` (`-sys`) crate whose build script exposes an `OUT_DIR`-relative path through the metadata convention (e.g. `cargo:include=$OUT_DIR/include`, or `cargo::metadata=include=$OUT_DIR/include`) propagates that value to a **dependent** crate's build script via `DEP__INCLUDE` with the producing crate's `OUT_DIR` rewritten to the literal, unresolved `${out_dir}` token.
The dependent build script therefore sees something like:
```
DEP_LZ4_INCLUDE=/${out_dir}/include
```
The `${out_dir}` substring is never resolved, the directory does not exist, and C/C++ includes that consume `DEP__INCLUDE` fail. Concretely, `librocksdb-sys` cannot find `lz4.h` when built against `lz4-sys`.
### Affected versions
This is a regression from the path-mapping work, not present in older releases:
- Introduced by #4011 ("Add support for `experimental_output_paths`", merged 2026-05-07), which added the `${out_dir}` tokenization of build-script outputs; refined by #4050.
- The `DEP__` metadata propagation that carries the include path comes from #3877.
- The latest tagged release at time of writing is `0.70.0` (2026-04-22), which **predates** #4011 and is unaffected. The bug currently exists on `main` only (not yet in a release).
### Root cause
In `cargo/private/cargo_build_script_runner/lib.rs`, `outputs_to_dep_env` redacts dep-env values with `redact_paths`, which rewrites the producing crate's `out_dir` to the generic `${out_dir}` token:
```rust
Self::escape_for_serializing(Self::redact_paths(env, exec_root, out_dir))
```
That generic token is only resolved by `process_wrapper`'s `--out-dir` flag, for the rustc action of the crate that **directly owns** the build script. But a dep-env (`.depenv`) file is consumed by a **dependent** crate's build-script runner (`bin.rs`), which only substitutes `${pwd}`:
```rust
command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
```
It has no notion of the *producing* crate's `out_dir` and no `${out_dir}` binding, so the token survives unresolved.
This is the same transitive-consumption scenario that `redact_flags` was deliberately written to handle (per-build-script unique tokens plus matching `--subst` entries added on the Starlark side). The dep-env path was left on the generic-token codepath, which has no resolution mechanism on the consuming side.
### Reproduction
`test/cargo_build_script/metadata_dep_env` already exercises `DEP__` propagation. Extending the producer to expose an `OUT_DIR`-relative include dir and asserting the dependent receives a resolved, existing path reproduces it (red before the fix, green after). A real-world trigger is `librocksdb-sys` with the bundled `lz4` feature.
### Proposed fix
In `outputs_to_dep_env`, redact only the exec root (`${pwd}`) and keep the producing crate's real `out_dir`-relative path. This is correct and safe because:
- The consumer is the dependent's build-script runner (`bin.rs`), which already resolves `${pwd}`.
- The producing crate's `out_dir` tree is already a declared input of the dependent build-script action at that same exec-root-relative path.
- Build-script actions do not advertise `supports-path-mapping`, so `--experimental_output_paths=strip` never rewrites that path out from under the literal value.
- The producing crate's own `.env` file (`outputs_to_env`) still uses the `${out_dir}` token, so path-mapping cache-shareability for the owning target is unchanged.
PR with the one-line fix plus unit and end-to-end regression tests to follow.
Reported downstream as hermeticbuild/rules_rs#163.
Contributor guide
Research direction
Start in cargo/private/cargo_build_script_runner/lib.rs at outputs_to_dep_env and inspect the existing test/cargo_build_script/metadata_dep_env coverage. Extend the producer to expose an OUT_DIR-relative include directory, then verify the dependent build script receives a resolved, existing path and that the regression test passes.
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
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100