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