blob: 7a045999384be0979d1abd8663c79e46a0693d95 [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();
Brian Silverman5f6f2762022-08-13 19:30:05 -0700167 for mut spec in crate_specs.into_iter() {
Brian Silvermancc09f182022-03-09 15:40:20 -0800168 log::debug!("{:?}", spec);
169 if let Some(existing) = consolidated_specs.get_mut(&spec.crate_id) {
170 existing.deps.extend(spec.deps);
171
Brian Silverman5f6f2762022-08-13 19:30:05 -0700172 spec.cfg.retain(|cfg| !existing.cfg.contains(cfg));
173 existing.cfg.extend(spec.cfg);
174
Brian Silvermancc09f182022-03-09 15:40:20 -0800175 // display_name should match the library's crate name because Rust Analyzer
176 // seems to use display_name for matching crate entries in rust-project.json
177 // against symbols in source files. For more details, see
178 // https://github.com/bazelbuild/rules_rust/issues/1032
179 if spec.crate_type == "rlib" {
180 existing.display_name = spec.display_name;
181 existing.crate_type = "rlib".into();
182 }
183
184 // For proc-macro crates that exist within the workspace, there will be a
185 // generated crate-spec in both the fastbuild and opt-exec configuration.
186 // Prefer proc macro paths with an opt-exec component in the path.
187 if let Some(dylib_path) = spec.proc_macro_dylib_path.as_ref() {
188 const OPT_PATH_COMPONENT: &str = "-opt-exec-";
189 if dylib_path.contains(OPT_PATH_COMPONENT) {
190 existing.proc_macro_dylib_path.replace(dylib_path.clone());
191 }
192 }
193 } else {
194 consolidated_specs.insert(spec.crate_id.clone(), spec);
195 }
196 }
197
198 Ok(consolidated_specs.into_values().collect())
199}
200
201#[cfg(test)]
202mod test {
203 use super::*;
204 use itertools::Itertools;
205
206 #[test]
207 fn consolidate_lib_then_test_specs() {
208 let crate_specs = vec![
209 CrateSpec {
210 crate_id: "ID-mylib.rs".into(),
211 display_name: "mylib".into(),
212 edition: "2018".into(),
213 root_module: "mylib.rs".into(),
214 is_workspace_member: true,
215 deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
216 proc_macro_dylib_path: None,
217 source: None,
218 cfg: vec!["test".into(), "debug_assertions".into()],
219 env: BTreeMap::new(),
220 target: "x86_64-unknown-linux-gnu".into(),
221 crate_type: "rlib".into(),
222 },
223 CrateSpec {
224 crate_id: "ID-extra_test_dep.rs".into(),
225 display_name: "extra_test_dep".into(),
226 edition: "2018".into(),
227 root_module: "extra_test_dep.rs".into(),
228 is_workspace_member: true,
229 deps: BTreeSet::new(),
230 proc_macro_dylib_path: None,
231 source: None,
232 cfg: vec!["test".into(), "debug_assertions".into()],
233 env: BTreeMap::new(),
234 target: "x86_64-unknown-linux-gnu".into(),
235 crate_type: "rlib".into(),
236 },
237 CrateSpec {
238 crate_id: "ID-lib_dep.rs".into(),
239 display_name: "lib_dep".into(),
240 edition: "2018".into(),
241 root_module: "lib_dep.rs".into(),
242 is_workspace_member: true,
243 deps: BTreeSet::new(),
244 proc_macro_dylib_path: None,
245 source: None,
246 cfg: vec!["test".into(), "debug_assertions".into()],
247 env: BTreeMap::new(),
248 target: "x86_64-unknown-linux-gnu".into(),
249 crate_type: "rlib".into(),
250 },
251 CrateSpec {
252 crate_id: "ID-mylib.rs".into(),
253 display_name: "mylib_test".into(),
254 edition: "2018".into(),
255 root_module: "mylib.rs".into(),
256 is_workspace_member: true,
257 deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
258 proc_macro_dylib_path: None,
259 source: None,
260 cfg: vec!["test".into(), "debug_assertions".into()],
261 env: BTreeMap::new(),
262 target: "x86_64-unknown-linux-gnu".into(),
263 crate_type: "bin".into(),
264 },
265 ];
266
267 assert_eq!(
268 consolidate_crate_specs(crate_specs).unwrap(),
269 BTreeSet::from([
270 CrateSpec {
271 crate_id: "ID-mylib.rs".into(),
272 display_name: "mylib".into(),
273 edition: "2018".into(),
274 root_module: "mylib.rs".into(),
275 is_workspace_member: true,
276 deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
277 proc_macro_dylib_path: None,
278 source: None,
279 cfg: vec!["test".into(), "debug_assertions".into()],
280 env: BTreeMap::new(),
281 target: "x86_64-unknown-linux-gnu".into(),
282 crate_type: "rlib".into(),
283 },
284 CrateSpec {
285 crate_id: "ID-extra_test_dep.rs".into(),
286 display_name: "extra_test_dep".into(),
287 edition: "2018".into(),
288 root_module: "extra_test_dep.rs".into(),
289 is_workspace_member: true,
290 deps: BTreeSet::new(),
291 proc_macro_dylib_path: None,
292 source: None,
293 cfg: vec!["test".into(), "debug_assertions".into()],
294 env: BTreeMap::new(),
295 target: "x86_64-unknown-linux-gnu".into(),
296 crate_type: "rlib".into(),
297 },
298 CrateSpec {
299 crate_id: "ID-lib_dep.rs".into(),
300 display_name: "lib_dep".into(),
301 edition: "2018".into(),
302 root_module: "lib_dep.rs".into(),
303 is_workspace_member: true,
304 deps: BTreeSet::new(),
305 proc_macro_dylib_path: None,
306 source: None,
307 cfg: vec!["test".into(), "debug_assertions".into()],
308 env: BTreeMap::new(),
309 target: "x86_64-unknown-linux-gnu".into(),
310 crate_type: "rlib".into(),
311 },
312 ])
313 );
314 }
315
316 #[test]
317 fn consolidate_test_then_lib_specs() {
318 let crate_specs = vec![
319 CrateSpec {
320 crate_id: "ID-mylib.rs".into(),
321 display_name: "mylib_test".into(),
322 edition: "2018".into(),
323 root_module: "mylib.rs".into(),
324 is_workspace_member: true,
325 deps: BTreeSet::from(["ID-extra_test_dep.rs".into()]),
326 proc_macro_dylib_path: None,
327 source: None,
328 cfg: vec!["test".into(), "debug_assertions".into()],
329 env: BTreeMap::new(),
330 target: "x86_64-unknown-linux-gnu".into(),
331 crate_type: "bin".into(),
332 },
333 CrateSpec {
334 crate_id: "ID-mylib.rs".into(),
335 display_name: "mylib".into(),
336 edition: "2018".into(),
337 root_module: "mylib.rs".into(),
338 is_workspace_member: true,
339 deps: BTreeSet::from(["ID-lib_dep.rs".into()]),
340 proc_macro_dylib_path: None,
341 source: None,
342 cfg: vec!["test".into(), "debug_assertions".into()],
343 env: BTreeMap::new(),
344 target: "x86_64-unknown-linux-gnu".into(),
345 crate_type: "rlib".into(),
346 },
347 CrateSpec {
348 crate_id: "ID-extra_test_dep.rs".into(),
349 display_name: "extra_test_dep".into(),
350 edition: "2018".into(),
351 root_module: "extra_test_dep.rs".into(),
352 is_workspace_member: true,
353 deps: BTreeSet::new(),
354 proc_macro_dylib_path: None,
355 source: None,
356 cfg: vec!["test".into(), "debug_assertions".into()],
357 env: BTreeMap::new(),
358 target: "x86_64-unknown-linux-gnu".into(),
359 crate_type: "rlib".into(),
360 },
361 CrateSpec {
362 crate_id: "ID-lib_dep.rs".into(),
363 display_name: "lib_dep".into(),
364 edition: "2018".into(),
365 root_module: "lib_dep.rs".into(),
366 is_workspace_member: true,
367 deps: BTreeSet::new(),
368 proc_macro_dylib_path: None,
369 source: None,
370 cfg: vec!["test".into(), "debug_assertions".into()],
371 env: BTreeMap::new(),
372 target: "x86_64-unknown-linux-gnu".into(),
373 crate_type: "rlib".into(),
374 },
375 ];
376
377 assert_eq!(
378 consolidate_crate_specs(crate_specs).unwrap(),
379 BTreeSet::from([
380 CrateSpec {
381 crate_id: "ID-mylib.rs".into(),
382 display_name: "mylib".into(),
383 edition: "2018".into(),
384 root_module: "mylib.rs".into(),
385 is_workspace_member: true,
386 deps: BTreeSet::from(["ID-lib_dep.rs".into(), "ID-extra_test_dep.rs".into()]),
387 proc_macro_dylib_path: None,
388 source: None,
389 cfg: vec!["test".into(), "debug_assertions".into()],
390 env: BTreeMap::new(),
391 target: "x86_64-unknown-linux-gnu".into(),
392 crate_type: "rlib".into(),
393 },
394 CrateSpec {
395 crate_id: "ID-extra_test_dep.rs".into(),
396 display_name: "extra_test_dep".into(),
397 edition: "2018".into(),
398 root_module: "extra_test_dep.rs".into(),
399 is_workspace_member: true,
400 deps: BTreeSet::new(),
401 proc_macro_dylib_path: None,
402 source: None,
403 cfg: vec!["test".into(), "debug_assertions".into()],
404 env: BTreeMap::new(),
405 target: "x86_64-unknown-linux-gnu".into(),
406 crate_type: "rlib".into(),
407 },
408 CrateSpec {
409 crate_id: "ID-lib_dep.rs".into(),
410 display_name: "lib_dep".into(),
411 edition: "2018".into(),
412 root_module: "lib_dep.rs".into(),
413 is_workspace_member: true,
414 deps: BTreeSet::new(),
415 proc_macro_dylib_path: None,
416 source: None,
417 cfg: vec!["test".into(), "debug_assertions".into()],
418 env: BTreeMap::new(),
419 target: "x86_64-unknown-linux-gnu".into(),
420 crate_type: "rlib".into(),
421 },
422 ])
423 );
424 }
425
426 #[test]
427 fn consolidate_lib_test_main_specs() {
428 // mylib.rs is a library but has tests and an entry point, and mylib2.rs
429 // depends on mylib.rs. The display_name of the library target mylib.rs
430 // should be "mylib" no matter what order the crate specs is in.
431 // Otherwise Rust Analyzer will not be able to resolve references to
432 // mylib in mylib2.rs.
433 let crate_specs = vec![
434 CrateSpec {
435 crate_id: "ID-mylib.rs".into(),
436 display_name: "mylib".into(),
437 edition: "2018".into(),
438 root_module: "mylib.rs".into(),
439 is_workspace_member: true,
440 deps: BTreeSet::new(),
441 proc_macro_dylib_path: None,
442 source: None,
443 cfg: vec!["test".into(), "debug_assertions".into()],
444 env: BTreeMap::new(),
445 target: "x86_64-unknown-linux-gnu".into(),
446 crate_type: "rlib".into(),
447 },
448 CrateSpec {
449 crate_id: "ID-mylib.rs".into(),
450 display_name: "mylib_test".into(),
451 edition: "2018".into(),
452 root_module: "mylib.rs".into(),
453 is_workspace_member: true,
454 deps: BTreeSet::new(),
455 proc_macro_dylib_path: None,
456 source: None,
457 cfg: vec!["test".into(), "debug_assertions".into()],
458 env: BTreeMap::new(),
459 target: "x86_64-unknown-linux-gnu".into(),
460 crate_type: "bin".into(),
461 },
462 CrateSpec {
463 crate_id: "ID-mylib.rs".into(),
464 display_name: "mylib_main".into(),
465 edition: "2018".into(),
466 root_module: "mylib.rs".into(),
467 is_workspace_member: true,
468 deps: BTreeSet::new(),
469 proc_macro_dylib_path: None,
470 source: None,
471 cfg: vec!["test".into(), "debug_assertions".into()],
472 env: BTreeMap::new(),
473 target: "x86_64-unknown-linux-gnu".into(),
474 crate_type: "bin".into(),
475 },
476 CrateSpec {
477 crate_id: "ID-mylib2.rs".into(),
478 display_name: "mylib2".into(),
479 edition: "2018".into(),
480 root_module: "mylib2.rs".into(),
481 is_workspace_member: true,
482 deps: BTreeSet::from(["ID-mylib.rs".into()]),
483 proc_macro_dylib_path: None,
484 source: None,
485 cfg: vec!["test".into(), "debug_assertions".into()],
486 env: BTreeMap::new(),
487 target: "x86_64-unknown-linux-gnu".into(),
488 crate_type: "rlib".into(),
489 },
490 ];
491
492 for perm in crate_specs.into_iter().permutations(4) {
493 assert_eq!(
494 consolidate_crate_specs(perm).unwrap(),
495 BTreeSet::from([
496 CrateSpec {
497 crate_id: "ID-mylib.rs".into(),
498 display_name: "mylib".into(),
499 edition: "2018".into(),
500 root_module: "mylib.rs".into(),
501 is_workspace_member: true,
502 deps: BTreeSet::from([]),
503 proc_macro_dylib_path: None,
504 source: None,
505 cfg: vec!["test".into(), "debug_assertions".into()],
506 env: BTreeMap::new(),
507 target: "x86_64-unknown-linux-gnu".into(),
508 crate_type: "rlib".into(),
509 },
510 CrateSpec {
511 crate_id: "ID-mylib2.rs".into(),
512 display_name: "mylib2".into(),
513 edition: "2018".into(),
514 root_module: "mylib2.rs".into(),
515 is_workspace_member: true,
516 deps: BTreeSet::from(["ID-mylib.rs".into()]),
517 proc_macro_dylib_path: None,
518 source: None,
519 cfg: vec!["test".into(), "debug_assertions".into()],
520 env: BTreeMap::new(),
521 target: "x86_64-unknown-linux-gnu".into(),
522 crate_type: "rlib".into(),
523 },
524 ])
525 );
526 }
527 }
528
529 #[test]
530 fn consolidate_proc_macro_prefer_exec() {
531 // proc macro crates should prefer the -opt-exec- path which is always generated
532 // during builds where it is used, while the fastbuild version would only be built
533 // when explicitly building that target.
534 let crate_specs = vec![
535 CrateSpec {
536 crate_id: "ID-myproc_macro.rs".into(),
537 display_name: "myproc_macro".into(),
538 edition: "2018".into(),
539 root_module: "myproc_macro.rs".into(),
540 is_workspace_member: true,
541 deps: BTreeSet::new(),
542 proc_macro_dylib_path: Some(
543 "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
544 .into(),
545 ),
546 source: None,
547 cfg: vec!["test".into(), "debug_assertions".into()],
548 env: BTreeMap::new(),
549 target: "x86_64-unknown-linux-gnu".into(),
550 crate_type: "proc_macro".into(),
551 },
552 CrateSpec {
553 crate_id: "ID-myproc_macro.rs".into(),
554 display_name: "myproc_macro".into(),
555 edition: "2018".into(),
556 root_module: "myproc_macro.rs".into(),
557 is_workspace_member: true,
558 deps: BTreeSet::new(),
559 proc_macro_dylib_path: Some(
560 "bazel-out/k8-fastbuild/bin/myproc_macro/libmyproc_macro-12345.so".into(),
561 ),
562 source: None,
563 cfg: vec!["test".into(), "debug_assertions".into()],
564 env: BTreeMap::new(),
565 target: "x86_64-unknown-linux-gnu".into(),
566 crate_type: "proc_macro".into(),
567 },
568 ];
569
570 for perm in crate_specs.into_iter().permutations(2) {
571 assert_eq!(
572 consolidate_crate_specs(perm).unwrap(),
573 BTreeSet::from([CrateSpec {
574 crate_id: "ID-myproc_macro.rs".into(),
575 display_name: "myproc_macro".into(),
576 edition: "2018".into(),
577 root_module: "myproc_macro.rs".into(),
578 is_workspace_member: true,
579 deps: BTreeSet::new(),
580 proc_macro_dylib_path: Some(
581 "bazel-out/k8-opt-exec-F005BA11/bin/myproc_macro/libmyproc_macro-12345.so"
582 .into()
583 ),
584 source: None,
585 cfg: vec!["test".into(), "debug_assertions".into()],
586 env: BTreeMap::new(),
587 target: "x86_64-unknown-linux-gnu".into(),
588 crate_type: "proc_macro".into(),
589 },])
590 );
591 }
592 }
593}