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