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