Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/tools/rust_analyzer/aquery.rs b/tools/rust_analyzer/aquery.rs
new file mode 100644
index 0000000..12164ab
--- /dev/null
+++ b/tools/rust_analyzer/aquery.rs
@@ -0,0 +1,590 @@
+use std::collections::{BTreeMap, BTreeSet};
+use std::fs::File;
+use std::option::Option;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process::Command;
+
+use anyhow::Context;
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+struct AqueryOutput {
+ artifacts: Vec<Artifact>,
+ actions: Vec<Action>,
+ #[serde(rename = "pathFragments")]
+ path_fragments: Vec<PathFragment>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Artifact {
+ id: u32,
+ #[serde(rename = "pathFragmentId")]
+ path_fragment_id: u32,
+}
+
+#[derive(Debug, Deserialize)]
+struct PathFragment {
+ id: u32,
+ label: String,
+ #[serde(rename = "parentId")]
+ parent_id: Option<u32>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Action {
+ #[serde(rename = "outputIds")]
+ output_ids: Vec<u32>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct CrateSpec {
+ pub crate_id: String,
+ pub display_name: String,
+ pub edition: String,
+ pub root_module: String,
+ pub is_workspace_member: bool,
+ pub deps: BTreeSet<String>,
+ pub proc_macro_dylib_path: Option<String>,
+ pub source: Option<CrateSpecSource>,
+ pub cfg: Vec<String>,
+ pub env: BTreeMap<String, String>,
+ pub target: String,
+ pub crate_type: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct CrateSpecSource {
+ pub exclude_dirs: Vec<String>,
+ pub include_dirs: Vec<String>,
+}
+
+pub fn get_crate_specs(
+ bazel: &Path,
+ workspace: &Path,
+ execution_root: &Path,
+ targets: &[String],
+ rules_rust_name: &str,
+) -> anyhow::Result<BTreeSet<CrateSpec>> {
+ log::debug!("Get crate specs with targets: {:?}", targets);
+ let target_pattern = targets
+ .iter()
+ .map(|t| format!("deps({})", t))
+ .collect::<Vec<_>>()
+ .join("+");
+
+ let aquery_output = Command::new(bazel)
+ .current_dir(workspace)
+ .arg("aquery")
+ .arg("--include_aspects")
+ .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",{})"#,
+ target_pattern
+ ))
+ .arg("--output=jsonproto")
+ .output()?;
+
+ let crate_spec_files =
+ parse_aquery_output_files(execution_root, &String::from_utf8(aquery_output.stdout)?)?;
+
+ let crate_specs = crate_spec_files
+ .into_iter()
+ .map(|file| {
+ let f = File::open(&file)
+ .with_context(|| format!("Failed to open file: {}", file.display()))?;
+ serde_json::from_reader(f)
+ .with_context(|| format!("Failed to deserialize file: {}", file.display()))
+ })
+ .collect::<anyhow::Result<Vec<CrateSpec>>>()?;
+
+ consolidate_crate_specs(crate_specs)
+}
+
+fn parse_aquery_output_files(
+ execution_root: &Path,
+ aquery_stdout: &str,
+) -> anyhow::Result<Vec<PathBuf>> {
+ let out: AqueryOutput = serde_json::from_str(aquery_stdout)?;
+
+ let artifacts = out
+ .artifacts
+ .iter()
+ .map(|a| (a.id, a))
+ .collect::<BTreeMap<_, _>>();
+ let path_fragments = out
+ .path_fragments
+ .iter()
+ .map(|pf| (pf.id, pf))
+ .collect::<BTreeMap<_, _>>();
+
+ let mut output_files: Vec<PathBuf> = Vec::new();
+ for action in out.actions {
+ for output_id in action.output_ids {
+ let artifact = artifacts
+ .get(&output_id)
+ .expect("internal consistency error in bazel output");
+ let path = path_from_fragments(artifact.path_fragment_id, &path_fragments)?;
+ let path = execution_root.join(path);
+ if path.exists() {
+ output_files.push(path);
+ } else {
+ log::warn!("Skipping missing crate_spec file: {:?}", path);
+ }
+ }
+ }
+
+ Ok(output_files)
+}
+
+fn path_from_fragments(
+ id: u32,
+ fragments: &BTreeMap<u32, &PathFragment>,
+) -> anyhow::Result<PathBuf> {
+ let path_fragment = fragments
+ .get(&id)
+ .expect("internal consistency error in bazel output");
+
+ let buf = match path_fragment.parent_id {
+ Some(parent_id) => path_from_fragments(parent_id, fragments)?
+ .join(PathBuf::from(&path_fragment.label.clone())),
+ None => PathBuf::from(&path_fragment.label.clone()),
+ };
+
+ Ok(buf)
+}
+
+/// Read all crate specs, deduplicating crates with the same ID. This happens when
+/// a rust_test depends on a rust_library, for example.
+fn consolidate_crate_specs(crate_specs: Vec<CrateSpec>) -> anyhow::Result<BTreeSet<CrateSpec>> {
+ let mut consolidated_specs: BTreeMap<String, CrateSpec> = BTreeMap::new();
+ for spec in crate_specs.into_iter() {
+ log::debug!("{:?}", spec);
+ if let Some(existing) = consolidated_specs.get_mut(&spec.crate_id) {
+ existing.deps.extend(spec.deps);
+
+ // display_name should match the library's crate name because Rust Analyzer
+ // seems to use display_name for matching crate entries in rust-project.json
+ // against symbols in source files. For more details, see
+ // https://github.com/bazelbuild/rules_rust/issues/1032
+ if spec.crate_type == "rlib" {
+ existing.display_name = spec.display_name;
+ existing.crate_type = "rlib".into();
+ }
+
+ // For proc-macro crates that exist within the workspace, there will be a
+ // generated crate-spec in both the fastbuild and opt-exec configuration.
+ // Prefer proc macro paths with an opt-exec component in the path.
+ if let Some(dylib_path) = spec.proc_macro_dylib_path.as_ref() {
+ const OPT_PATH_COMPONENT: &str = "-opt-exec-";
+ if dylib_path.contains(OPT_PATH_COMPONENT) {
+ existing.proc_macro_dylib_path.replace(dylib_path.clone());
+ }
+ }
+ } else {
+ consolidated_specs.insert(spec.crate_id.clone(), spec);
+ }
+ }
+
+ Ok(consolidated_specs.into_values().collect())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use itertools::Itertools;
+
+ #[test]
+ fn consolidate_lib_then_test_specs() {
+ let crate_specs = vec![
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-extra_test_dep.rs".into(),
+ display_name: "extra_test_dep".into(),
+ edition: "2018".into(),
+ root_module: "extra_test_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-lib_dep.rs".into(),
+ display_name: "lib_dep".into(),
+ edition: "2018".into(),
+ root_module: "lib_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib_test".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "bin".into(),
+ },
+ ];
+
+ assert_eq!(
+ consolidate_crate_specs(crate_specs).unwrap(),
+ BTreeSet::from([
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-extra_test_dep.rs".into(),
+ display_name: "extra_test_dep".into(),
+ edition: "2018".into(),
+ root_module: "extra_test_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-lib_dep.rs".into(),
+ display_name: "lib_dep".into(),
+ edition: "2018".into(),
+ root_module: "lib_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn consolidate_test_then_lib_specs() {
+ let crate_specs = vec![
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib_test".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "bin".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-extra_test_dep.rs".into(),
+ display_name: "extra_test_dep".into(),
+ edition: "2018".into(),
+ root_module: "extra_test_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-lib_dep.rs".into(),
+ display_name: "lib_dep".into(),
+ edition: "2018".into(),
+ root_module: "lib_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ ];
+
+ assert_eq!(
+ consolidate_crate_specs(crate_specs).unwrap(),
+ BTreeSet::from([
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-extra_test_dep.rs".into(),
+ display_name: "extra_test_dep".into(),
+ edition: "2018".into(),
+ root_module: "extra_test_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-lib_dep.rs".into(),
+ display_name: "lib_dep".into(),
+ edition: "2018".into(),
+ root_module: "lib_dep.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn consolidate_lib_test_main_specs() {
+ // mylib.rs is a library but has tests and an entry point, and mylib2.rs
+ // depends on mylib.rs. The display_name of the library target mylib.rs
+ // should be "mylib" no matter what order the crate specs is in.
+ // Otherwise Rust Analyzer will not be able to resolve references to
+ // mylib in mylib2.rs.
+ let crate_specs = vec![
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib_test".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "bin".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib_main".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "bin".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib2.rs".into(),
+ display_name: "mylib2".into(),
+ edition: "2018".into(),
+ root_module: "mylib2.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-mylib.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ ];
+
+ for perm in crate_specs.into_iter().permutations(4) {
+ assert_eq!(
+ consolidate_crate_specs(perm).unwrap(),
+ BTreeSet::from([
+ CrateSpec {
+ crate_id: "ID-mylib.rs".into(),
+ display_name: "mylib".into(),
+ edition: "2018".into(),
+ root_module: "mylib.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from([]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-mylib2.rs".into(),
+ display_name: "mylib2".into(),
+ edition: "2018".into(),
+ root_module: "mylib2.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::from(["ID-mylib.rs".into()]),
+ proc_macro_dylib_path: None,
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "rlib".into(),
+ },
+ ])
+ );
+ }
+ }
+
+ #[test]
+ fn consolidate_proc_macro_prefer_exec() {
+ // proc macro crates should prefer the -opt-exec- path which is always generated
+ // during builds where it is used, while the fastbuild version would only be built
+ // when explicitly building that target.
+ let crate_specs = vec![
+ CrateSpec {
+ crate_id: "ID-myproc_macro.rs".into(),
+ display_name: "myproc_macro".into(),
+ edition: "2018".into(),
+ root_module: "myproc_macro.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: Some(
+ "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
+ .into(),
+ ),
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "proc_macro".into(),
+ },
+ CrateSpec {
+ crate_id: "ID-myproc_macro.rs".into(),
+ display_name: "myproc_macro".into(),
+ edition: "2018".into(),
+ root_module: "myproc_macro.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: Some(
+ "bazel-out/k8-fastbuild/bin/myproc_macro/libmyproc_macro-12345.so".into(),
+ ),
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "proc_macro".into(),
+ },
+ ];
+
+ for perm in crate_specs.into_iter().permutations(2) {
+ assert_eq!(
+ consolidate_crate_specs(perm).unwrap(),
+ BTreeSet::from([CrateSpec {
+ crate_id: "ID-myproc_macro.rs".into(),
+ display_name: "myproc_macro".into(),
+ edition: "2018".into(),
+ root_module: "myproc_macro.rs".into(),
+ is_workspace_member: true,
+ deps: BTreeSet::new(),
+ proc_macro_dylib_path: Some(
+ "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
+ .into()
+ ),
+ source: None,
+ cfg: vec!["test".into(), "debug_assertions".into()],
+ env: BTreeMap::new(),
+ target: "x86_64-unknown-linux-gnu".into(),
+ crate_type: "proc_macro".into(),
+ },])
+ );
+ }
+ }
+}