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