bazelbuild / bazelbuild/rules_rust
rust_analyzer: rust-project.json consolidation merges distinct crates with the same crate_id
- Dominant language
- Starlark
- Stars
- 843
- Forks
- 651
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 15
Description
This issue was created with AI assistance and has been human reviewed for accuracy before filing.
**Description**
`rust-project.json` generation can merge distinct Rust crates that happen to share the same `crate_id`.
The current consolidation logic in `tools/rust_analyzer/aquery.rs::consolidate_crate_specs` uses only `CrateSpec.crate_id` as the map key. `crate_id` is derived from the crate root module path, so sharing it is intentional for some cases, such as `rust_test(crate = ":lib")`. However, the same `crate_id` can also occur for crates that should remain distinct.
Current wrong behavior:
A standalone `rust_binary` and `rust_test` using the same root file are consolidated into one rust-analyzer crate. The resulting crate can contain mixed metadata from both targets. In the repro below, the generated crate has `display_name = "app"`, but `env.KIND = "test"`, `env.CARGO_CRATE_NAME = "app_test"`, and `build.label = "//:app_test"`.
Expected behavior:
`rust-project.json` should only merge crate specs when they represent the same logical rust-analyzer crate, such as the existing `rust_test(crate = ":lib")` case. Distinct same-root standalone binary/test crates should remain separate entries.
**Reproduction steps**
Create a minimal workspace using upstream, unpatched `rules_rust`.
`MODULE.bazel`:
```starlark
module(name = "ra_crate_id_repro")
bazel_dep(name = "rules_rust", version = "0.71.3")
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(edition = "2021")
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
```
`BUILD.bazel`:
```starlark
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
rust_binary(
name = "app",
srcs = ["main.rs"],
rustc_env = {"KIND": "binary"},
)
rust_test(
name = "app_test",
srcs = ["main.rs"],
rustc_env = {"KIND": "test"},
)
```
`main.rs`:
```rust
pub fn value() -> u8 {
1
}
fn main() {
let _ = value();
}
#[cfg(test)]
mod tests {
#[test]
fn value_is_one() {
assert_eq!(super::value(), 1);
}
}
```
First verify the targets themselves build/test successfully:
```bash
bazel test //:app_test
bazel build //:app
```
Generate `rust-project.json`:
```bash
bazel run @rules_rust//tools/rust_analyzer:gen_rust_project -- //:app //:app_test
```
Inspect the crates generated for `main.rs`:
```bash
jq '[.crates[] | select(.root_module | endswith("/main.rs"))] | {matching_crate_count: length, crates: [.[] | {display_name, env_KIND: .env.KIND, cargo_crate_name: .env.CARGO_CRATE_NAME, build_label: .build.label, build_target_kind: .build.target_kind}]}' rust-project.json
```
Actual output from the repro:
```json
{
"matching_crate_count": 1,
"crates": [
{
"display_name": "app",
"env_KIND": "test",
"cargo_crate_name": "app_test",
"build_label": "//:app_test",
"build_target_kind": "bin"
}
]
}
```
Expected output:
There should be two distinct crate entries for `main.rs`: one for `//:app` with `KIND = "binary"`, and one for `//:app_test` with `KIND = "test"`.
**Additional context**
This appears to be caused by `consolidate_crate_specs` using `spec.crate_id` alone as the identity key for consolidation.
The existing merge behavior is useful for `rust_test(crate = ":lib")`, where the test and library share a root module and should be merged so rust-analyzer test code lenses work. The problem is that same `crate_id` does not always mean same logical rust-analyzer crate.
The same identity issue can also affect target-specific crate variants when the same crate root appears under multiple target triples/configurations. A fix likely needs to distinguish intentional library/test consolidation from unrelated same-root crates and avoid collapsing target-specific variants into one crate.
Related but distinct issues:
- #1589 discusses consolidation creating dependency cycles.
- #3126 discusses generated-source consolidation behavior.
**Impact**
This can make rust-analyzer use incorrect crate metadata. In practice this can cause wrong runnables/code lenses, wrong build labels, wrong environment variables, wrong dependency/alias metadata, or incorrect target-specific analysis.
The workaround is to carry a local patch that qualifies crate IDs before consolidation for distinct same-root binary/test crates and for non-proc-macro crates appearing under multiple target triples.
**Bazel and rules_rust version**
Reproduced with:
- Bazel: `9.1.1`
- rules_rust: `0.71.3` from Bzlmod
- Also reproduced against upstream `bazelbuild/rules_rust` `main` at `46d67bc1fda4592dab7465c8152d94ae6f969cfc`
Contributor guide
Research direction
Read tools/rust_analyzer/aquery.rs, focusing on consolidate_crate_specs and how CrateSpec.crate_id is used as the consolidation key. Reproduce the issue with the provided MODULE.bazel, BUILD.bazel, and main.rs, then run the Bazel rust-project generator and inspect the generated entries. Done means standalone //:app and //:app_test remain distinct while the existing rust_test(crate = ":lib") consolidation still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- build-system, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100