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