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