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