1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Context;
4use camino::{Utf8Path, Utf8PathBuf};
5
6use crate::{bazel_command, deserialize_file_content};
7
8#[derive(Clone, Debug, serde_derive::Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct CrateSpec {
11 pub aliases: BTreeMap<String, String>,
12 pub crate_id: String,
13 pub display_name: String,
14 pub edition: String,
15 pub root_module: String,
16 pub is_workspace_member: bool,
17 pub deps: BTreeSet<String>,
18 pub proc_macro_dylib_path: Option<String>,
19 pub source: Option<CrateSpecSource>,
20 pub cfg: BTreeSet<String>,
21 pub env: BTreeMap<String, String>,
22 pub target: String,
23 pub crate_type: CrateType,
24 pub is_test: bool,
25 pub build: Option<CrateSpecBuild>,
26 pub info: Option<InfoFile>,
27}
28
29#[derive(Clone, Debug, serde_derive::Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct CrateSpecBuild {
32 pub label: String,
33 pub build_file: String,
34}
35
36#[derive(Clone, Debug, serde_derive::Deserialize)]
37#[serde(deny_unknown_fields)]
38pub struct CrateSpecSource {
39 pub exclude_dirs: Vec<String>,
40 pub include_dirs: Vec<String>,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde_derive::Deserialize)]
44#[serde(rename_all = "kebab-case")]
45pub enum CrateType {
46 Bin,
47 Rlib,
48 Lib,
49 Dylib,
50 Cdylib,
51 Staticlib,
52 ProcMacro,
53}
54
55#[derive(serde_derive::Deserialize)]
56struct CQueryOutput {
57 specs: Vec<Utf8PathBuf>,
58 info: Utf8PathBuf,
59}
60
61#[derive(Clone, Debug, serde_derive::Deserialize)]
62pub struct InfoFile {
63 pub id: String,
64 pub crate_label: Option<String>,
65 pub test_label: Option<String>,
66 pub doc_test_label: Option<String>,
67 pub clippy_label: Option<String>,
68}
69
70#[allow(clippy::too_many_arguments)]
71pub fn get_crate_specs(
72 bazel: &Utf8Path,
73 output_base: &Utf8Path,
74 workspace: &Utf8Path,
75 execution_root: &Utf8Path,
76 bazel_startup_options: &[String],
77 bazel_args: &[String],
78 targets: &[String],
79) -> anyhow::Result<impl IntoIterator<Item = CrateSpec>> {
80 log::info!("running bazel query...");
81 log::debug!("Get crate specs with targets: {targets:?}");
82
83 let query_output = bazel_command(bazel, Some(workspace), Some(output_base))
84 .args(bazel_startup_options)
85 .arg("query")
86 .arg(format!(r#"kind("rust_analyzer_info rule", set({}))"#, targets.join(" ")))
87 .output()
88 .context("bazel query")?;
89
90 if !query_output.status.success() {
91 anyhow::bail!("failed to run bazel query: {}", String::from_utf8_lossy(&query_output.stderr))
92 }
93
94 let stdout = String::from_utf8_lossy(&query_output.stdout);
95 let queried_targets: Vec<_> = stdout.lines().map(|l| l.trim()).filter(|l| !l.is_empty()).collect();
96
97 let mut command = bazel_command(bazel, Some(workspace), Some(output_base));
98 command
99 .args(bazel_startup_options)
100 .arg("cquery")
101 .args(bazel_args)
102 .arg(format!("set({})", queried_targets.join(" ")))
103 .arg("--output=starlark")
104 .arg(r#"--starlark:expr={"specs":[file.path for file in target.output_groups.rust_analyzer_crate_spec.to_list()],"info":target.output_groups.rust_analyzer_info.to_list()[0].path}"#)
105 .arg("--build")
106 .arg("--output_groups=rust_analyzer_info,rust_analyzer_proc_macro_dylib,rust_analyzer_src,rust_analyzer_crate_spec");
107
108 log::trace!("Running cquery: {command:#?}");
109 let cquery_output = command.output().context("Failed to spawn cquery command")?;
110
111 log::info!("bazel cquery finished; parsing spec files...");
112
113 let mut outputs = Vec::new();
114 for line in String::from_utf8_lossy(&cquery_output.stdout)
115 .lines()
116 .map(|l| l.trim())
117 .filter(|l| !l.is_empty())
118 {
119 outputs.push(serde_json::from_str::<CQueryOutput>(line).context("parse line")?);
120 }
121
122 let spec_files: BTreeSet<_> = outputs.iter().flat_map(|out| out.specs.iter()).collect();
123 let info_files = outputs
124 .iter()
125 .map(|out| &out.info)
126 .map(|file| {
127 deserialize_file_content::<InfoFile>(&execution_root.join(file), output_base, workspace, execution_root)
128 .map(|file| (file.id.clone(), file))
129 })
130 .collect::<anyhow::Result<BTreeMap<_, _>>>()
131 .context("info file parse")?;
132
133 let crate_specs = spec_files
134 .into_iter()
135 .map(|file| deserialize_file_content(&execution_root.join(file), output_base, workspace, execution_root))
136 .collect::<anyhow::Result<Vec<_>>>()?;
137
138 consolidate_crate_specs(crate_specs, info_files)
139}
140
141fn consolidate_crate_specs(
144 crate_specs: Vec<CrateSpec>,
145 mut infos: BTreeMap<String, InfoFile>,
146) -> anyhow::Result<impl IntoIterator<Item = CrateSpec>> {
147 let mut consolidated_specs: BTreeMap<String, CrateSpec> = BTreeMap::new();
148 for mut spec in crate_specs.into_iter() {
149 if let Some(existing) = consolidated_specs.get_mut(&spec.crate_id) {
150 existing.deps.extend(spec.deps);
151 existing.env.extend(spec.env);
152 existing.aliases.extend(spec.aliases);
153
154 if let Some(source) = &mut existing.source {
155 if let Some(mut new_source) = spec.source {
156 new_source.exclude_dirs.retain(|src| !source.exclude_dirs.contains(src));
157 new_source.include_dirs.retain(|src| !source.include_dirs.contains(src));
158 source.exclude_dirs.extend(new_source.exclude_dirs);
159 source.include_dirs.extend(new_source.include_dirs);
160 }
161 } else {
162 existing.source = spec.source;
163 }
164
165 existing.cfg.extend(spec.cfg);
166
167 if spec.crate_type == CrateType::Rlib {
172 existing.display_name = spec.display_name;
173 existing.crate_type = CrateType::Rlib;
174 }
175
176 if spec.is_test {
179 existing.is_test = true;
180 if let Some(build) = spec.build {
181 existing.build = Some(build);
182 }
183 }
184
185 if let Some(dylib_path) = spec.proc_macro_dylib_path.as_ref() {
189 const OPT_PATH_COMPONENT: &str = "-opt-exec-";
190 if dylib_path.contains(OPT_PATH_COMPONENT) {
191 existing.proc_macro_dylib_path.replace(dylib_path.clone());
192 }
193 }
194 } else {
195 if let Some(info) = infos.remove(&spec.crate_id) {
196 spec.info = Some(info);
197 }
198 consolidated_specs.insert(spec.crate_id.clone(), spec);
199 }
200 }
201
202 Ok(consolidated_specs.into_values())
203}