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