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 99f579b836..404a2bce1c 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,8 @@ 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" fi @@ -86,15 +88,12 @@ 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) + if [[ -f "${source_dir}/discover_path" ]]; then + discover_arg='{"path":"'"${workspace}/$(<"${source_dir}/discover_path")"'"}' fi - sed ${SEDOPTS[@]} 's/"manual"//' "${workspace}/BUILD.bazel" pushd "${workspace}" &>/dev/null + echo "Generating rust-project.json..." if [[ -n "${generator_arg}" ]]; then @@ -107,14 +106,26 @@ 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", //...)') 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 } 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)) } } }