]> git.lizzy.rs Git - rust.git/blob - crates/project_model/src/workspace.rs
Merge #10686
[rust.git] / crates / project_model / src / workspace.rs
1 //! Handles lowering of build-system specific workspace information (`cargo
2 //! metadata` or `rust-project.json`) into representation stored in the salsa
3 //! database -- `CrateGraph`.
4
5 use std::{collections::VecDeque, fmt, fs, process::Command};
6
7 use anyhow::{format_err, Context, Result};
8 use base_db::{
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                     krate.version.clone(),
472                     cfg_options.clone(),
473                     cfg_options,
474                     env,
475                     proc_macro.unwrap_or_default(),
476                 ),
477             )
478         })
479         .collect();
480
481     for (from, krate) in project.crates() {
482         if let Some(&from) = crates.get(&from) {
483             if let Some((public_deps, libproc_macro)) = &sysroot_deps {
484                 public_deps.add(from, &mut crate_graph);
485                 if krate.is_proc_macro {
486                     if let Some(proc_macro) = libproc_macro {
487                         add_dep(
488                             &mut crate_graph,
489                             from,
490                             CrateName::new("proc_macro").unwrap(),
491                             *proc_macro,
492                         );
493                     }
494                 }
495             }
496
497             for dep in &krate.deps {
498                 if let Some(&to) = crates.get(&dep.crate_id) {
499                     add_dep(&mut crate_graph, from, dep.name.clone(), to)
500                 }
501             }
502         }
503     }
504     crate_graph
505 }
506
507 fn cargo_to_crate_graph(
508     rustc_cfg: Vec<CfgFlag>,
509     override_cfg: &CfgOverrides,
510     load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
511     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
512     cargo: &CargoWorkspace,
513     build_scripts: &WorkspaceBuildScripts,
514     sysroot: Option<&Sysroot>,
515     rustc: &Option<CargoWorkspace>,
516 ) -> CrateGraph {
517     let _p = profile::span("cargo_to_crate_graph");
518     let mut crate_graph = CrateGraph::default();
519     let (public_deps, libproc_macro) = match sysroot {
520         Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
521         None => (SysrootPublicDeps::default(), None),
522     };
523
524     let mut cfg_options = CfgOptions::default();
525     cfg_options.extend(rustc_cfg);
526
527     let mut pkg_to_lib_crate = FxHashMap::default();
528
529     // Add test cfg for non-sysroot crates
530     cfg_options.insert_atom("test".into());
531     cfg_options.insert_atom("debug_assertions".into());
532
533     let mut pkg_crates = FxHashMap::default();
534     // Does any crate signal to rust-analyzer that they need the rustc_private crates?
535     let mut has_private = false;
536     // Next, create crates for each package, target pair
537     for pkg in cargo.packages() {
538         let mut cfg_options = &cfg_options;
539         let mut replaced_cfg_options;
540
541         let overrides = match override_cfg {
542             CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
543             CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
544         };
545
546         if let Some(overrides) = overrides {
547             // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
548             // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
549             // working on rust-lang/rust as that's the only time it appears outside sysroot).
550             //
551             // A more ideal solution might be to reanalyze crates based on where the cursor is and
552             // figure out the set of cfgs that would have to apply to make it active.
553
554             replaced_cfg_options = cfg_options.clone();
555             replaced_cfg_options.apply_diff(overrides.clone());
556             cfg_options = &replaced_cfg_options;
557         };
558
559         has_private |= cargo[pkg].metadata.rustc_private;
560         let mut lib_tgt = None;
561         for &tgt in cargo[pkg].targets.iter() {
562             if let Some(file_id) = load(&cargo[tgt].root) {
563                 let crate_id = add_target_crate_root(
564                     &mut crate_graph,
565                     &cargo[pkg],
566                     build_scripts.outputs.get(pkg),
567                     cfg_options,
568                     load_proc_macro,
569                     file_id,
570                     &cargo[tgt].name,
571                 );
572                 if cargo[tgt].kind == TargetKind::Lib {
573                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
574                     pkg_to_lib_crate.insert(pkg, crate_id);
575                 }
576                 if let Some(proc_macro) = libproc_macro {
577                     add_dep_with_prelude(
578                         &mut crate_graph,
579                         crate_id,
580                         CrateName::new("proc_macro").unwrap(),
581                         proc_macro,
582                         cargo[tgt].is_proc_macro,
583                     );
584                 }
585
586                 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
587             }
588         }
589
590         // Set deps to the core, std and to the lib target of the current package
591         for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
592             if let Some((to, name)) = lib_tgt.clone() {
593                 if to != *from && *kind != TargetKind::BuildScript {
594                     // (build script can not depend on its library target)
595
596                     // For root projects with dashes in their name,
597                     // cargo metadata does not do any normalization,
598                     // so we do it ourselves currently
599                     let name = CrateName::normalize_dashes(&name);
600                     add_dep(&mut crate_graph, *from, name, to);
601                 }
602             }
603             public_deps.add(*from, &mut crate_graph);
604         }
605     }
606
607     // Now add a dep edge from all targets of upstream to the lib
608     // target of downstream.
609     for pkg in cargo.packages() {
610         for dep in cargo[pkg].dependencies.iter() {
611             let name = CrateName::new(&dep.name).unwrap();
612             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
613                 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
614                     if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
615                         // Only build scripts may depend on build dependencies.
616                         continue;
617                     }
618                     if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
619                         // Build scripts may only depend on build dependencies.
620                         continue;
621                     }
622
623                     add_dep(&mut crate_graph, *from, name.clone(), to)
624                 }
625             }
626         }
627     }
628
629     if has_private {
630         // If the user provided a path to rustc sources, we add all the rustc_private crates
631         // and create dependencies on them for the crates which opt-in to that
632         if let Some(rustc_workspace) = rustc {
633             handle_rustc_crates(
634                 rustc_workspace,
635                 load,
636                 &mut crate_graph,
637                 &cfg_options,
638                 load_proc_macro,
639                 &mut pkg_to_lib_crate,
640                 &public_deps,
641                 cargo,
642                 &pkg_crates,
643             );
644         }
645     }
646     crate_graph
647 }
648
649 fn detached_files_to_crate_graph(
650     rustc_cfg: Vec<CfgFlag>,
651     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
652     detached_files: &[AbsPathBuf],
653     sysroot: &Sysroot,
654 ) -> CrateGraph {
655     let _p = profile::span("detached_files_to_crate_graph");
656     let mut crate_graph = CrateGraph::default();
657     let (public_deps, _libproc_macro) =
658         sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
659
660     let mut cfg_options = CfgOptions::default();
661     cfg_options.extend(rustc_cfg);
662
663     for detached_file in detached_files {
664         let file_id = match load(detached_file) {
665             Some(file_id) => file_id,
666             None => {
667                 tracing::error!("Failed to load detached file {:?}", detached_file);
668                 continue;
669             }
670         };
671         let display_name = detached_file
672             .file_stem()
673             .and_then(|os_str| os_str.to_str())
674             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
675         let detached_file_crate = crate_graph.add_crate_root(
676             file_id,
677             Edition::CURRENT,
678             display_name,
679             None,
680             cfg_options.clone(),
681             cfg_options.clone(),
682             Env::default(),
683             Vec::new(),
684         );
685
686         public_deps.add(detached_file_crate, &mut crate_graph);
687     }
688     crate_graph
689 }
690
691 fn handle_rustc_crates(
692     rustc_workspace: &CargoWorkspace,
693     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
694     crate_graph: &mut CrateGraph,
695     cfg_options: &CfgOptions,
696     load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
697     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
698     public_deps: &SysrootPublicDeps,
699     cargo: &CargoWorkspace,
700     pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
701 ) {
702     let mut rustc_pkg_crates = FxHashMap::default();
703     // The root package of the rustc-dev component is rustc_driver, so we match that
704     let root_pkg =
705         rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
706     // The rustc workspace might be incomplete (such as if rustc-dev is not
707     // installed for the current toolchain) and `rustcSource` is set to discover.
708     if let Some(root_pkg) = root_pkg {
709         // Iterate through every crate in the dependency subtree of rustc_driver using BFS
710         let mut queue = VecDeque::new();
711         queue.push_back(root_pkg);
712         while let Some(pkg) = queue.pop_front() {
713             // Don't duplicate packages if they are dependended on a diamond pattern
714             // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates
715             // which is not ideal
716             if rustc_pkg_crates.contains_key(&pkg) {
717                 continue;
718             }
719             for dep in &rustc_workspace[pkg].dependencies {
720                 queue.push_back(dep.pkg);
721             }
722             for &tgt in rustc_workspace[pkg].targets.iter() {
723                 if rustc_workspace[tgt].kind != TargetKind::Lib {
724                     continue;
725                 }
726                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
727                     let crate_id = add_target_crate_root(
728                         crate_graph,
729                         &rustc_workspace[pkg],
730                         None,
731                         cfg_options,
732                         load_proc_macro,
733                         file_id,
734                         &rustc_workspace[tgt].name,
735                     );
736                     pkg_to_lib_crate.insert(pkg, crate_id);
737                     // Add dependencies on core / std / alloc for this crate
738                     public_deps.add(crate_id, crate_graph);
739                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
740                 }
741             }
742         }
743     }
744     // Now add a dep edge from all targets of upstream to the lib
745     // target of downstream.
746     for pkg in rustc_pkg_crates.keys().copied() {
747         for dep in rustc_workspace[pkg].dependencies.iter() {
748             let name = CrateName::new(&dep.name).unwrap();
749             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
750                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
751                     add_dep(crate_graph, from, name.clone(), to);
752                 }
753             }
754         }
755     }
756     // Add a dependency on the rustc_private crates for all targets of each package
757     // which opts in
758     for dep in rustc_workspace.packages() {
759         let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
760
761         if let Some(&to) = pkg_to_lib_crate.get(&dep) {
762             for pkg in cargo.packages() {
763                 let package = &cargo[pkg];
764                 if !package.metadata.rustc_private {
765                     continue;
766                 }
767                 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
768                     // Avoid creating duplicate dependencies
769                     // This avoids the situation where `from` depends on e.g. `arrayvec`, but
770                     // `rust_analyzer` thinks that it should use the one from the `rustcSource`
771                     // instead of the one from `crates.io`
772                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
773                         add_dep(crate_graph, *from, name.clone(), to);
774                     }
775                 }
776             }
777         }
778     }
779 }
780
781 fn add_target_crate_root(
782     crate_graph: &mut CrateGraph,
783     pkg: &PackageData,
784     build_data: Option<&BuildScriptOutput>,
785     cfg_options: &CfgOptions,
786     load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
787     file_id: FileId,
788     cargo_name: &str,
789 ) -> CrateId {
790     let edition = pkg.edition;
791     let cfg_options = {
792         let mut opts = cfg_options.clone();
793         for feature in pkg.active_features.iter() {
794             opts.insert_key_value("feature".into(), feature.into());
795         }
796         if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
797             opts.extend(cfgs.iter().cloned());
798         }
799         opts
800     };
801
802     let mut env = Env::default();
803     inject_cargo_env(pkg, &mut env);
804
805     if let Some(envs) = build_data.map(|it| &it.envs) {
806         for (k, v) in envs {
807             env.set(k, v.clone());
808         }
809     }
810
811     let proc_macro = build_data
812         .as_ref()
813         .and_then(|it| it.proc_macro_dylib_path.as_ref())
814         .map(|it| load_proc_macro(it))
815         .unwrap_or_default();
816
817     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
818     let mut potential_cfg_options = cfg_options.clone();
819     potential_cfg_options.extend(
820         pkg.features
821             .iter()
822             .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
823     );
824
825     crate_graph.add_crate_root(
826         file_id,
827         edition,
828         Some(display_name),
829         Some(pkg.version.to_string()),
830         cfg_options,
831         potential_cfg_options,
832         env,
833         proc_macro,
834     )
835 }
836
837 #[derive(Default)]
838 struct SysrootPublicDeps {
839     deps: Vec<(CrateName, CrateId, bool)>,
840 }
841
842 impl SysrootPublicDeps {
843     /// Makes `from` depend on the public sysroot crates.
844     fn add(&self, from: CrateId, crate_graph: &mut CrateGraph) {
845         for (name, krate, prelude) in &self.deps {
846             add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
847         }
848     }
849 }
850
851 fn sysroot_to_crate_graph(
852     crate_graph: &mut CrateGraph,
853     sysroot: &Sysroot,
854     rustc_cfg: Vec<CfgFlag>,
855     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
856 ) -> (SysrootPublicDeps, Option<CrateId>) {
857     let _p = profile::span("sysroot_to_crate_graph");
858     let mut cfg_options = CfgOptions::default();
859     cfg_options.extend(rustc_cfg);
860     let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
861         .crates()
862         .filter_map(|krate| {
863             let file_id = load(&sysroot[krate].root)?;
864
865             let env = Env::default();
866             let proc_macro = vec![];
867             let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
868             let crate_id = crate_graph.add_crate_root(
869                 file_id,
870                 Edition::CURRENT,
871                 Some(display_name),
872                 None,
873                 cfg_options.clone(),
874                 cfg_options.clone(),
875                 env,
876                 proc_macro,
877             );
878             Some((krate, crate_id))
879         })
880         .collect();
881
882     for from in sysroot.crates() {
883         for &to in sysroot[from].deps.iter() {
884             let name = CrateName::new(&sysroot[to].name).unwrap();
885             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
886                 add_dep(crate_graph, from, name, to);
887             }
888         }
889     }
890
891     let public_deps = SysrootPublicDeps {
892         deps: sysroot
893             .public_deps()
894             .map(|(name, idx, prelude)| {
895                 (CrateName::new(name).unwrap(), sysroot_crates[&idx], prelude)
896             })
897             .collect::<Vec<_>>(),
898     };
899
900     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
901     (public_deps, libproc_macro)
902 }
903
904 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
905     add_dep_inner(graph, from, Dependency::new(name, to))
906 }
907
908 fn add_dep_with_prelude(
909     graph: &mut CrateGraph,
910     from: CrateId,
911     name: CrateName,
912     to: CrateId,
913     prelude: bool,
914 ) {
915     add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
916 }
917
918 fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
919     if let Err(err) = graph.add_dep(from, dep) {
920         tracing::error!("{}", err)
921     }
922 }
923
924 /// Recreates the compile-time environment variables that Cargo sets.
925 ///
926 /// Should be synced with
927 /// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
928 ///
929 /// FIXME: ask Cargo to provide this data instead of re-deriving.
930 fn inject_cargo_env(package: &PackageData, env: &mut Env) {
931     // FIXME: Missing variables:
932     // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
933
934     let manifest_dir = package.manifest.parent();
935     env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
936
937     // Not always right, but works for common cases.
938     env.set("CARGO", "cargo".into());
939
940     env.set("CARGO_PKG_VERSION", package.version.to_string());
941     env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
942     env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
943     env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
944     env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
945
946     env.set("CARGO_PKG_AUTHORS", String::new());
947
948     env.set("CARGO_PKG_NAME", package.name.clone());
949     // FIXME: This isn't really correct (a package can have many crates with different names), but
950     // it's better than leaving the variable unset.
951     env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
952     env.set("CARGO_PKG_DESCRIPTION", String::new());
953     env.set("CARGO_PKG_HOMEPAGE", String::new());
954     env.set("CARGO_PKG_REPOSITORY", String::new());
955     env.set("CARGO_PKG_LICENSE", String::new());
956
957     env.set("CARGO_PKG_LICENSE_FILE", String::new());
958 }