blob: 98f145beb553283b4c90bf5f9338bc110f37e876 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001use std::collections::{BTreeMap, BTreeSet};
2use std::fs::File;
3use std::option::Option;
4use std::path::Path;
5use std::path::PathBuf;
6use std::process::Command;
7
8use anyhow::Context;
9use serde::Deserialize;
10
11#[derive(Debug, Deserialize)]
12struct AqueryOutput {
13 artifacts: Vec<Artifact>,
14 actions: Vec<Action>,
15 #[serde(rename = "pathFragments")]
16 path_fragments: Vec<PathFragment>,
17}
18
19#[derive(Debug, Deserialize)]
20struct Artifact {
21 id: u32,
22 #[serde(rename = "pathFragmentId")]
23 path_fragment_id: u32,
24}
25
26#[derive(Debug, Deserialize)]
27struct PathFragment {
28 id: u32,
29 label: String,
30 #[serde(rename = "parentId")]
31 parent_id: Option<u32>,
32}
33
34#[derive(Debug, Deserialize)]
35struct Action {
36 #[serde(rename = "outputIds")]
37 output_ids: Vec<u32>,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
41#[serde(deny_unknown_fields)]
42pub struct CrateSpec {
43 pub crate_id: String,
44 pub display_name: String,
45 pub edition: String,
46 pub root_module: String,
47 pub is_workspace_member: bool,
48 pub deps: BTreeSet<String>,
49 pub proc_macro_dylib_path: Option<String>,
50 pub source: Option<CrateSpecSource>,
51 pub cfg: Vec<String>,
52 pub env: BTreeMap<String, String>,
53 pub target: String,
54 pub crate_type: String,
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub struct CrateSpecSource {
60 pub exclude_dirs: Vec<String>,
61 pub include_dirs: Vec<String>,
62}
63
64pub fn get_crate_specs(
65 bazel: &Path,
66 workspace: &Path,
67 execution_root: &Path,
68 targets: &[String],
69 rules_rust_name: &str,
70) -> anyhow::Result<BTreeSet<CrateSpec>> {
71 log::debug!("Get crate specs with targets: {:?}", targets);
72 let target_pattern = targets
73 .iter()
Adam Snaider1c095c92023-07-08 02:09:58 -040074 .map(|t| format!("deps({t})"))
Brian Silvermancc09f182022-03-09 15:40:20 -080075 .collect::<Vec<_>>()
76 .join("+");
77
78 let aquery_output = Command::new(bazel)
79 .current_dir(workspace)
80 .arg("aquery")
81 .arg("--include_aspects")
82 .arg(format!(
Adam Snaider1c095c92023-07-08 02:09:58 -040083 "--aspects={rules_rust_name}//rust:defs.bzl%rust_analyzer_aspect"
Brian Silvermancc09f182022-03-09 15:40:20 -080084 ))
85 .arg("--output_groups=rust_analyzer_crate_spec")
86 .arg(format!(
Adam Snaider1c095c92023-07-08 02:09:58 -040087 r#"outputs(".*[.]rust_analyzer_crate_spec",{target_pattern})"#
Brian Silvermancc09f182022-03-09 15:40:20 -080088 ))
89 .arg("--output=jsonproto")
90 .output()?;
91
92 let crate_spec_files =
93 parse_aquery_output_files(execution_root, &String::from_utf8(aquery_output.stdout)?)?;
94
95 let crate_specs = crate_spec_files
96 .into_iter()
97 .map(|file| {
98 let f = File::open(&file)
99 .with_context(|| format!("Failed to open file: {}", file.display()))?;
100 serde_json::from_reader(f)
101 .with_context(|| format!("Failed to deserialize file: {}", file.display()))
102 })
103 .collect::<anyhow::Result<Vec<CrateSpec>>>()?;
104
105 consolidate_crate_specs(crate_specs)
106}
107
108fn parse_aquery_output_files(
109 execution_root: &Path,
110 aquery_stdout: &str,
111) -> anyhow::Result<Vec<PathBuf>> {
112 let out: AqueryOutput = serde_json::from_str(aquery_stdout)?;
113
114 let artifacts = out
115 .artifacts
116 .iter()
117 .map(|a| (a.id, a))
118 .collect::<BTreeMap<_, _>>();
119 let path_fragments = out
120 .path_fragments
121 .iter()
122 .map(|pf| (pf.id, pf))
123 .collect::<BTreeMap<_, _>>();
124
125 let mut output_files: Vec<PathBuf> = Vec::new();
126 for action in out.actions {
127 for output_id in action.output_ids {
128 let artifact = artifacts
129 .get(&output_id)
130 .expect("internal consistency error in bazel output");
131 let path = path_from_fragments(artifact.path_fragment_id, &path_fragments)?;
132 let path = execution_root.join(path);
133 if path.exists() {
134 output_files.push(path);
135 } else {
136 log::warn!("Skipping missing crate_spec file: {:?}", path);
137 }
138 }
139 }
140
141 Ok(output_files)
142}
143
144fn path_from_fragments(
145 id: u32,
146 fragments: &BTreeMap<u32, &PathFragment>,
147) -> anyhow::Result<PathBuf> {
148 let path_fragment = fragments
149 .get(&id)
150 .expect("internal consistency error in bazel output");
151
152 let buf = match path_fragment.parent_id {
153 Some(parent_id) => path_from_fragments(parent_id, fragments)?
154 .join(PathBuf::from(&path_fragment.label.clone())),
155 None => PathBuf::from(&path_fragment.label.clone()),
156 };
157
158 Ok(buf)
159}
160
161/// Read all crate specs, deduplicating crates with the same ID. This happens when
162/// a rust_test depends on a rust_library, for example.
163fn consolidate_crate_specs(crate_specs: Vec<CrateSpec>) -> anyhow::Result<BTreeSet<CrateSpec>> {
164 let mut consolidated_specs: BTreeMap<String, CrateSpec> = BTreeMap::new();
Brian Silverman5f6f2762022-08-13 19:30:05 -0700165 for mut spec in crate_specs.into_iter() {
Brian Silvermancc09f182022-03-09 15:40:20 -0800166 log::debug!("{:?}", spec);
167 if let Some(existing) = consolidated_specs.get_mut(&spec.crate_id) {
168 existing.deps.extend(spec.deps);
169
Brian Silverman5f6f2762022-08-13 19:30:05 -0700170 spec.cfg.retain(|cfg| !existing.cfg.contains(cfg));
171 existing.cfg.extend(spec.cfg);
172
Brian Silvermancc09f182022-03-09 15:40:20 -0800173 // display_name should match the library's crate name because Rust Analyzer
174 // seems to use display_name for matching crate entries in rust-project.json
175 // against symbols in source files. For more details, see
176 // https://github.com/bazelbuild/rules_rust/issues/1032
177 if spec.crate_type == "rlib" {
178 existing.display_name = spec.display_name;
179 existing.crate_type = "rlib".into();
180 }
181
182 // For proc-macro crates that exist within the workspace, there will be a
183 // generated crate-spec in both the fastbuild and opt-exec configuration.
184 // Prefer proc macro paths with an opt-exec component in the path.
185 if let Some(dylib_path) = spec.proc_macro_dylib_path.as_ref() {
186 const OPT_PATH_COMPONENT: &str = "-opt-exec-";
187 if dylib_path.contains(OPT_PATH_COMPONENT) {
188 existing.proc_macro_dylib_path.replace(dylib_path.clone());
189 }
190 }
191 } else {
192 consolidated_specs.insert(spec.crate_id.clone(), spec);
193 }
194 }
195
196 Ok(consolidated_specs.into_values().collect())
197}
198
199#[cfg(test)]
200mod test {
201 use super::*;
202 use itertools::Itertools;
203
204 #[test]
205 fn consolidate_lib_then_test_specs() {
206 let crate_specs = vec![
207 CrateSpec {
208 crate_id: "ID-mylib.rs".into(),
209 display_name: "mylib".into(),
210 edition: "2018".into(),
211 root_module: "mylib.rs".into(),
212 is_workspace_member: true,
213 deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
214 proc_macro_dylib_path: None,
215 source: None,
216 cfg: vec!["test".into(), "debug_assertions".into()],
217 env: BTreeMap::new(),
218 target: "x86_64-unknown-linux-gnu".into(),
219 crate_type: "rlib".into(),
220 },
221 CrateSpec {
222 crate_id: "ID-extra_test_dep.rs".into(),
223 display_name: "extra_test_dep".into(),
224 edition: "2018".into(),
225 root_module: "extra_test_dep.rs".into(),
226 is_workspace_member: true,
227 deps: BTreeSet::new(),
228 proc_macro_dylib_path: None,
229 source: None,
230 cfg: vec!["test".into(), "debug_assertions".into()],
231 env: BTreeMap::new(),
232 target: "x86_64-unknown-linux-gnu".into(),
233 crate_type: "rlib".into(),
234 },
235 CrateSpec {
236 crate_id: "ID-lib_dep.rs".into(),
237 display_name: "lib_dep".into(),
238 edition: "2018".into(),
239 root_module: "lib_dep.rs".into(),
240 is_workspace_member: true,
241 deps: BTreeSet::new(),
242 proc_macro_dylib_path: None,
243 source: None,
244 cfg: vec!["test".into(), "debug_assertions".into()],
245 env: BTreeMap::new(),
246 target: "x86_64-unknown-linux-gnu".into(),
247 crate_type: "rlib".into(),
248 },
249 CrateSpec {
250 crate_id: "ID-mylib.rs".into(),
251 display_name: "mylib_test".into(),
252 edition: "2018".into(),
253 root_module: "mylib.rs".into(),
254 is_workspace_member: true,
255 deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
256 proc_macro_dylib_path: None,
257 source: None,
258 cfg: vec!["test".into(), "debug_assertions".into()],
259 env: BTreeMap::new(),
260 target: "x86_64-unknown-linux-gnu".into(),
261 crate_type: "bin".into(),
262 },
263 ];
264
265 assert_eq!(
266 consolidate_crate_specs(crate_specs).unwrap(),
267 BTreeSet::from([
268 CrateSpec {
269 crate_id: "ID-mylib.rs".into(),
270 display_name: "mylib".into(),
271 edition: "2018".into(),
272 root_module: "mylib.rs".into(),
273 is_workspace_member: true,
274 deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
275 proc_macro_dylib_path: None,
276 source: None,
277 cfg: vec!["test".into(), "debug_assertions".into()],
278 env: BTreeMap::new(),
279 target: "x86_64-unknown-linux-gnu".into(),
280 crate_type: "rlib".into(),
281 },
282 CrateSpec {
283 crate_id: "ID-extra_test_dep.rs".into(),
284 display_name: "extra_test_dep".into(),
285 edition: "2018".into(),
286 root_module: "extra_test_dep.rs".into(),
287 is_workspace_member: true,
288 deps: BTreeSet::new(),
289 proc_macro_dylib_path: None,
290 source: None,
291 cfg: vec!["test".into(), "debug_assertions".into()],
292 env: BTreeMap::new(),
293 target: "x86_64-unknown-linux-gnu".into(),
294 crate_type: "rlib".into(),
295 },
296 CrateSpec {
297 crate_id: "ID-lib_dep.rs".into(),
298 display_name: "lib_dep".into(),
299 edition: "2018".into(),
300 root_module: "lib_dep.rs".into(),
301 is_workspace_member: true,
302 deps: BTreeSet::new(),
303 proc_macro_dylib_path: None,
304 source: None,
305 cfg: vec!["test".into(), "debug_assertions".into()],
306 env: BTreeMap::new(),
307 target: "x86_64-unknown-linux-gnu".into(),
308 crate_type: "rlib".into(),
309 },
310 ])
311 );
312 }
313
314 #[test]
315 fn consolidate_test_then_lib_specs() {
316 let crate_specs = vec![
317 CrateSpec {
318 crate_id: "ID-mylib.rs".into(),
319 display_name: "mylib_test".into(),
320 edition: "2018".into(),
321 root_module: "mylib.rs".into(),
322 is_workspace_member: true,
323 deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
324 proc_macro_dylib_path: None,
325 source: None,
326 cfg: vec!["test".into(), "debug_assertions".into()],
327 env: BTreeMap::new(),
328 target: "x86_64-unknown-linux-gnu".into(),
329 crate_type: "bin".into(),
330 },
331 CrateSpec {
332 crate_id: "ID-mylib.rs".into(),
333 display_name: "mylib".into(),
334 edition: "2018".into(),
335 root_module: "mylib.rs".into(),
336 is_workspace_member: true,
337 deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
338 proc_macro_dylib_path: None,
339 source: None,
340 cfg: vec!["test".into(), "debug_assertions".into()],
341 env: BTreeMap::new(),
342 target: "x86_64-unknown-linux-gnu".into(),
343 crate_type: "rlib".into(),
344 },
345 CrateSpec {
346 crate_id: "ID-extra_test_dep.rs".into(),
347 display_name: "extra_test_dep".into(),
348 edition: "2018".into(),
349 root_module: "extra_test_dep.rs".into(),
350 is_workspace_member: true,
351 deps: BTreeSet::new(),
352 proc_macro_dylib_path: None,
353 source: None,
354 cfg: vec!["test".into(), "debug_assertions".into()],
355 env: BTreeMap::new(),
356 target: "x86_64-unknown-linux-gnu".into(),
357 crate_type: "rlib".into(),
358 },
359 CrateSpec {
360 crate_id: "ID-lib_dep.rs".into(),
361 display_name: "lib_dep".into(),
362 edition: "2018".into(),
363 root_module: "lib_dep.rs".into(),
364 is_workspace_member: true,
365 deps: BTreeSet::new(),
366 proc_macro_dylib_path: None,
367 source: None,
368 cfg: vec!["test".into(), "debug_assertions".into()],
369 env: BTreeMap::new(),
370 target: "x86_64-unknown-linux-gnu".into(),
371 crate_type: "rlib".into(),
372 },
373 ];
374
375 assert_eq!(
376 consolidate_crate_specs(crate_specs).unwrap(),
377 BTreeSet::from([
378 CrateSpec {
379 crate_id: "ID-mylib.rs".into(),
380 display_name: "mylib".into(),
381 edition: "2018".into(),
382 root_module: "mylib.rs".into(),
383 is_workspace_member: true,
384 deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
385 proc_macro_dylib_path: None,
386 source: None,
387 cfg: vec!["test".into(), "debug_assertions".into()],
388 env: BTreeMap::new(),
389 target: "x86_64-unknown-linux-gnu".into(),
390 crate_type: "rlib".into(),
391 },
392 CrateSpec {
393 crate_id: "ID-extra_test_dep.rs".into(),
394 display_name: "extra_test_dep".into(),
395 edition: "2018".into(),
396 root_module: "extra_test_dep.rs".into(),
397 is_workspace_member: true,
398 deps: BTreeSet::new(),
399 proc_macro_dylib_path: None,
400 source: None,
401 cfg: vec!["test".into(), "debug_assertions".into()],
402 env: BTreeMap::new(),
403 target: "x86_64-unknown-linux-gnu".into(),
404 crate_type: "rlib".into(),
405 },
406 CrateSpec {
407 crate_id: "ID-lib_dep.rs".into(),
408 display_name: "lib_dep".into(),
409 edition: "2018".into(),
410 root_module: "lib_dep.rs".into(),
411 is_workspace_member: true,
412 deps: BTreeSet::new(),
413 proc_macro_dylib_path: None,
414 source: None,
415 cfg: vec!["test".into(), "debug_assertions".into()],
416 env: BTreeMap::new(),
417 target: "x86_64-unknown-linux-gnu".into(),
418 crate_type: "rlib".into(),
419 },
420 ])
421 );
422 }
423
424 #[test]
425 fn consolidate_lib_test_main_specs() {
426 // mylib.rs is a library but has tests and an entry point, and mylib2.rs
427 // depends on mylib.rs. The display_name of the library target mylib.rs
428 // should be "mylib" no matter what order the crate specs is in.
429 // Otherwise Rust Analyzer will not be able to resolve references to
430 // mylib in mylib2.rs.
431 let crate_specs = vec![
432 CrateSpec {
433 crate_id: "ID-mylib.rs".into(),
434 display_name: "mylib".into(),
435 edition: "2018".into(),
436 root_module: "mylib.rs".into(),
437 is_workspace_member: true,
438 deps: BTreeSet::new(),
439 proc_macro_dylib_path: None,
440 source: None,
441 cfg: vec!["test".into(), "debug_assertions".into()],
442 env: BTreeMap::new(),
443 target: "x86_64-unknown-linux-gnu".into(),
444 crate_type: "rlib".into(),
445 },
446 CrateSpec {
447 crate_id: "ID-mylib.rs".into(),
448 display_name: "mylib_test".into(),
449 edition: "2018".into(),
450 root_module: "mylib.rs".into(),
451 is_workspace_member: true,
452 deps: BTreeSet::new(),
453 proc_macro_dylib_path: None,
454 source: None,
455 cfg: vec!["test".into(), "debug_assertions".into()],
456 env: BTreeMap::new(),
457 target: "x86_64-unknown-linux-gnu".into(),
458 crate_type: "bin".into(),
459 },
460 CrateSpec {
461 crate_id: "ID-mylib.rs".into(),
462 display_name: "mylib_main".into(),
463 edition: "2018".into(),
464 root_module: "mylib.rs".into(),
465 is_workspace_member: true,
466 deps: BTreeSet::new(),
467 proc_macro_dylib_path: None,
468 source: None,
469 cfg: vec!["test".into(), "debug_assertions".into()],
470 env: BTreeMap::new(),
471 target: "x86_64-unknown-linux-gnu".into(),
472 crate_type: "bin".into(),
473 },
474 CrateSpec {
475 crate_id: "ID-mylib2.rs".into(),
476 display_name: "mylib2".into(),
477 edition: "2018".into(),
478 root_module: "mylib2.rs".into(),
479 is_workspace_member: true,
480 deps: BTreeSet::from(["ID-mylib.rs".into()]),
481 proc_macro_dylib_path: None,
482 source: None,
483 cfg: vec!["test".into(), "debug_assertions".into()],
484 env: BTreeMap::new(),
485 target: "x86_64-unknown-linux-gnu".into(),
486 crate_type: "rlib".into(),
487 },
488 ];
489
490 for perm in crate_specs.into_iter().permutations(4) {
491 assert_eq!(
492 consolidate_crate_specs(perm).unwrap(),
493 BTreeSet::from([
494 CrateSpec {
495 crate_id: "ID-mylib.rs".into(),
496 display_name: "mylib".into(),
497 edition: "2018".into(),
498 root_module: "mylib.rs".into(),
499 is_workspace_member: true,
500 deps: BTreeSet::from([]),
501 proc_macro_dylib_path: None,
502 source: None,
503 cfg: vec!["test".into(), "debug_assertions".into()],
504 env: BTreeMap::new(),
505 target: "x86_64-unknown-linux-gnu".into(),
506 crate_type: "rlib".into(),
507 },
508 CrateSpec {
509 crate_id: "ID-mylib2.rs".into(),
510 display_name: "mylib2".into(),
511 edition: "2018".into(),
512 root_module: "mylib2.rs".into(),
513 is_workspace_member: true,
514 deps: BTreeSet::from(["ID-mylib.rs".into()]),
515 proc_macro_dylib_path: None,
516 source: None,
517 cfg: vec!["test".into(), "debug_assertions".into()],
518 env: BTreeMap::new(),
519 target: "x86_64-unknown-linux-gnu".into(),
520 crate_type: "rlib".into(),
521 },
522 ])
523 );
524 }
525 }
526
527 #[test]
528 fn consolidate_proc_macro_prefer_exec() {
529 // proc macro crates should prefer the -opt-exec- path which is always generated
530 // during builds where it is used, while the fastbuild version would only be built
531 // when explicitly building that target.
532 let crate_specs = vec![
533 CrateSpec {
534 crate_id: "ID-myproc_macro.rs".into(),
535 display_name: "myproc_macro".into(),
536 edition: "2018".into(),
537 root_module: "myproc_macro.rs".into(),
538 is_workspace_member: true,
539 deps: BTreeSet::new(),
540 proc_macro_dylib_path: Some(
541 "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
542 .into(),
543 ),
544 source: None,
545 cfg: vec!["test".into(), "debug_assertions".into()],
546 env: BTreeMap::new(),
547 target: "x86_64-unknown-linux-gnu".into(),
548 crate_type: "proc_macro".into(),
549 },
550 CrateSpec {
551 crate_id: "ID-myproc_macro.rs".into(),
552 display_name: "myproc_macro".into(),
553 edition: "2018".into(),
554 root_module: "myproc_macro.rs".into(),
555 is_workspace_member: true,
556 deps: BTreeSet::new(),
557 proc_macro_dylib_path: Some(
558 "bazel-out/k8-fastbuild/bin/myproc_macro/libmyproc_macro-12345.so".into(),
559 ),
560 source: None,
561 cfg: vec!["test".into(), "debug_assertions".into()],
562 env: BTreeMap::new(),
563 target: "x86_64-unknown-linux-gnu".into(),
564 crate_type: "proc_macro".into(),
565 },
566 ];
567
568 for perm in crate_specs.into_iter().permutations(2) {
569 assert_eq!(
570 consolidate_crate_specs(perm).unwrap(),
571 BTreeSet::from([CrateSpec {
572 crate_id: "ID-myproc_macro.rs".into(),
573 display_name: "myproc_macro".into(),
574 edition: "2018".into(),
575 root_module: "myproc_macro.rs".into(),
576 is_workspace_member: true,
577 deps: BTreeSet::new(),
578 proc_macro_dylib_path: Some(
579 "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
580 .into()
581 ),
582 source: None,
583 cfg: vec!["test".into(), "debug_assertions".into()],
584 env: BTreeMap::new(),
585 target: "x86_64-unknown-linux-gnu".into(),
586 crate_type: "proc_macro".into(),
587 },])
588 );
589 }
590 }
591}