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(),
+                },])
+            );
+        }
+    }
+}