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