Description
Two bugs in gen_rust_project that together make rust-analyzer non-functional when using bzlmod with local_path_override.
Bug 1: External crate paths point into the ephemeral execroot
gen_rust_project generates root_module paths like:
{output_base}/execroot/_main/external/rules_rust+crate+anyhow-1.0.0/src/lib.rs
These paths do not exist after the build completes. The execroot/ directory is a build-time sandbox; its external/ symlinks are only valid during an action. The real cached sources live at:
{output_base}/external/rules_rust+crate+anyhow-1.0.0/src/lib.rs
Because rust-analyzer cannot read these paths, it has no type information for any external crate — no completions, no go-to-definition, no inline documentation for dependencies like clap, anyhow, serde, etc.
The root cause is in tools/rust_analyzer/lib.rs, deserialize_file_content: __EXEC_ROOT__ is replaced with execution_root ({output_base}/execroot/_main), which is correct for bazel-out/ paths but wrong for external/ paths.
Bug 2: local_path_override modules not detected as workspace members
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/<module>+/. The aspect marks these crates is_workspace_member: false because the build system has no way to communicate this. rust-analyzer therefore shows no diagnostics (errors, warnings) for these crates.
After Bug 1 is fixed, the root_module path for such crates ({output_base}/external/<module>+/src/main.rs) resolves through the symlink back into the workspace tree. The fix is to canonicalize the path and check if it falls under workspace.
Reproduction steps
Minimal bzlmod workspace with a local_path_override module:
# MODULE.bazel (root)
bazel_dep(name = "rules_rust", version = "0.70.0")
bazel_dep(name = "mypkg", version = "0.1.0")
local_path_override(module_name = "mypkg", path = "mypkg")
# mypkg/MODULE.bazel
module(name = "mypkg", version = "0.1.0")
bazel_dep(name = "rules_rust", version = "0.70.0")
crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate")
crate.from_cargo(name = "crates", manifests = ["//tools/mybin:Cargo.toml"])
use_repo(crate, "crates")
# mypkg/BUILD.bazel
rust_binary(name = "mybin", srcs = ["src/main.rs"], deps = ["@crates//:anyhow"])
// mypkg/src/main.rs
fn main() { let _e = anyhow::anyhow!("test"); }
Run gen_rust_project and open src/main.rs in any editor with rust-analyzer:
anyhow:: produces no completions (Bug 1)
- Errors in
src/main.rs are not shown (Bug 2)
Proposed fixes:
tools/rust_analyzer/lib.rs:
let content = fs::read_to_string(path)?
...
.replace("__EXEC_ROOT__", execution_root.as_str())
.replace("__OUTPUT_BASE__", output_base.as_str())
// External crate sources live at output_base/external/, not inside execroot.
// execroot symlinks are ephemeral (only valid during a build action).
.replace(
&format!("{}/external/", execution_root),
&format!("{}/external/", output_base),
);
tools/rust_analyzer/rust_project.rs, inside assemble_rust_project:
// Crates from local_path_override modules resolve through an external/ symlink
// back into the real workspace tree. Detect this so rust-analyzer shows diagnostics.
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);
Additional context
Workaround (used in our project): post-process the generated rust-project.json with a Python script that replaces execroot/_main/external/ with external/ in all paths and sets is_workspace_member: true for crates whose root_module resolves to a path under the workspace.
Bug 1 likely affects all bzlmod projects, not just those using local_path_override, since execroot/external/ symlinks are never stable across builds.
Impact
rust-analyzer is effectively broken for any Rust code in a bzlmod local_path_override module. Workaround exists (post-processing script) but it is fragile. Does not block updating to a newer rules_rust version, but makes IDE support unusable without the workaround.
Bazel and rules_rust version
- Bazel: 9.x (bzlmod)
- rules_rust: 0.70.0
Description
Two bugs in
gen_rust_projectthat together make rust-analyzer non-functional when using bzlmod withlocal_path_override.Bug 1: External crate paths point into the ephemeral execroot
gen_rust_projectgeneratesroot_modulepaths like:These paths do not exist after the build completes. The
execroot/directory is a build-time sandbox; itsexternal/symlinks are only valid during an action. The real cached sources live at:Because rust-analyzer cannot read these paths, it has no type information for any external crate — no completions, no go-to-definition, no inline documentation for dependencies like
clap,anyhow,serde, etc.The root cause is in
tools/rust_analyzer/lib.rs,deserialize_file_content:__EXEC_ROOT__is replaced withexecution_root({output_base}/execroot/_main), which is correct forbazel-out/paths but wrong forexternal/paths.Bug 2:
local_path_overridemodules not detected as workspace membersIn bzlmod, a module declared with
local_path_overridehas its sources in the real workspace tree, accessed via a symlink at{output_base}/external/<module>+/. The aspect marks these cratesis_workspace_member: falsebecause the build system has no way to communicate this. rust-analyzer therefore shows no diagnostics (errors, warnings) for these crates.After Bug 1 is fixed, the
root_modulepath for such crates ({output_base}/external/<module>+/src/main.rs) resolves through the symlink back into the workspace tree. The fix is to canonicalize the path and check if it falls underworkspace.Reproduction steps
Minimal bzlmod workspace with a
local_path_overridemodule:Run
gen_rust_projectand opensrc/main.rsin any editor with rust-analyzer:anyhow::produces no completions (Bug 1)src/main.rsare not shown (Bug 2)Proposed fixes:
tools/rust_analyzer/lib.rs:tools/rust_analyzer/rust_project.rs, insideassemble_rust_project:Additional context
Workaround (used in our project): post-process the generated
rust-project.jsonwith a Python script that replacesexecroot/_main/external/withexternal/in all paths and setsis_workspace_member: truefor crates whoseroot_moduleresolves to a path under the workspace.Bug 1 likely affects all bzlmod projects, not just those using
local_path_override, sinceexecroot/external/symlinks are never stable across builds.Impact
rust-analyzer is effectively broken for any Rust code in a bzlmod
local_path_overridemodule. Workaround exists (post-processing script) but it is fragile. Does not block updating to a newer rules_rust version, but makes IDE support unusable without the workaround.Bazel and rules_rust version