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