diff --git a/tools/rust_analyzer/bin/flycheck.rs b/tools/rust_analyzer/bin/flycheck.rs index b7fc3c93ee..06808c3289 100644 --- a/tools/rust_analyzer/bin/flycheck.rs +++ b/tools/rust_analyzer/bin/flycheck.rs @@ -457,6 +457,23 @@ fn resolve_label_for( query_label_for(bazel, workspace, saved_file) } +/// Produces the query string used by `query_label_for` +fn query_string_for(workspace: &Utf8Path, saved_file: &Utf8Path) -> Result { + let file_rel = saved_file + .strip_prefix(workspace) + .with_context(|| format!("saved file {saved_file} is not under workspace {workspace}"))?; + let package = find_owning_package(workspace, file_rel).with_context(|| { + format!("no BUILD.bazel found above {saved_file} — is this file part of a Bazel target?") + })?; + + // Generate a label that preserves the package-relative file path. + let file_in_package = file_rel.strip_prefix(&package).with_context(|| { + format!("saved file {saved_file} is not under Bazel package //{package}") + })?; + let pattern = format!("//{package}:{file_in_package}"); + Ok(format!("attr(srcs, {pattern:?}, //{package}:*)")) +} + /// `bazel query 'attr(srcs, "", //:*)'` scoped to the /// nearest `BUILD.bazel`. Returns the first match — if a file belongs /// to several targets, any is correct. @@ -465,17 +482,7 @@ fn query_label_for( workspace: &Utf8Path, saved_file: &Utf8Path, ) -> Result { - let file_rel = saved_file - .strip_prefix(workspace) - .with_context(|| format!("saved file {saved_file} is not under workspace {workspace}"))?; - let package = find_owning_package(workspace, file_rel).with_context(|| { - format!("no BUILD.bazel found above {saved_file} — is this file part of a Bazel target?") - })?; - let file_basename = file_rel - .file_name() - .with_context(|| format!("saved file {saved_file} has no file name"))?; - let pattern = format!("//{package}:{file_basename}"); - let query = format!("attr(srcs, {pattern:?}, //{package}:*)"); + let query = query_string_for(workspace, saved_file)?; let output = Command::new(bazel.as_str()) .current_dir(workspace) .arg("query") @@ -492,7 +499,7 @@ fn query_label_for( .lines() .find(|l| !l.is_empty()) .map(str::to_owned) - .with_context(|| format!("bazel query returned no targets for {pattern}")) + .with_context(|| format!("bazel query {query:?} returned no targets")) } /// Walk up looking for `BUILD.bazel` or `BUILD`. Returns the @@ -523,8 +530,49 @@ fn scopeguard(path: Utf8PathBuf) -> impl Drop { #[cfg(test)] mod tests { use super::*; + use gen_rust_project_lib::make_workspace; use serde_json::json; + #[test] + fn find_owning_package_test() { + let pkg_path = "example/library"; + let build_path = format!("{pkg_path}/BUILD.bazel"); + let rust_src_path = format!("{pkg_path}/src/main.rs"); + let workspace = make_workspace( + "find_owning_package_test", + &[ + ("MODULE.bazel", ""), + (&build_path, "package()"), + (&rust_src_path, "fn main() {}"), + ], + ); + + assert_eq!( + find_owning_package(&workspace, &Utf8PathBuf::from(&rust_src_path)).unwrap(), + pkg_path + ); + } + + #[test] + fn query_test() { + let pkg_path = "example/library"; + let build_path = format!("{pkg_path}/BUILD.bazel"); + let rust_src_path = format!("{pkg_path}/src/main.rs"); + let workspace = make_workspace( + "query_test", + &[ + ("MODULE.bazel", ""), + (&build_path, "package()"), + (&rust_src_path, "fn main() {}"), + ], + ); + + assert_eq!( + query_string_for(&workspace, &workspace.join(&rust_src_path)).unwrap(), + r#"attr(srcs, "//example/library:src/main.rs", //example/library:*)"# + ); + } + #[test] fn relative_file_names_become_absolute() { let workspace = Utf8Path::new("/abs/ws"); diff --git a/tools/rust_analyzer/bin/setup.rs b/tools/rust_analyzer/bin/setup.rs index 70bad8a34a..09154426b9 100644 --- a/tools/rust_analyzer/bin/setup.rs +++ b/tools/rust_analyzer/bin/setup.rs @@ -1273,6 +1273,7 @@ fn generate_settings_json(ctx: &SetupCtx, launcher_dir: &Utf8Path) -> String { #[cfg(test)] mod tests { use super::*; + use gen_rust_project_lib::make_workspace; fn dummy_toolchain() -> ToolchainBinaries { ToolchainBinaries { @@ -1831,20 +1832,6 @@ mod tests { // `.code-workspace` support // ----------------------------------------------------------------- - /// Build a workspace dir in $TMPDIR, populated with the listed - /// files. Returns the dir path; caller is responsible for cleanup - /// (use `remove_dir_all` in a `_guard`-style drop, or accept the - /// leak — TMPDIR gets cleaned eventually). - fn make_workspace(tag: &str, files: &[(&str, &str)]) -> Utf8PathBuf { - let tmp = std::env::temp_dir().join(format!("setup_{tag}_{}", std::process::id())); - let _ = std::fs::remove_dir_all(&tmp); - std::fs::create_dir_all(&tmp).unwrap(); - for (name, content) in files { - std::fs::write(tmp.join(name), content).unwrap(); - } - Utf8PathBuf::try_from(tmp).unwrap() - } - #[test] fn merge_under_settings_key_preserves_top_level_keys() { let (ctx, _launcher_dir) = dummy_ctx(); diff --git a/tools/rust_analyzer/lib.rs b/tools/rust_analyzer/lib.rs index 6733caedf3..1e95045328 100644 --- a/tools/rust_analyzer/lib.rs +++ b/tools/rust_analyzer/lib.rs @@ -4,7 +4,7 @@ mod cache; mod rust_project; pub mod user_config; -use std::{collections::BTreeMap, fs, process::Command}; +use std::{collections::BTreeMap, convert::TryFrom, fs, process::Command}; use anyhow::{bail, Context}; use camino::{Utf8Path, Utf8PathBuf}; @@ -553,6 +553,22 @@ pub struct ToolchainInfo { pub version: String, } +/// Build a workspace dir in $TMPDIR, populated with the listed +/// files. Returns the dir path; caller is responsible for cleanup +/// (use `remove_dir_all` in a `_guard`-style drop, or accept the +/// leak — TMPDIR gets cleaned eventually). +pub fn make_workspace(tag: &str, files: &[(&str, &str)]) -> Utf8PathBuf { + let tmp = std::env::temp_dir().join(format!("setup_{tag}_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&tmp); + std::fs::create_dir_all(&tmp).unwrap(); + for (name, content) in files { + let abs_path = tmp.join(name); + std::fs::create_dir_all(abs_path.parent().unwrap()).unwrap(); + std::fs::write(abs_path, content).unwrap(); + } + Utf8PathBuf::try_from(tmp).unwrap() +} + #[cfg(test)] mod tests { use super::*;