From 2f458383be82553129afc94b5a5da6470ca3cbd1 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Thu, 11 Nov 2021 05:00:49 -0800 Subject: [PATCH] Address PR feedback --- .bazelci/presubmit.yml | 5 + .gitignore | 3 + rust/defs.bzl | 4 - rust/private/BUILD.bazel | 6 ++ rust/private/rust_analyzer.bzl | 2 +- test/rust_analyzer/BUILD.bazel | 5 + .../aspect_traversal_test/BUILD.bazel | 14 ++- .../rust_project_json_test.rs | 7 +- .../merging_crates_test/BUILD.bazel | 18 ++-- .../rust_project_json_test.rs | 9 +- .../merging_crates_test_reversed/BUILD.bazel | 41 -------- .../extra_test_dep.rs | 1 - .../merging_crates_test_reversed/lib_dep.rs | 1 - .../merging_crates_test_reversed/mylib.rs | 1 - .../rust_project_json_test.rs | 19 ---- .../rust_analyzer_test_runner.sh | 94 +++++++++++++++++++ tools/BUILD.bazel | 0 tools/rust_analyzer/BUILD.bazel | 8 +- tools/rust_analyzer/aquery.rs | 39 +++++--- tools/rust_analyzer/lib.rs | 14 ++- tools/rust_analyzer/main.rs | 33 +++---- tools/rust_analyzer/rust_project.rs | 63 ++++++++++--- tools/rustfmt/BUILD.bazel | 2 +- tools/rustfmt/rustfmt_utils.bzl | 18 ---- tools/tool_utils.bzl | 15 +++ 25 files changed, 254 insertions(+), 168 deletions(-) create mode 100644 test/rust_analyzer/BUILD.bazel delete mode 100644 test/rust_analyzer/merging_crates_test_reversed/BUILD.bazel delete mode 100644 test/rust_analyzer/merging_crates_test_reversed/extra_test_dep.rs delete mode 100644 test/rust_analyzer/merging_crates_test_reversed/lib_dep.rs delete mode 100644 test/rust_analyzer/merging_crates_test_reversed/mylib.rs delete mode 100644 test/rust_analyzer/merging_crates_test_reversed/rust_project_json_test.rs create mode 100755 test/rust_analyzer/rust_analyzer_test_runner.sh create mode 100644 tools/BUILD.bazel delete mode 100644 tools/rustfmt/rustfmt_utils.bzl create mode 100644 tools/tool_utils.bzl diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 8a8150a276..1bb0a42c57 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -181,6 +181,11 @@ tasks: platform: ubuntu2004 run_targets: - "//test/rustfmt:test_runner" + rust_analyzer_tests: + name: Rust-Analyzer Tests + platform: ubuntu2004 + run_targets: + - "//test/rust_analyzer:rust_analyzer_test" ubuntu2004_examples: name: Examples platform: ubuntu2004 diff --git a/.gitignore b/.gitignore index 2f8bfb1f1d..52814c8a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ # BazelCI bazelci.py + +# rust-analyzer +rust-project.json diff --git a/rust/defs.bzl b/rust/defs.bzl index a4ee8db33d..022d52c8ca 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -39,7 +39,6 @@ load( "//rust/private:rust_analyzer.bzl", _rust_analyzer = "rust_analyzer", _rust_analyzer_aspect = "rust_analyzer_aspect", - _rust_analyzer_detect_sysroot = "rust_analyzer_detect_sysroot", ) load( "//rust/private:rustc.bzl", @@ -108,9 +107,6 @@ rust_common = _rust_common rust_analyzer_aspect = _rust_analyzer_aspect # See @rules_rust//rust/private:rust_analyzer.bzl for a complete description. -rust_analyzer_detect_sysroot = _rust_analyzer_detect_sysroot -# See @rules_rust//rust/private:rust_analyzer.bzl for a complete description. - rust_analyzer = _rust_analyzer # See @rules_rust//rust/private:rust_analyzer.bzl for a complete description. diff --git a/rust/private/BUILD.bazel b/rust/private/BUILD.bazel index e649c91b44..6e3c77ef9e 100644 --- a/rust/private/BUILD.bazel +++ b/rust/private/BUILD.bazel @@ -1,4 +1,5 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//rust/private:rust_analyzer.bzl", "rust_analyzer_detect_sysroot") load("//rust/private:stamp.bzl", "stamp_build_setting") bzl_library( @@ -13,3 +14,8 @@ bzl_library( ) stamp_build_setting(name = "stamp") + +rust_analyzer_detect_sysroot( + name = "rust_analyzer_detect_sysroot", + visibility = ["//visibility:public"], +) diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl index 4903011bb7..290731bc6f 100644 --- a/rust/private/rust_analyzer.bzl +++ b/rust/private/rust_analyzer.bzl @@ -30,8 +30,8 @@ RustAnalyzerInfo = provider( fields = { "build_info": "BuildInfo: build info for this crate if present", "cfgs": "List[String]: features or other compilation --cfg settings", - "crate_specs": "List[File]: transitive closure of OutputGroupInfo files", "crate": "rust_common.crate_info", + "crate_specs": "List[File]: transitive closure of OutputGroupInfo files", "deps": "List[RustAnalyzerInfo]: direct dependencies", "env": "Dict{String: String}: Environment variables, used for the `env!` macro", "proc_macro_dylib_path": "File: compiled shared library output of proc-macro rule", diff --git a/test/rust_analyzer/BUILD.bazel b/test/rust_analyzer/BUILD.bazel new file mode 100644 index 0000000000..3132ee4206 --- /dev/null +++ b/test/rust_analyzer/BUILD.bazel @@ -0,0 +1,5 @@ +sh_binary( + name = "rust_analyzer_test", + srcs = ["rust_analyzer_test_runner.sh"], + args = [package_name()], +) diff --git a/test/rust_analyzer/aspect_traversal_test/BUILD.bazel b/test/rust_analyzer/aspect_traversal_test/BUILD.bazel index cefc646f5c..2a79bbeaba 100644 --- a/test/rust_analyzer/aspect_traversal_test/BUILD.bazel +++ b/test/rust_analyzer/aspect_traversal_test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//rust:defs.bzl", "rust_analyzer", "rust_library", "rust_proc_macro", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_test") rust_library( name = "mylib", @@ -34,16 +34,14 @@ rust_proc_macro( srcs = ["extra_proc_macro_dep.rs"], ) -rust_analyzer( - name = "rust_analyzer", - testonly = True, - targets = [":mylib_test"], -) - rust_test( name = "rust_project_json_test", srcs = ["rust_project_json_test.rs"], data = [":rust-project.json"], edition = "2018", - deps = ["//tools/runfiles"], + env = {"RUST_PROJECT_JSON": "$(rootpath :rust-project.json)"}, + # This target is tagged as manual since it's not expected to pass in + # contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run + # that target to execute this test. + tags = ["manual"], ) diff --git a/test/rust_analyzer/aspect_traversal_test/rust_project_json_test.rs b/test/rust_analyzer/aspect_traversal_test/rust_project_json_test.rs index a297a1449b..b934ac2774 100644 --- a/test/rust_analyzer/aspect_traversal_test/rust_project_json_test.rs +++ b/test/rust_analyzer/aspect_traversal_test/rust_project_json_test.rs @@ -1,12 +1,11 @@ #[cfg(test)] mod tests { - use runfiles::Runfiles; + use std::env; + use std::path::PathBuf; #[test] fn test_aspect_traverses_all_the_right_corners_of_target_graph() { - let r = Runfiles::create().unwrap(); - let rust_project_path = - r.rlocation("rules_rust/test/rust_analyzer/aspect_traversal_test/rust-project.json"); + let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap()); let content = std::fs::read_to_string(&rust_project_path) .unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path)); diff --git a/test/rust_analyzer/merging_crates_test/BUILD.bazel b/test/rust_analyzer/merging_crates_test/BUILD.bazel index a54e1932e0..4a7eb3d37c 100644 --- a/test/rust_analyzer/merging_crates_test/BUILD.bazel +++ b/test/rust_analyzer/merging_crates_test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//rust:defs.bzl", "rust_analyzer", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "mylib", @@ -22,20 +22,14 @@ rust_library( srcs = ["extra_test_dep.rs"], ) -rust_analyzer( - name = "rust_analyzer", - testonly = True, - targets = [ - # it's significant that `mylib` goes before `mylib_test`. - ":mylib", - ":mylib_test", - ], -) - rust_test( name = "rust_project_json_test", srcs = ["rust_project_json_test.rs"], data = [":rust-project.json"], edition = "2018", - deps = ["//tools/runfiles"], + env = {"RUST_PROJECT_JSON": "$(rootpath :rust-project.json)"}, + # This target is tagged as manual since it's not expected to pass in + # contexts outside of `//test/rust_analyzer:rust_analyzer_test`. Run + # that target to execute this test. + tags = ["manual"], ) diff --git a/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs b/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs index 417f72f25b..8f0db407f1 100644 --- a/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs +++ b/test/rust_analyzer/merging_crates_test/rust_project_json_test.rs @@ -1,18 +1,17 @@ #[cfg(test)] mod tests { - use runfiles::Runfiles; + use std::env; + use std::path::PathBuf; #[test] fn test_deps_of_crate_and_its_test_are_merged() { - let r = Runfiles::create().unwrap(); - let rust_project_path = - r.rlocation("rules_rust/test/rust_analyzer/merging_crates_test/rust-project.json"); + let rust_project_path = PathBuf::from(env::var("RUST_PROJECT_JSON").unwrap()); let content = std::fs::read_to_string(&rust_project_path) .unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path)); assert!( - content.contains(r#""root_module":"test/rust_analyzer/merging_crates_test/mylib.rs","deps":[{"crate":0,"name":"lib_dep"},{"crate":2,"name":"extra_test_dep"}]"#), + content.contains(r#""root_module":"mylib.rs","edition":"2018","deps":[{"crate":0,"name":"extra_test_dep"},{"crate":1,"name":"lib_dep"}]"#), "expected rust-project.json to contain both lib_dep and extra_test_dep in deps of mylib.rs."); } } diff --git a/test/rust_analyzer/merging_crates_test_reversed/BUILD.bazel b/test/rust_analyzer/merging_crates_test_reversed/BUILD.bazel deleted file mode 100644 index 8cdd7f567d..0000000000 --- a/test/rust_analyzer/merging_crates_test_reversed/BUILD.bazel +++ /dev/null @@ -1,41 +0,0 @@ -load("//rust:defs.bzl", "rust_analyzer", "rust_library", "rust_test") - -rust_library( - name = "mylib", - srcs = ["mylib.rs"], - deps = [":lib_dep"], -) - -rust_library( - name = "lib_dep", - srcs = ["lib_dep.rs"], -) - -rust_test( - name = "mylib_test", - crate = ":mylib", - deps = [":extra_test_dep"], -) - -rust_library( - name = "extra_test_dep", - srcs = ["extra_test_dep.rs"], -) - -rust_analyzer( - name = "rust_analyzer", - testonly = True, - targets = [ - # it's significant that `mylib_test` goes before `mylib`. - ":mylib_test", - ":mylib", - ], -) - -rust_test( - name = "rust_project_json_test", - srcs = ["rust_project_json_test.rs"], - data = [":rust-project.json"], - edition = "2018", - deps = ["//tools/runfiles"], -) diff --git a/test/rust_analyzer/merging_crates_test_reversed/extra_test_dep.rs b/test/rust_analyzer/merging_crates_test_reversed/extra_test_dep.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/test/rust_analyzer/merging_crates_test_reversed/extra_test_dep.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/rust_analyzer/merging_crates_test_reversed/lib_dep.rs b/test/rust_analyzer/merging_crates_test_reversed/lib_dep.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/test/rust_analyzer/merging_crates_test_reversed/lib_dep.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/rust_analyzer/merging_crates_test_reversed/mylib.rs b/test/rust_analyzer/merging_crates_test_reversed/mylib.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/test/rust_analyzer/merging_crates_test_reversed/mylib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/rust_analyzer/merging_crates_test_reversed/rust_project_json_test.rs b/test/rust_analyzer/merging_crates_test_reversed/rust_project_json_test.rs deleted file mode 100644 index f77c2a6ef5..0000000000 --- a/test/rust_analyzer/merging_crates_test_reversed/rust_project_json_test.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[cfg(test)] -mod tests { - use runfiles::Runfiles; - - #[test] - fn test_deps_of_crate_and_its_test_are_merged() { - let r = Runfiles::create().unwrap(); - let rust_project_path = r.rlocation( - "rules_rust/test/rust_analyzer/merging_crates_test_reversed/rust-project.json", - ); - - let content = std::fs::read_to_string(&rust_project_path) - .unwrap_or_else(|_| panic!("couldn't open {:?}", &rust_project_path)); - - assert!( - content.contains(r#""root_module":"test/rust_analyzer/merging_crates_test_reversed/mylib.rs","deps":[{"crate":0,"name":"lib_dep"},{"crate":1,"name":"extra_test_dep"}]"#), - "expected rust-project.json to contain both lib_dep and extra_test_dep in deps of mylib.rs."); - } -} diff --git a/test/rust_analyzer/rust_analyzer_test_runner.sh b/test/rust_analyzer/rust_analyzer_test_runner.sh new file mode 100755 index 0000000000..6efc843012 --- /dev/null +++ b/test/rust_analyzer/rust_analyzer_test_runner.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Runs Bazel build commands over rustfmt rules, where some are expected +# to fail. +# +# Can be run from anywhere within the rules_rust workspace. + +set -euo pipefail + +if [[ -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then + echo "This script should be run under Bazel" + exit 1 +fi + +PACKAGE_NAME="$1" +if [[ -z "${PACKAGE_NAME:-}" ]]; then + echo "The first argument should be the package name of the test target" + exit 1 +fi + +function generate_workspace() { + local temp_dir="$(mktemp -d -t rules_rust_test_rust_analyzer-XXXXXXXXXX)" + local new_workspace="${temp_dir}/rules_rust_test_rust_analyzer" + + mkdir -p "${new_workspace}" + cat << EOF > "${new_workspace}/WORKSPACE.bazel" +workspace(name = "rules_rust_test_rust_analyzer") +local_repository( + name = "rules_rust", + path = "${BUILD_WORKSPACE_DIRECTORY}", +) +load("@rules_rust//rust:repositories.bzl", "rust_repositories") +rust_repositories(include_rustc_srcs = True) +load("@rules_rust//tools/rust_analyzer:deps.bzl", "rust_analyzer_deps") +rust_analyzer_deps() +EOF + +cat << EOF > "${new_workspace}/.bazelrc" +build --keep_going +test --test_output=errors +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 +build:strict --output_groups=+clippy_checks +EOF + + echo "${new_workspace}" +} + +function rust_analyzer_test() { + local source_dir="$1" + local workspace="$2" + + echo "Testing '$(basename "${source_dir}")'" + rm -f "${workspace}"/*.rs "${workspace}"/*.json "${workspace}/BUILD.bazel" + 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..." + bazel run "@rules_rust//tools/rust_analyzer:gen_rust_project" + echo "Building..." + bazel build //... + echo "Testing..." + bazel test //... + echo "Building with Aspects..." + bazel build //... --config=strict + popd &> /dev/null +} + +function run_test_suite() { + local temp_workspace="$(generate_workspace)" + echo "Generated workspace: ${temp_workspace}" + + for test_dir in "${BUILD_WORKSPACE_DIRECTORY}/${PACKAGE_NAME}"/*; do + # Skip everything but directories + if [[ ! -d "${test_dir}" ]]; then + continue + fi + + rust_analyzer_test "${test_dir}" "${temp_workspace}" + done + + rm -rf "${temp_workspace}" +} + +run_test_suite diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/rust_analyzer/BUILD.bazel b/tools/rust_analyzer/BUILD.bazel index 62759de241..354c97a027 100644 --- a/tools/rust_analyzer/BUILD.bazel +++ b/tools/rust_analyzer/BUILD.bazel @@ -1,11 +1,13 @@ -load("//rust:defs.bzl", "rust_analyzer_detect_sysroot", "rust_binary", "rust_clippy", "rust_library", "rust_test") - -rust_analyzer_detect_sysroot(name = "detect_sysroot") +load("//rust:defs.bzl", "rust_binary", "rust_clippy", "rust_library", "rust_test") +load("//tools:tool_utils.bzl", "aspect_repository") rust_binary( name = "gen_rust_project", srcs = ["main.rs"], edition = "2018", + rustc_env = { + "ASPECT_REPOSITORY": aspect_repository(), + }, visibility = ["//visibility:public"], deps = [ ":gen_rust_project_lib", diff --git a/tools/rust_analyzer/aquery.rs b/tools/rust_analyzer/aquery.rs index 04745d3f74..6633f2cdb9 100644 --- a/tools/rust_analyzer/aquery.rs +++ b/tools/rust_analyzer/aquery.rs @@ -1,11 +1,12 @@ -use serde::Deserialize; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fs::File; use std::option::Option; use std::path::Path; use std::path::PathBuf; use std::process::Command; +use serde::Deserialize; + #[derive(Debug, Deserialize)] struct Output { artifacts: Vec, @@ -63,10 +64,11 @@ pub fn get_crate_specs( workspace: &Path, execution_root: &Path, targets: &[&str], + rules_rust_name: &str, ) -> anyhow::Result> { log::debug!("Get crate specs with targets: {:?}", targets); let target_pattern = targets - .into_iter() + .iter() .map(|t| format!("deps({})", t)) .collect::>() .join("+"); @@ -75,7 +77,10 @@ pub fn get_crate_specs( .current_dir(workspace) .arg("aquery") .arg("--include_aspects") - .arg("--aspects=@rules_rust//rust:defs.bzl%rust_analyzer_aspect") + .arg(format!( + "--aspects={}//rust:defs.bzl%rust_analyzer_aspect", + rules_rust_name + )) .arg("--output_groups=rust_analyzer_crate_spec") .arg(format!( r#"outputs(".*[.]rust_analyzer_crate_spec",{})"#, @@ -89,7 +94,7 @@ pub fn get_crate_specs( // Read all crate specs, deduplicating crates with the same ID. This happens when // a rust_test depends on a rust_library, for example. - let mut crate_specs: HashMap = HashMap::new(); + let mut crate_specs: BTreeMap = BTreeMap::new(); for file in crate_spec_files { let spec: CrateSpec = serde_json::from_reader(File::open(file)?)?; log::debug!("{:?}", spec); @@ -109,17 +114,20 @@ pub fn get_sysroot_src( bazel: &Path, workspace: &Path, execution_root: &Path, - rules_rust: &str, + rules_rust_name: &str, ) -> anyhow::Result { let aquery_output = Command::new(bazel) .current_dir(workspace) .arg("aquery") .arg("--include_aspects") - .arg("--aspects=@rules_rust//rust:defs.bzl%rust_analyzer_aspect") + .arg(format!( + "--aspects={}//rust:defs.bzl%rust_analyzer_aspect", + rules_rust_name + )) .arg("--output_groups=rust_analyzer_sysroot_src") .arg(format!( - r#"outputs(".*[.]rust_analyzer_sysroot_src",{}//tools/rust_analyzer:detect_sysroot)"#, - rules_rust + r#"outputs(".*[.]rust_analyzer_sysroot_src",{}//rust/private:rust_analyzer_detect_sysroot)"#, + rules_rust_name )) .arg("--output=jsonproto") .output()?; @@ -132,22 +140,25 @@ pub fn get_sysroot_src( Ok(std::fs::read_to_string(&sysroot_src_files[0])?) } -fn parse_aquery_output_files(execution_root: &Path, s: String) -> anyhow::Result> { - let o: Output = serde_json::from_str(&s)?; +fn parse_aquery_output_files( + execution_root: &Path, + aquery_stdout: String, +) -> anyhow::Result> { + let out: Output = serde_json::from_str(&aquery_stdout)?; - let artifacts = o + let artifacts = out .artifacts .iter() .map(|a| (a.id, a)) .collect::>(); - let path_fragments = o + let path_fragments = out .path_fragments .iter() .map(|pf| (pf.id, pf)) .collect::>(); let mut output_files: Vec = Vec::new(); - for action in o.actions { + for action in out.actions { for output_id in action.output_ids { let artifact = artifacts .get(&output_id) diff --git a/tools/rust_analyzer/lib.rs b/tools/rust_analyzer/lib.rs index e1bb529cd2..7ef7ad8c51 100644 --- a/tools/rust_analyzer/lib.rs +++ b/tools/rust_analyzer/lib.rs @@ -1,7 +1,8 @@ -use anyhow::anyhow; use std::path::Path; use std::process::Command; +use anyhow::anyhow; + mod aquery; mod rust_project; @@ -22,7 +23,7 @@ pub fn generate_crate_and_sysroot_info( )) .arg("--output_groups=rust_analyzer_crate_spec,rust_analyzer_sysroot_src") .arg(format!( - "{}//tools/rust_analyzer:detect_sysroot", + "{}//rust/private:rust_analyzer_detect_sysroot", rules_rust.as_ref() )) .args(targets) @@ -42,7 +43,7 @@ pub fn generate_crate_and_sysroot_info( pub fn write_rust_project( bazel: impl AsRef, workspace: impl AsRef, - rules_rust: &impl AsRef, + rules_rust_name: &impl AsRef, targets: &[&str], execution_root: impl AsRef, rust_project_path: impl AsRef, @@ -51,14 +52,17 @@ pub fn write_rust_project( bazel.as_ref(), workspace.as_ref(), execution_root.as_ref(), - &targets, + targets, + rules_rust_name.as_ref(), )?; + let sysroot_src = aquery::get_sysroot_src( bazel.as_ref(), workspace.as_ref(), execution_root.as_ref(), - rules_rust.as_ref(), + rules_rust_name.as_ref(), )?; + let rust_project = rust_project::generate_rust_project(&sysroot_src, &crate_specs)?; rust_project::write_rust_project( diff --git a/tools/rust_analyzer/main.rs b/tools/rust_analyzer/main.rs index 1a867888b1..e542b35645 100644 --- a/tools/rust_analyzer/main.rs +++ b/tools/rust_analyzer/main.rs @@ -1,14 +1,16 @@ -use anyhow::anyhow; -use gen_rust_project_lib::generate_crate_and_sysroot_info; -use gen_rust_project_lib::write_rust_project; use std::collections::HashMap; use std::env; use std::path::PathBuf; use std::process::Command; + +use anyhow::anyhow; +use gen_rust_project_lib::generate_crate_and_sysroot_info; +use gen_rust_project_lib::write_rust_project; use structopt::StructOpt; // TODO(david): This shells out to an expected rule in the workspace root //:rust_analyzer that the user must define. -// It would be more convenient if it could automatically discover all the rust code in the workspace if this target does not exist. +// It would be more convenient if it could automatically discover all the rust code in the workspace if this target +// does not exist. fn main() -> anyhow::Result<()> { env_logger::init(); @@ -18,21 +20,24 @@ fn main() -> anyhow::Result<()> { .workspace .as_ref() .expect("failed to find workspace root, set with --workspace"); + let execution_root = config .execution_root .as_ref() .expect("failed to find execution root, is --execution-root set correctly?"); - let targets = config.targets.split(',').collect::>(); + let targets: Vec<&str> = config.targets.split(',').collect(); + + let rules_rust_name = env!("ASPECT_REPOSITORY"); // Generate the crate specs and sysroot src. - generate_crate_and_sysroot_info(&config.bazel, &workspace_root, &config.rules_rust, &targets)?; + generate_crate_and_sysroot_info(&config.bazel, &workspace_root, &rules_rust_name, &targets)?; // Use the generated files to write rust-project.json. write_rust_project( &config.bazel, &workspace_root, - &config.rules_rust, + &rules_rust_name, &targets, &execution_root, &workspace_root.join("rust-project.json"), @@ -106,17 +111,7 @@ struct Config { #[structopt(long, default_value = "bazel")] bazel: PathBuf, - #[structopt( - long, - default_value = "@rules_rust", - help = "The name of the rules_rust repository" - )] - rules_rust: String, - - #[structopt( - long, - default_value = "@//...", - help = "Comma-separated list of target patterns" - )] + /// Comma-separated list of target patterns + #[structopt(long, default_value = "@//...")] targets: String, } diff --git a/tools/rust_analyzer/rust_project.rs b/tools/rust_analyzer/rust_project.rs index 953fb1f566..8d6ca528e4 100644 --- a/tools/rust_analyzer/rust_project.rs +++ b/tools/rust_analyzer/rust_project.rs @@ -1,36 +1,73 @@ -// Library for generating rust_project.json files from a Vec -// See official documentation of file format at https://rust-analyzer.github.io/manual.html +//! Library for generating rust_project.json files from a `Vec` +//! See official documentation of file format at https://rust-analyzer.github.io/manual.html -use crate::aquery::CrateSpec; -use anyhow::anyhow; -use serde::Serialize; use std::collections::HashMap; use std::io::ErrorKind; use std::path::Path; +use anyhow::anyhow; +use serde::Serialize; + +use crate::aquery::CrateSpec; + +/// A `rust-project.json` workspace representation. See +/// [rust-analyzer documentation][rd] for a thorough description of this interface. +/// [rd]: https://rust-analyzer.github.io/manual.html#non-cargo-based-projects #[derive(Serialize)] pub struct RustProject { + /// Path to the directory with *source code* of + /// sysroot crates. sysroot_src: Option, + + /// The set of crates comprising the current + /// project. Must include all transitive + /// dependencies as well as sysroot crate (libstd, + /// libcore and such). crates: Vec, } +/// A `rust-project.json` crate representation. See +/// [rust-analyzer documentation][rd] for a thorough description of this interface. +/// [rd]: https://rust-analyzer.github.io/manual.html#non-cargo-based-projects #[derive(Serialize)] pub struct Crate { + /// A name used in the package's project declaration #[serde(skip_serializing_if = "Option::is_none")] display_name: Option, + + /// Path to the root module of the crate. root_module: String, + + /// Edition of the crate. edition: String, + + /// Dependencies deps: Vec, + + /// Should this crate be treated as a member of current "workspace". #[serde(skip_serializing_if = "Option::is_none")] is_workspace_member: Option, + + /// Optionally specify the (super)set of `.rs` files comprising this crate. #[serde(skip_serializing_if = "Option::is_none")] source: Option, + + /// The set of cfgs activated for a given crate, like + /// `["unix", "feature=\"foo\"", "feature=\"bar\""]`. cfg: Vec, + + /// Target triple for this Crate. #[serde(skip_serializing_if = "Option::is_none")] target: Option, + + /// Environment variables, used for the `env!` macro #[serde(skip_serializing_if = "Option::is_none")] env: Option>, + + /// Whether the crate is a proc-macro crate. is_proc_macro: bool, + + /// For proc-macro crates, path to compiled proc-macro (.so file). #[serde(skip_serializing_if = "Option::is_none")] proc_macro_dylib_path: Option, } @@ -46,12 +83,14 @@ pub struct Dependency { /// Index of a crate in the `crates` array. #[serde(rename = "crate")] crate_index: usize, + + /// The display name of the crate. name: String, } pub fn generate_rust_project( sysroot_src: &str, - crates: &Vec, + crates: &[CrateSpec], ) -> anyhow::Result { let mut project = RustProject { sysroot_src: Some(sysroot_src.into()), @@ -142,7 +181,7 @@ pub fn write_rust_project( ) -> anyhow::Result<()> { let execution_root = execution_root .to_str() - .ok_or(anyhow!("execution_root is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("execution_root is not valid UTF-8"))?; // Try to remove the existing rust-project.json. It's OK if the file doesn't exist. match std::fs::remove_file(rust_project_path) { @@ -156,11 +195,13 @@ pub fn write_rust_project( } } + // Render the `rust-project.json` file and replace the exec root + // placeholders with the path to the local exec root. + let rust_project_content = + serde_json::to_string(rust_project)?.replace("__EXEC_ROOT__", execution_root); + // Write the new rust-project.json file. - std::fs::write( - rust_project_path, - serde_json::to_string(rust_project)?.replace("__EXEC_ROOT__", &execution_root), - )?; + std::fs::write(rust_project_path, rust_project_content)?; Ok(()) } diff --git a/tools/rustfmt/BUILD.bazel b/tools/rustfmt/BUILD.bazel index 135ec025d9..1456eb1cc1 100644 --- a/tools/rustfmt/BUILD.bazel +++ b/tools/rustfmt/BUILD.bazel @@ -1,5 +1,5 @@ load("//rust:defs.bzl", "rust_binary", "rust_clippy", "rust_library") -load(":rustfmt_utils.bzl", "aspect_repository") +load("//tools:tool_utils.bzl", "aspect_repository") package(default_visibility = ["//visibility:public"]) diff --git a/tools/rustfmt/rustfmt_utils.bzl b/tools/rustfmt/rustfmt_utils.bzl deleted file mode 100644 index b1fb391058..0000000000 --- a/tools/rustfmt/rustfmt_utils.bzl +++ /dev/null @@ -1,18 +0,0 @@ -"""A helper module for the `@rules_rust//tools/rustfmt` package""" - -def aspect_repository(): - """Determines the repository name to use for the `rustfmt_manifest` aspect in `rustfmt` binaries. - - The `//tools/rustfmt` target has a hard coded `--aspects` command built into it. - This function is designed to allow for this aspect to be updated to work within - the `rules_rust` repository itself since aspects do not work if the repository name - is explicitly set. - - https://github.com/bazelbuild/rules_rust/issues/749 - - Returns: - str: The string to use for the `rustfmt_aspect` repository - """ - if native.repository_name() == "@": - return "" - return native.repository_name() diff --git a/tools/tool_utils.bzl b/tools/tool_utils.bzl new file mode 100644 index 0000000000..de876b1403 --- /dev/null +++ b/tools/tool_utils.bzl @@ -0,0 +1,15 @@ +"""A helper module for the various targets in the `@rules_rust//tools` package""" + +def aspect_repository(): + """Determines the repository name to use in Bazel commands that use aspects. + + Some tools (`//tools/rustfmt` `//tools/rust_analyzer`) make calls to Bazel + and pass the `--aspects` flag. This macro allows those tools to work around + the following issue: https://github.com/bazelbuild/bazel/issues/11734 + + Returns: + str: The string to use for the `--aspects` repository labels + """ + if native.repository_name() == "@": + return "" + return native.repository_name()