]> git.lizzy.rs Git - rust.git/blob - crates/project_model/src/workspace.rs
Merge #9400
[rust.git] / crates / project_model / src / workspace.rs
1 //! Handles lowering of build-system specific workspace information (`cargo
2 //! metadata` or `rust-project.json`) into representation stored in the salsa
3 //! database -- `CrateGraph`.
4
5 use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
6
7 use anyhow::{format_err, Context, Result};
8 use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
9 use cargo_workspace::DepKind;
10 use cfg::{CfgDiff, CfgOptions};
11 use paths::{AbsPath, AbsPathBuf};
12 use proc_macro_api::ProcMacroClient;
13 use rustc_hash::{FxHashMap, FxHashSet};
14
15 use crate::{
16     build_data::{BuildDataResult, PackageBuildData, WorkspaceBuildData},
17     cargo_workspace,
18     cfg_flag::CfgFlag,
19     rustc_cfg,
20     sysroot::SysrootCrate,
21     utf8_stdout, BuildDataCollector, CargoConfig, CargoWorkspace, ProjectJson, ProjectManifest,
22     Sysroot, TargetKind,
23 };
24
25 pub type CfgOverrides = FxHashMap<String, CfgDiff>;
26
27 /// `PackageRoot` describes a package root folder.
28 /// Which may be an external dependency, or a member of
29 /// the current workspace.
30 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
31 pub struct PackageRoot {
32     /// Is a member of the current workspace
33     pub is_member: bool,
34     pub include: Vec<AbsPathBuf>,
35     pub exclude: Vec<AbsPathBuf>,
36 }
37
38 #[derive(Clone, Eq, PartialEq)]
39 pub enum ProjectWorkspace {
40     /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
41     Cargo {
42         cargo: CargoWorkspace,
43         sysroot: Sysroot,
44         rustc: Option<CargoWorkspace>,
45         /// Holds cfg flags for the current target. We get those by running
46         /// `rustc --print cfg`.
47         ///
48         /// FIXME: make this a per-crate map, as, eg, build.rs might have a
49         /// different target.
50         rustc_cfg: Vec<CfgFlag>,
51         cfg_overrides: CfgOverrides,
52     },
53     /// Project workspace was manually specified using a `rust-project.json` file.
54     Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
55
56     // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
57     // That's not the end user experience we should strive for.
58     // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
59     // That needs some changes on the salsa-level though.
60     // In particular, we should split the unified CrateGraph (which currently has maximal durability) into proper crate graph, and a set of ad hoc roots (with minimal durability).
61     // Then, we need to hide the graph behind the queries such that most queries look only at the proper crate graph, and fall back to ad hoc roots only if there's no results.
62     // After this, we should be able to tweak the logic in reload.rs to add newly opened files, which don't belong to any existing crates, to the set of the detached files.
63     // //
64     /// Project with a set of disjoint files, not belonging to any particular workspace.
65     /// Backed by basic sysroot crates for basic completion and highlighting.
66     DetachedFiles { files: Vec<AbsPathBuf>, sysroot: Sysroot, rustc_cfg: Vec<CfgFlag> },
67 }
68
69 impl fmt::Debug for ProjectWorkspace {
70     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71         // Make sure this isn't too verbose.
72         match self {
73             ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => f
74                 .debug_struct("Cargo")
75                 .field("root", &cargo.workspace_root().file_name())
76                 .field("n_packages", &cargo.packages().len())
77                 .field("n_sysroot_crates", &sysroot.crates().len())
78                 .field(
79                     "n_rustc_compiler_crates",
80                     &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
81                 )
82                 .field("n_rustc_cfg", &rustc_cfg.len())
83                 .field("n_cfg_overrides", &cfg_overrides.len())
84                 .finish(),
85             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
86                 let mut debug_struct = f.debug_struct("Json");
87                 debug_struct.field("n_crates", &project.n_crates());
88                 if let Some(sysroot) = sysroot {
89                     debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
90                 }
91                 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
92                 debug_struct.finish()
93             }
94             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
95                 .debug_struct("DetachedFiles")
96                 .field("n_files", &files.len())
97                 .field("n_sysroot_crates", &sysroot.crates().len())
98                 .field("n_rustc_cfg", &rustc_cfg.len())
99                 .finish(),
100         }
101     }
102 }
103
104 impl ProjectWorkspace {
105     pub fn load(
106         manifest: ProjectManifest,
107         config: &CargoConfig,
108         progress: &dyn Fn(String),
109     ) -> Result<ProjectWorkspace> {
110         let res = match manifest {
111             ProjectManifest::ProjectJson(project_json) => {
112                 let file = fs::read_to_string(&project_json).with_context(|| {
113                     format!("Failed to read json file {}", project_json.display())
114                 })?;
115                 let data = serde_json::from_str(&file).with_context(|| {
116                     format!("Failed to deserialize json file {}", project_json.display())
117                 })?;
118                 let project_location = project_json.parent().unwrap().to_path_buf();
119                 let project_json = ProjectJson::new(&project_location, data);
120                 ProjectWorkspace::load_inline(project_json, config.target.as_deref())?
121             }
122             ProjectManifest::CargoToml(cargo_toml) => {
123                 let cargo_version = utf8_stdout({
124                     let mut cmd = Command::new(toolchain::cargo());
125                     cmd.arg("--version");
126                     cmd
127                 })?;
128
129                 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, config, progress)
130                     .with_context(|| {
131                         format!(
132                             "Failed to read Cargo metadata from Cargo.toml file {}, {}",
133                             cargo_toml.display(),
134                             cargo_version
135                         )
136                     })?;
137
138                 let sysroot = if config.no_sysroot {
139                     Sysroot::default()
140                 } else {
141                     Sysroot::discover(&cargo_toml).with_context(|| {
142                         format!(
143                             "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
144                             cargo_toml.display()
145                         )
146                     })?
147                 };
148
149                 let rustc_dir = if let Some(rustc_source) = &config.rustc_source {
150                     use cargo_workspace::RustcSource;
151                     match rustc_source {
152                         RustcSource::Path(path) => Some(path.clone()),
153                         RustcSource::Discover => Sysroot::discover_rustc(&cargo_toml),
154                     }
155                 } else {
156                     None
157                 };
158
159                 let rustc = if let Some(rustc_dir) = rustc_dir {
160                     Some(
161                         CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress)
162                             .with_context(|| {
163                                 format!("Failed to read Cargo metadata for Rust sources")
164                             })?,
165                     )
166                 } else {
167                     None
168                 };
169
170                 let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
171
172                 let cfg_overrides = config.cfg_overrides();
173                 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides }
174             }
175         };
176
177         Ok(res)
178     }
179
180     pub fn load_inline(
181         project_json: ProjectJson,
182         target: Option<&str>,
183     ) -> Result<ProjectWorkspace> {
184         let sysroot = match &project_json.sysroot_src {
185             Some(path) => Some(Sysroot::load(path)?),
186             None => None,
187         };
188         let rustc_cfg = rustc_cfg::get(None, target);
189         Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
190     }
191
192     pub fn load_detached_files(detached_files: Vec<AbsPathBuf>) -> Result<ProjectWorkspace> {
193         let sysroot = Sysroot::discover(
194             detached_files.first().ok_or_else(|| format_err!("No detached files to load"))?,
195         )?;
196         let rustc_cfg = rustc_cfg::get(None, None);
197         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
198     }
199
200     /// Returns the roots for the current `ProjectWorkspace`
201     /// The return type contains the path and whether or not
202     /// the root is a member of the current workspace
203     pub fn to_roots(&self, build_data: Option<&BuildDataResult>) -> Vec<PackageRoot> {
204         match self {
205             ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
206                 .crates()
207                 .map(|(_, krate)| PackageRoot {
208                     is_member: krate.is_workspace_member,
209                     include: krate.include.clone(),
210                     exclude: krate.exclude.clone(),
211                 })
212                 .collect::<FxHashSet<_>>()
213                 .into_iter()
214                 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
215                     sysroot.crates().map(move |krate| PackageRoot {
216                         is_member: false,
217                         include: vec![sysroot[krate].root_dir().to_path_buf()],
218                         exclude: Vec::new(),
219                     })
220                 }))
221                 .collect::<Vec<_>>(),
222             ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _, cfg_overrides: _ } => {
223                 cargo
224                     .packages()
225                     .map(|pkg| {
226                         let is_member = cargo[pkg].is_member;
227                         let pkg_root = cargo[pkg].root().to_path_buf();
228
229                         let mut include = vec![pkg_root.clone()];
230                         include.extend(
231                             build_data
232                                 .and_then(|it| it.get(cargo.workspace_root()))
233                                 .and_then(|map| map.get(&cargo[pkg].id))
234                                 .and_then(|it| it.out_dir.clone()),
235                         );
236
237                         // In case target's path is manually set in Cargo.toml to be
238                         // outside the package root, add its parent as an extra include.
239                         // An example of this situation would look like this:
240                         //
241                         // ```toml
242                         // [lib]
243                         // path = "../../src/lib.rs"
244                         // ```
245                         let extra_targets = cargo[pkg]
246                             .targets
247                             .iter()
248                             .filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
249                             .filter_map(|&tgt| cargo[tgt].root.parent())
250                             .map(|tgt| tgt.normalize().to_path_buf())
251                             .filter(|path| !path.starts_with(pkg_root.clone()));
252                         include.extend(extra_targets);
253
254                         let mut exclude = vec![pkg_root.join(".git")];
255                         if is_member {
256                             exclude.push(pkg_root.join("target"));
257                         } else {
258                             exclude.push(pkg_root.join("tests"));
259                             exclude.push(pkg_root.join("examples"));
260                             exclude.push(pkg_root.join("benches"));
261                         }
262                         PackageRoot { is_member, include, exclude }
263                     })
264                     .chain(sysroot.crates().map(|krate| PackageRoot {
265                         is_member: false,
266                         include: vec![sysroot[krate].root_dir().to_path_buf()],
267                         exclude: Vec::new(),
268                     }))
269                     .chain(rustc.into_iter().flat_map(|rustc| {
270                         rustc.packages().map(move |krate| PackageRoot {
271                             is_member: false,
272                             include: vec![rustc[krate].root().to_path_buf()],
273                             exclude: Vec::new(),
274                         })
275                     }))
276                     .collect()
277             }
278             ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
279                 .into_iter()
280                 .map(|detached_file| PackageRoot {
281                     is_member: true,
282                     include: vec![detached_file.clone()],
283                     exclude: Vec::new(),
284                 })
285                 .chain(sysroot.crates().map(|krate| PackageRoot {
286                     is_member: false,
287                     include: vec![sysroot[krate].root_dir().to_path_buf()],
288                     exclude: Vec::new(),
289                 }))
290                 .collect(),
291         }
292     }
293
294     pub fn n_packages(&self) -> usize {
295         match self {
296             ProjectWorkspace::Json { project, .. } => project.n_crates(),
297             ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
298                 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
299                 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
300             }
301             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
302                 sysroot.crates().len() + files.len()
303             }
304         }
305     }
306
307     pub fn to_crate_graph(
308         &self,
309         build_data: Option<&BuildDataResult>,
310         proc_macro_client: Option<&ProcMacroClient>,
311         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
312     ) -> CrateGraph {
313         let _p = profile::span("ProjectWorkspace::to_crate_graph");
314         let proc_macro_loader = |path: &Path| match proc_macro_client {
315             Some(client) => client.by_dylib_path(path),
316             None => Vec::new(),
317         };
318
319         let mut crate_graph = match self {
320             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
321                 rustc_cfg.clone(),
322                 &proc_macro_loader,
323                 load,
324                 project,
325                 sysroot,
326             ),
327             ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg, cfg_overrides } => {
328                 cargo_to_crate_graph(
329                     rustc_cfg.clone(),
330                     cfg_overrides,
331                     &proc_macro_loader,
332                     load,
333                     cargo,
334                     build_data.and_then(|it| it.get(cargo.workspace_root())),
335                     sysroot,
336                     rustc,
337                     rustc
338                         .as_ref()
339                         .zip(build_data)
340                         .and_then(|(it, map)| map.get(it.workspace_root())),
341                 )
342             }
343             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
344                 detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
345             }
346         };
347         if crate_graph.patch_cfg_if() {
348             log::debug!("Patched std to depend on cfg-if")
349         } else {
350             log::debug!("Did not patch std to depend on cfg-if")
351         }
352         crate_graph
353     }
354
355     pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) {
356         match self {
357             ProjectWorkspace::Cargo { cargo, .. } => {
358                 collector.add_config(cargo.workspace_root(), cargo.build_data_config().clone());
359             }
360             _ => {}
361         }
362     }
363 }
364
365 fn project_json_to_crate_graph(
366     rustc_cfg: Vec<CfgFlag>,
367     proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
368     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
369     project: &ProjectJson,
370     sysroot: &Option<Sysroot>,
371 ) -> CrateGraph {
372     let mut crate_graph = CrateGraph::default();
373     let sysroot_deps = sysroot
374         .as_ref()
375         .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load));
376
377     let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
378     let crates: FxHashMap<CrateId, CrateId> = project
379         .crates()
380         .filter_map(|(crate_id, krate)| {
381             let file_path = &krate.root_module;
382             let file_id = load(file_path)?;
383             Some((crate_id, krate, file_id))
384         })
385         .map(|(crate_id, krate, file_id)| {
386             let env = krate.env.clone().into_iter().collect();
387             let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it));
388
389             let target_cfgs = match krate.target.as_deref() {
390                 Some(target) => {
391                     cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
392                 }
393                 None => &rustc_cfg,
394             };
395
396             let mut cfg_options = CfgOptions::default();
397             cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
398             (
399                 crate_id,
400                 crate_graph.add_crate_root(
401                     file_id,
402                     krate.edition,
403                     krate.display_name.clone(),
404                     cfg_options.clone(),
405                     cfg_options,
406                     env,
407                     proc_macro.unwrap_or_default(),
408                 ),
409             )
410         })
411         .collect();
412
413     for (from, krate) in project.crates() {
414         if let Some(&from) = crates.get(&from) {
415             if let Some((public_deps, _proc_macro)) = &sysroot_deps {
416                 for (name, to) in public_deps.iter() {
417                     add_dep(&mut crate_graph, from, name.clone(), *to)
418                 }
419             }
420
421             for dep in &krate.deps {
422                 if let Some(&to) = crates.get(&dep.crate_id) {
423                     add_dep(&mut crate_graph, from, dep.name.clone(), to)
424                 }
425             }
426         }
427     }
428     crate_graph
429 }
430
431 fn cargo_to_crate_graph(
432     rustc_cfg: Vec<CfgFlag>,
433     override_cfg: &CfgOverrides,
434     proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
435     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
436     cargo: &CargoWorkspace,
437     build_data_map: Option<&WorkspaceBuildData>,
438     sysroot: &Sysroot,
439     rustc: &Option<CargoWorkspace>,
440     rustc_build_data_map: Option<&WorkspaceBuildData>,
441 ) -> CrateGraph {
442     let _p = profile::span("cargo_to_crate_graph");
443     let mut crate_graph = CrateGraph::default();
444     let (public_deps, libproc_macro) =
445         sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
446
447     let mut cfg_options = CfgOptions::default();
448     cfg_options.extend(rustc_cfg);
449
450     let mut pkg_to_lib_crate = FxHashMap::default();
451
452     // Add test cfg for non-sysroot crates
453     cfg_options.insert_atom("test".into());
454     cfg_options.insert_atom("debug_assertions".into());
455
456     let mut pkg_crates = FxHashMap::default();
457     // Does any crate signal to rust-analyzer that they need the rustc_private crates?
458     let mut has_private = false;
459     // Next, create crates for each package, target pair
460     for pkg in cargo.packages() {
461         let mut cfg_options = &cfg_options;
462         let mut replaced_cfg_options;
463         if let Some(overrides) = override_cfg.get(&cargo[pkg].name) {
464             // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
465             // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
466             // working on rust-lang/rust as that's the only time it appears outside sysroot).
467             //
468             // A more ideal solution might be to reanalyze crates based on where the cursor is and
469             // figure out the set of cfgs that would have to apply to make it active.
470
471             replaced_cfg_options = cfg_options.clone();
472             replaced_cfg_options.apply_diff(overrides.clone());
473             cfg_options = &replaced_cfg_options;
474         };
475
476         has_private |= cargo[pkg].metadata.rustc_private;
477         let mut lib_tgt = None;
478         for &tgt in cargo[pkg].targets.iter() {
479             if let Some(file_id) = load(&cargo[tgt].root) {
480                 let crate_id = add_target_crate_root(
481                     &mut crate_graph,
482                     &cargo[pkg],
483                     build_data_map.and_then(|it| it.get(&cargo[pkg].id)),
484                     &cfg_options,
485                     proc_macro_loader,
486                     file_id,
487                     &cargo[tgt].name,
488                 );
489                 if cargo[tgt].kind == TargetKind::Lib {
490                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
491                     pkg_to_lib_crate.insert(pkg, crate_id);
492                 }
493                 if cargo[tgt].is_proc_macro {
494                     if let Some(proc_macro) = libproc_macro {
495                         add_dep(
496                             &mut crate_graph,
497                             crate_id,
498                             CrateName::new("proc_macro").unwrap(),
499                             proc_macro,
500                         );
501                     }
502                 }
503
504                 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
505             }
506         }
507
508         // Set deps to the core, std and to the lib target of the current package
509         for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
510             if let Some((to, name)) = lib_tgt.clone() {
511                 if to != *from && *kind != TargetKind::BuildScript {
512                     // (build script can not depend on its library target)
513
514                     // For root projects with dashes in their name,
515                     // cargo metadata does not do any normalization,
516                     // so we do it ourselves currently
517                     let name = CrateName::normalize_dashes(&name);
518                     add_dep(&mut crate_graph, *from, name, to);
519                 }
520             }
521             for (name, krate) in public_deps.iter() {
522                 add_dep(&mut crate_graph, *from, name.clone(), *krate);
523             }
524         }
525     }
526
527     // Now add a dep edge from all targets of upstream to the lib
528     // target of downstream.
529     for pkg in cargo.packages() {
530         for dep in cargo[pkg].dependencies.iter() {
531             let name = CrateName::new(&dep.name).unwrap();
532             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
533                 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
534                     if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
535                         // Only build scripts may depend on build dependencies.
536                         continue;
537                     }
538                     if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
539                         // Build scripts may only depend on build dependencies.
540                         continue;
541                     }
542
543                     add_dep(&mut crate_graph, *from, name.clone(), to)
544                 }
545             }
546         }
547     }
548
549     if has_private {
550         // If the user provided a path to rustc sources, we add all the rustc_private crates
551         // and create dependencies on them for the crates which opt-in to that
552         if let Some(rustc_workspace) = rustc {
553             handle_rustc_crates(
554                 rustc_workspace,
555                 load,
556                 &mut crate_graph,
557                 rustc_build_data_map,
558                 &cfg_options,
559                 proc_macro_loader,
560                 &mut pkg_to_lib_crate,
561                 &public_deps,
562                 cargo,
563                 &pkg_crates,
564             );
565         }
566     }
567     crate_graph
568 }
569
570 fn detached_files_to_crate_graph(
571     rustc_cfg: Vec<CfgFlag>,
572     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
573     detached_files: &[AbsPathBuf],
574     sysroot: &Sysroot,
575 ) -> CrateGraph {
576     let _p = profile::span("detached_files_to_crate_graph");
577     let mut crate_graph = CrateGraph::default();
578     let (public_deps, _libproc_macro) =
579         sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
580
581     let mut cfg_options = CfgOptions::default();
582     cfg_options.extend(rustc_cfg);
583
584     for detached_file in detached_files {
585         let file_id = match load(detached_file) {
586             Some(file_id) => file_id,
587             None => {
588                 log::error!("Failed to load detached file {:?}", detached_file);
589                 continue;
590             }
591         };
592         let display_name = detached_file
593             .file_stem()
594             .and_then(|os_str| os_str.to_str())
595             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
596         let detached_file_crate = crate_graph.add_crate_root(
597             file_id,
598             Edition::Edition2018,
599             display_name,
600             cfg_options.clone(),
601             cfg_options.clone(),
602             Env::default(),
603             Vec::new(),
604         );
605
606         for (name, krate) in public_deps.iter() {
607             add_dep(&mut crate_graph, detached_file_crate, name.clone(), *krate);
608         }
609     }
610     crate_graph
611 }
612
613 fn handle_rustc_crates(
614     rustc_workspace: &CargoWorkspace,
615     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
616     crate_graph: &mut CrateGraph,
617     rustc_build_data_map: Option<&WorkspaceBuildData>,
618     cfg_options: &CfgOptions,
619     proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
620     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
621     public_deps: &[(CrateName, CrateId)],
622     cargo: &CargoWorkspace,
623     pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
624 ) {
625     let mut rustc_pkg_crates = FxHashMap::default();
626     // The root package of the rustc-dev component is rustc_driver, so we match that
627     let root_pkg =
628         rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
629     // The rustc workspace might be incomplete (such as if rustc-dev is not
630     // installed for the current toolchain) and `rustcSource` is set to discover.
631     if let Some(root_pkg) = root_pkg {
632         // Iterate through every crate in the dependency subtree of rustc_driver using BFS
633         let mut queue = VecDeque::new();
634         queue.push_back(root_pkg);
635         while let Some(pkg) = queue.pop_front() {
636             // Don't duplicate packages if they are dependended on a diamond pattern
637             // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates
638             // which is not ideal
639             if rustc_pkg_crates.contains_key(&pkg) {
640                 continue;
641             }
642             for dep in &rustc_workspace[pkg].dependencies {
643                 queue.push_back(dep.pkg);
644             }
645             for &tgt in rustc_workspace[pkg].targets.iter() {
646                 if rustc_workspace[tgt].kind != TargetKind::Lib {
647                     continue;
648                 }
649                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
650                     let crate_id = add_target_crate_root(
651                         crate_graph,
652                         &rustc_workspace[pkg],
653                         rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)),
654                         cfg_options,
655                         proc_macro_loader,
656                         file_id,
657                         &rustc_workspace[tgt].name,
658                     );
659                     pkg_to_lib_crate.insert(pkg, crate_id);
660                     // Add dependencies on core / std / alloc for this crate
661                     for (name, krate) in public_deps.iter() {
662                         add_dep(crate_graph, crate_id, name.clone(), *krate);
663                     }
664                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
665                 }
666             }
667         }
668     }
669     // Now add a dep edge from all targets of upstream to the lib
670     // target of downstream.
671     for pkg in rustc_pkg_crates.keys().copied() {
672         for dep in rustc_workspace[pkg].dependencies.iter() {
673             let name = CrateName::new(&dep.name).unwrap();
674             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
675                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
676                     add_dep(crate_graph, from, name.clone(), to);
677                 }
678             }
679         }
680     }
681     // Add a dependency on the rustc_private crates for all targets of each package
682     // which opts in
683     for dep in rustc_workspace.packages() {
684         let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
685
686         if let Some(&to) = pkg_to_lib_crate.get(&dep) {
687             for pkg in cargo.packages() {
688                 let package = &cargo[pkg];
689                 if !package.metadata.rustc_private {
690                     continue;
691                 }
692                 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
693                     // Avoid creating duplicate dependencies
694                     // This avoids the situation where `from` depends on e.g. `arrayvec`, but
695                     // `rust_analyzer` thinks that it should use the one from the `rustcSource`
696                     // instead of the one from `crates.io`
697                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
698                         add_dep(crate_graph, *from, name.clone(), to);
699                     }
700                 }
701             }
702         }
703     }
704 }
705
706 fn add_target_crate_root(
707     crate_graph: &mut CrateGraph,
708     pkg: &cargo_workspace::PackageData,
709     build_data: Option<&PackageBuildData>,
710     cfg_options: &CfgOptions,
711     proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
712     file_id: FileId,
713     cargo_name: &str,
714 ) -> CrateId {
715     let edition = pkg.edition;
716     let cfg_options = {
717         let mut opts = cfg_options.clone();
718         for feature in pkg.active_features.iter() {
719             opts.insert_key_value("feature".into(), feature.into());
720         }
721         if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
722             opts.extend(cfgs.iter().cloned());
723         }
724         opts
725     };
726
727     let mut env = Env::default();
728     if let Some(envs) = build_data.map(|it| &it.envs) {
729         for (k, v) in envs {
730             env.set(k, v.clone());
731         }
732     }
733
734     let proc_macro = build_data
735         .as_ref()
736         .and_then(|it| it.proc_macro_dylib_path.as_ref())
737         .map(|it| proc_macro_loader(it))
738         .unwrap_or_default();
739
740     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
741     let mut potential_cfg_options = cfg_options.clone();
742     potential_cfg_options.extend(
743         pkg.features
744             .iter()
745             .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
746     );
747
748     let crate_id = crate_graph.add_crate_root(
749         file_id,
750         edition,
751         Some(display_name),
752         cfg_options,
753         potential_cfg_options,
754         env,
755         proc_macro,
756     );
757
758     crate_id
759 }
760
761 fn sysroot_to_crate_graph(
762     crate_graph: &mut CrateGraph,
763     sysroot: &Sysroot,
764     rustc_cfg: Vec<CfgFlag>,
765     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
766 ) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
767     let _p = profile::span("sysroot_to_crate_graph");
768     let mut cfg_options = CfgOptions::default();
769     cfg_options.extend(rustc_cfg);
770     let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
771         .crates()
772         .filter_map(|krate| {
773             let file_id = load(&sysroot[krate].root)?;
774
775             let env = Env::default();
776             let proc_macro = vec![];
777             let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
778             let crate_id = crate_graph.add_crate_root(
779                 file_id,
780                 Edition::Edition2018,
781                 Some(display_name),
782                 cfg_options.clone(),
783                 cfg_options.clone(),
784                 env,
785                 proc_macro,
786             );
787             Some((krate, crate_id))
788         })
789         .collect();
790
791     for from in sysroot.crates() {
792         for &to in sysroot[from].deps.iter() {
793             let name = CrateName::new(&sysroot[to].name).unwrap();
794             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
795                 add_dep(crate_graph, from, name, to);
796             }
797         }
798     }
799
800     let public_deps = sysroot
801         .public_deps()
802         .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
803         .collect::<Vec<_>>();
804
805     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
806     (public_deps, libproc_macro)
807 }
808
809 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
810     if let Err(err) = graph.add_dep(from, name, to) {
811         log::error!("{}", err)
812     }
813 }