bazelbuild / bazelbuild/rules_rust
gen_rust_project: crates from local_path_override modules are not marked as workspace members
- Dominant language
- Starlark
- Stars
- 843
- Forks
- 651
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 15
Description
**Description**
Follow-up to #4057, split out as requested by @UebelAndre (Bug 1 was fixed in #4212; this issue covers Bug 2).
In bzlmod, a module declared with `local_path_override` has its sources in the real workspace tree, accessed via a symlink at `{output_base}/external/+/`. The rust-analyzer aspect marks crates from these modules `is_workspace_member: false` — from the aspect's perspective they are just another external repository. rust-analyzer therefore shows no diagnostics (errors, warnings, clippy) for these crates, even though they are first-party code the user is actively editing.
**Reproduction**
Minimal bzlmod workspace with a `local_path_override` module:
```python
# MODULE.bazel (root)
bazel_dep(name = "rules_rust", version = "0.73.0")
bazel_dep(name = "mypkg", version = "0.1.0")
local_path_override(module_name = "mypkg", path = "mypkg")
```
```python
# mypkg/MODULE.bazel
module(name = "mypkg", version = "0.1.0")
bazel_dep(name = "rules_rust", version = "0.73.0")
```
```python
# mypkg/BUILD.bazel
rust_binary(name = "mybin", srcs = ["src/main.rs"])
```
Run `gen_rust_project` and open `mypkg/src/main.rs` in an editor with rust-analyzer: errors in the file are not shown, because the crate is not a workspace member.
**Use case**
Our monorepo is composed of multiple Bazel modules stitched together with `local_path_override` — some of them are published separately as OSS, so they must remain standalone modules. All of that code is first-party: we edit it daily and need diagnostics for it. I'd expect anyone splitting a bzlmod workspace into local modules (or using `--override_module` to develop against a local checkout of a dependency) to hit this.
**Possible approaches**
*1. Path-based detection (no override knowledge needed)*
With #4212 merged, the `root_module` path for such crates (`{output_base}/external/+/src/main.rs`) resolves through the symlink back into the workspace tree. So gen_rust_project can canonicalize the path and check whether it falls under `workspace`:
```rust
let is_workspace_member = c.is_workspace_member
|| std::path::Path::new(&c.root_module)
.canonicalize()
.map(|p| p.starts_with(workspace.as_std_path()))
.unwrap_or(false);
```
This is what we run in production today (as a patch). Two caveats we learned the hard way:
- If you canonicalize `root_module` for the emitted `rust-project.json` (which helps editors that report real paths in file URIs), you must canonicalize `source.include_dirs` in lockstep — otherwise rust-analyzer's file→crate mapping breaks and every item from the crate shows as an unresolved import.
- Generated sources from *other* modules land under `{execution_root}/bazel-out/.../external/+/…`; a naive "resolves under workspace" check must not be confused by those (they don't resolve into the workspace, but they also shouldn't be canonicalized into oblivion — `canonicalize()` fails on paths whose exec-configuration variant doesn't exist).
*2. Asking Bazel which modules are overridden*
@UebelAndre asked in #4057 whether Bazel exposes overrides as a general utility. It does, via the `mod` subcommand:
- `bazel mod graph --depth 1` lists overridden modules with the version placeholder `_` (e.g. `mypkg@_`) — this covers non-registry overrides generally, i.e. both `local_path_override` and `--override_module`.
- `bazel mod graph --depth 1 --output json` gives the same, machine-readable.
- `bazel mod show_repo ` reveals the backing repo rule, which for a local override is a `local_repository` with its `path`.
So gen_rust_project could run one cheap `bazel mod graph --output json --depth 1` and mark crates whose repository belongs to an overridden module as workspace members. This is more explicit than path canonicalization, at the cost of one extra bazel invocation and coupling to `bazel mod` output.
The two approaches can also be combined: use `bazel mod` to decide *which* external repos are really local, and path canonicalization only for those.
Happy to prepare a PR once there's agreement on the preferred direction.
**Bazel and rules_rust version**
- Bazel: 9.x (bzlmod)
- rules_rust: 0.73.0 / main
Contributor guide
Research direction
Start with the gen_rust_project entry point and reproduce the minimal bzlmod workspace using local_path_override described in the issue. Inspect the generated rust-project.json and the crate's root_module and source.include_dirs paths; done means crates from local overrides are marked as workspace members and rust-analyzer reports diagnostics without breaking file-to-crate mapping.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100