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