From c04e7477e5b7e0df600d8fadd8c6da5f06c7520e Mon Sep 17 00:00:00 2001 From: Mick van Gelderen Date: Tue, 17 Mar 2026 11:12:54 -0700 Subject: [PATCH 1/2] rust_analyzer: query fixture tests instead of stripping manual Stop rewriting fixture BUILD files to remove manual tags in the rust-analyzer test runner. Instead, ask Bazel for the test rules in the temporary workspace and invoke them explicitly, so manual assertion tests still run while preserving the original target metadata. Also include those explicit test targets in the strict aspect build path. --- .../rust_analyzer_test_runner.sh | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/rust_analyzer/rust_analyzer_test_runner.sh b/test/rust_analyzer/rust_analyzer_test_runner.sh index 99f579b836..fc38962bbb 100755 --- a/test/rust_analyzer/rust_analyzer_test_runner.sh +++ b/test/rust_analyzer/rust_analyzer_test_runner.sh @@ -54,9 +54,9 @@ EOF cat <"${new_workspace}/.bazelrc" build --keep_going test --test_output=errors -# The 'strict' config is used to ensure extra checks are run on the test -# targets that would otherwise not run due to them being tagged as "manual". -# Note that that tag is stripped for this test. +# The 'strict' config is used to ensure extra checks are run on the fixture +# test targets, including ones that are explicitly invoked despite being tagged +# as "manual". build:strict --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect build:strict --output_groups=+rustfmt_checks build:strict --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect @@ -78,6 +78,7 @@ function rust_analyzer_test() { local workspace="$2" local generator_arg="$3" local rust_log="info" + local -a test_targets=() if [[ -n "${RUST_ANALYZER_TEST_DEBUG:-}" ]]; then rust_log="debug" fi @@ -86,15 +87,8 @@ function rust_analyzer_test() { rm -f "${workspace}"/*.rs "${workspace}"/*.json "${workspace}"/*.bzl "${workspace}/BUILD.bazel" "${workspace}/BUILD.bazel-e" cp -r "${source_dir}"/* "${workspace}" - # Drop the 'manual' tags - if [ "$(uname)" == "Darwin" ]; then - SEDOPTS=(-i '' -e) - else - SEDOPTS=(-i) - fi - sed ${SEDOPTS[@]} 's/"manual"//' "${workspace}/BUILD.bazel" - pushd "${workspace}" &>/dev/null + echo "Generating rust-project.json..." if [[ -n "${generator_arg}" ]]; then @@ -109,12 +103,20 @@ function rust_analyzer_test() { echo "Generating auto-discovery.json..." RUST_LOG="${rust_log}" bazel run "@rules_rust//tools/rust_analyzer:discover_bazel_rust_project" > auto-discovery.json + mapfile -t test_targets < <(bazel query 'kind(".*_test rule", //...)') + echo "Building..." bazel build //... echo "Testing..." - bazel test //... + if (( ${#test_targets[@]} > 0 )); then + bazel test "${test_targets[@]}" + fi echo "Building with Aspects..." - bazel build //... --config=strict + if (( ${#test_targets[@]} > 0 )); then + bazel build //... "${test_targets[@]}" --config=strict + else + bazel build //... --config=strict + fi popd &>/dev/null } From 22f9624c1b39a1f6f1517e451fc58178452b9498 Mon Sep 17 00:00:00 2001 From: Mick van Gelderen Date: Tue, 17 Mar 2026 11:30:27 -0700 Subject: [PATCH 2/2] rust_analyzer: preserve manual targets in discovery Resolve package discovery targets with bazel query instead of relying on :all wildcard build semantics, which drop manual targets from the generated rust-project. Add a regression to the existing auto-discovery static/shared library fixture that opens a manual root-package Rust file by path and asserts that the crate appears in auto-discovery output. The test runner now supports an optional fixture-local discover path for this case. --- .../BUILD.bazel | 8 ++++ .../auto_discovery_json_test.rs | 16 +++++++ .../discover_path | 1 + .../manual_root.rs | 3 ++ .../rust_analyzer_test_runner.sh | 11 ++++- .../bin/discover_rust_project.rs | 12 +++-- tools/rust_analyzer/lib.rs | 45 ++++++++++++++++++- tools/rust_analyzer/rust_project.rs | 26 +++++++++-- 8 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 test/rust_analyzer/auto_discovery_static_and_shared_lib_test/discover_path create mode 100644 test/rust_analyzer/auto_discovery_static_and_shared_lib_test/manual_root.rs diff --git a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/BUILD.bazel b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/BUILD.bazel index c363ad776d..f74236d5f2 100644 --- a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/BUILD.bazel +++ b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/BUILD.bazel @@ -1,5 +1,6 @@ load( "@rules_rust//rust:defs.bzl", + "rust_library", "rust_shared_library", "rust_static_library", "rust_test", @@ -25,6 +26,13 @@ rust_static_library( edition = "2018", ) +rust_library( + name = "manual_root", + srcs = ["manual_root.rs"], + edition = "2018", + tags = ["manual"], +) + rust_test( name = "auto_discovery_json_test", srcs = ["auto_discovery_json_test.rs"], diff --git a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/auto_discovery_json_test.rs b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/auto_discovery_json_test.rs index e015b7cee2..4a59256319 100644 --- a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/auto_discovery_json_test.rs +++ b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/auto_discovery_json_test.rs @@ -30,6 +30,8 @@ mod tests { .unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path)); println!("{}", content); + let mut found_manual_root = false; + for line in content.lines() { let discovery: DiscoverProject = serde_json::from_str(line).expect("Failed to deserialize discovery JSON"); @@ -52,6 +54,20 @@ mod tests { .find(|c| &c.display_name == "greeter_staticlib") .unwrap(); assert!(staticlib.root_module.ends_with("/static_lib.rs")); + + found_manual_root = project.crates.iter().any(|crate_spec| { + crate_spec.display_name == "manual_root" + && crate_spec.root_module.ends_with("/manual_root.rs") + }); + + if found_manual_root { + break; + } } + + assert!( + found_manual_root, + "manual root-package crate was not discovered" + ); } } diff --git a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/discover_path b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/discover_path new file mode 100644 index 0000000000..3c860426dc --- /dev/null +++ b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/discover_path @@ -0,0 +1 @@ +manual_root.rs diff --git a/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/manual_root.rs b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/manual_root.rs new file mode 100644 index 0000000000..c5360a80ab --- /dev/null +++ b/test/rust_analyzer/auto_discovery_static_and_shared_lib_test/manual_root.rs @@ -0,0 +1,3 @@ +pub fn meaning() -> i32 { + 42 +} diff --git a/test/rust_analyzer/rust_analyzer_test_runner.sh b/test/rust_analyzer/rust_analyzer_test_runner.sh index fc38962bbb..404a2bce1c 100755 --- a/test/rust_analyzer/rust_analyzer_test_runner.sh +++ b/test/rust_analyzer/rust_analyzer_test_runner.sh @@ -78,6 +78,7 @@ function rust_analyzer_test() { local workspace="$2" local generator_arg="$3" local rust_log="info" + local discover_arg="" local -a test_targets=() if [[ -n "${RUST_ANALYZER_TEST_DEBUG:-}" ]]; then rust_log="debug" @@ -87,6 +88,10 @@ function rust_analyzer_test() { rm -f "${workspace}"/*.rs "${workspace}"/*.json "${workspace}"/*.bzl "${workspace}/BUILD.bazel" "${workspace}/BUILD.bazel-e" cp -r "${source_dir}"/* "${workspace}" + if [[ -f "${source_dir}/discover_path" ]]; then + discover_arg='{"path":"'"${workspace}/$(<"${source_dir}/discover_path")"'"}' + fi + pushd "${workspace}" &>/dev/null echo "Generating rust-project.json..." @@ -101,7 +106,11 @@ function rust_analyzer_test() { bazel run "@rules_rust//tools/rust_analyzer:validate" -- rust-project.json echo "Generating auto-discovery.json..." - RUST_LOG="${rust_log}" bazel run "@rules_rust//tools/rust_analyzer:discover_bazel_rust_project" > auto-discovery.json + if [[ -n "${discover_arg}" ]]; then + RUST_LOG="${rust_log}" bazel run "@rules_rust//tools/rust_analyzer:discover_bazel_rust_project" -- "${discover_arg}" > auto-discovery.json + else + RUST_LOG="${rust_log}" bazel run "@rules_rust//tools/rust_analyzer:discover_bazel_rust_project" > auto-discovery.json + fi mapfile -t test_targets < <(bazel query 'kind(".*_test rule", //...)') diff --git a/tools/rust_analyzer/bin/discover_rust_project.rs b/tools/rust_analyzer/bin/discover_rust_project.rs index 9598f0519a..9e02895f4f 100644 --- a/tools/rust_analyzer/bin/discover_rust_project.rs +++ b/tools/rust_analyzer/bin/discover_rust_project.rs @@ -53,10 +53,16 @@ fn project_discovery() -> anyhow::Result> { log::info!("resolved rust-analyzer argument: {ra_arg:?}"); - let (buildfile, targets) = ra_arg.into_target_details(&workspace)?; + let (buildfile, targets) = ra_arg.into_target_details( + &bazel, + &output_base, + &workspace, + &bazel_startup_options, + &bazel_args, + )?; log::debug!("got buildfile: {buildfile}"); - log::debug!("got targets: {targets}"); + log::debug!("got targets: {:?}", targets); // Use the generated files to print the rust-project.json. let project = generate_rust_project( @@ -67,7 +73,7 @@ fn project_discovery() -> anyhow::Result> { &bazel_startup_options, &bazel_args, rules_rust_name, - &[targets], + &targets, )?; Ok(DiscoverProject::Finished { buildfile, project }) diff --git a/tools/rust_analyzer/lib.rs b/tools/rust_analyzer/lib.rs index 4ea641e6a0..65adb2e82b 100644 --- a/tools/rust_analyzer/lib.rs +++ b/tools/rust_analyzer/lib.rs @@ -177,7 +177,10 @@ fn source_file_to_buildfile(file: &Utf8Path) -> anyhow::Result { .with_context(|| format!("no buildfile found for {file}")) } -fn buildfile_to_targets(workspace: &Utf8Path, buildfile: &Utf8Path) -> anyhow::Result { +fn buildfile_to_package_target_pattern( + workspace: &Utf8Path, + buildfile: &Utf8Path, +) -> anyhow::Result { log::info!("getting targets for buildfile: {buildfile}"); let parent_dir = buildfile @@ -187,12 +190,50 @@ fn buildfile_to_targets(workspace: &Utf8Path, buildfile: &Utf8Path) -> anyhow::R let targets = match parent_dir { Some(p) if !p.as_str().is_empty() => format!("//{p}:all"), - _ => "//...".to_string(), + _ => "//:all".to_string(), }; Ok(targets) } +fn buildfile_to_targets( + bazel: &Utf8Path, + output_base: &Utf8Path, + workspace: &Utf8Path, + bazel_startup_options: &[String], + bazel_args: &[String], + buildfile: &Utf8Path, +) -> anyhow::Result> { + let target_pattern = buildfile_to_package_target_pattern(workspace, buildfile)?; + let query = format!("kind(rule, {target_pattern})"); + + let output = bazel_command(bazel, Some(workspace), Some(output_base)) + .args(bazel_startup_options) + .arg("query") + .args(bazel_args) + .arg(query) + .output()?; + + if !output.status.success() { + let status = output.status; + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("bazel query failed: ({status:?})\n{stderr}"); + } + + let targets = String::from_utf8(output.stdout)? + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(ToOwned::to_owned) + .collect::>(); + + if targets.is_empty() { + bail!("no rule targets found for buildfile: {buildfile}"); + } + + Ok(targets) +} + #[derive(Debug, Deserialize)] struct ToolchainInfo { sysroot: Utf8PathBuf, diff --git a/tools/rust_analyzer/rust_project.rs b/tools/rust_analyzer/rust_project.rs index 749347ea33..e95e2f448d 100644 --- a/tools/rust_analyzer/rust_project.rs +++ b/tools/rust_analyzer/rust_project.rs @@ -28,15 +28,35 @@ impl RustAnalyzerArg { /// Consumes itself to return a build file and the targets to build. pub fn into_target_details( self, + bazel: &Utf8Path, + output_base: &Utf8Path, workspace: &Utf8Path, - ) -> anyhow::Result<(Utf8PathBuf, String)> { + bazel_startup_options: &[String], + bazel_args: &[String], + ) -> anyhow::Result<(Utf8PathBuf, Vec)> { match self { Self::Path(file) => { let buildfile = source_file_to_buildfile(&file)?; - buildfile_to_targets(workspace, &buildfile).map(|t| (buildfile, t)) + buildfile_to_targets( + bazel, + output_base, + workspace, + bazel_startup_options, + bazel_args, + &buildfile, + ) + .map(|t| (buildfile, t)) } Self::Buildfile(buildfile) => { - buildfile_to_targets(workspace, &buildfile).map(|t| (buildfile, t)) + buildfile_to_targets( + bazel, + output_base, + workspace, + bazel_startup_options, + bazel_args, + &buildfile, + ) + .map(|t| (buildfile, t)) } } }