]> git.lizzy.rs Git - rust.git/blob - crates/project_model/src/workspace.rs
Merge #9963
[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: Option<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("sysroot", &sysroot.is_some())
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                     None
149                 } else {
150                     Some(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.clone())?),
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.into_iter().map(|sysroot| PackageRoot {
308                         is_member: false,
309                         include: vec![sysroot.root().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, |it| it.packages().len());
342                 let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
343                 cargo.packages().len() + sysroot_package_len + rustc_package_len
344             }
345             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
346                 sysroot.crates().len() + files.len()
347             }
348         }
349     }
350
351     pub fn to_crate_graph(
352         &self,
353         proc_macro_client: Option<&ProcMacroClient>,
354         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
355     ) -> CrateGraph {
356         let _p = profile::span("ProjectWorkspace::to_crate_graph");
357         let proc_macro_loader = |path: &AbsPath| match proc_macro_client {
358             Some(client) => client.by_dylib_path(path),
359             None => Vec::new(),
360         };
361
362         let mut crate_graph = match self {
363             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
364                 rustc_cfg.clone(),
365                 &proc_macro_loader,
366                 load,
367                 project,
368                 sysroot,
369             ),
370             ProjectWorkspace::Cargo {
371                 cargo,
372                 sysroot,
373                 rustc,
374                 rustc_cfg,
375                 cfg_overrides,
376                 build_scripts,
377             } => cargo_to_crate_graph(
378                 rustc_cfg.clone(),
379                 cfg_overrides,
380                 &proc_macro_loader,
381                 load,
382                 cargo,
383                 build_scripts,
384                 sysroot.as_ref(),
385                 rustc,
386             ),
387             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
388                 detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
389             }
390         };
391         if crate_graph.patch_cfg_if() {
392             log::debug!("Patched std to depend on cfg-if")
393         } else {
394             log::debug!("Did not patch std to depend on cfg-if")
395         }
396         crate_graph
397     }
398 }
399
400 fn project_json_to_crate_graph(
401     rustc_cfg: Vec<CfgFlag>,
402     proc_macro_loader: &dyn Fn(&AbsPath) -> Vec<ProcMacro>,
403     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
404     project: &ProjectJson,
405     sysroot: &Option<Sysroot>,
406 ) -> CrateGraph {
407     let mut crate_graph = CrateGraph::default();
408     let sysroot_deps = sysroot
409         .as_ref()
410         .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load));
411
412     let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
413     let crates: FxHashMap<CrateId, CrateId> = project
414         .crates()
415         .filter_map(|(crate_id, krate)| {
416             let file_path = &krate.root_module;
417             let file_id = load(file_path)?;
418             Some((crate_id, krate, file_id))
419         })
420         .map(|(crate_id, krate, file_id)| {
421             let env = krate.env.clone().into_iter().collect();
422             let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it));
423
424             let target_cfgs = match krate.target.as_deref() {
425                 Some(target) => {
426                     cfg_cache.entry(target).or_insert_with(|| rustc_cfg::get(None, Some(target)))
427                 }
428                 None => &rustc_cfg,
429             };
430
431             let mut cfg_options = CfgOptions::default();
432             cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
433             (
434                 crate_id,
435                 crate_graph.add_crate_root(
436                     file_id,
437                     krate.edition,
438                     krate.display_name.clone(),
439                     cfg_options.clone(),
440                     cfg_options,
441                     env,
442                     proc_macro.unwrap_or_default(),
443                 ),
444             )
445         })
446         .collect();
447
448     for (from, krate) in project.crates() {
449         if let Some(&from) = crates.get(&from) {
450             if let Some((public_deps, libproc_macro)) = &sysroot_deps {
451                 for (name, to) in public_deps.iter() {
452                     add_dep(&mut crate_graph, from, name.clone(), *to)
453                 }
454                 if krate.is_proc_macro {
455                     if let Some(proc_macro) = libproc_macro {
456                         add_dep(
457                             &mut crate_graph,
458                             from,
459                             CrateName::new("proc_macro").unwrap(),
460                             *proc_macro,
461                         );
462                     }
463                 }
464             }
465
466             for dep in &krate.deps {
467                 if let Some(&to) = crates.get(&dep.crate_id) {
468                     add_dep(&mut crate_graph, from, dep.name.clone(), to)
469                 }
470             }
471         }
472     }
473     crate_graph
474 }
475
476 fn cargo_to_crate_graph(
477     rustc_cfg: Vec<CfgFlag>,
478     override_cfg: &CfgOverrides,
479     proc_macro_loader: &dyn Fn(&AbsPath) -> Vec<ProcMacro>,
480     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
481     cargo: &CargoWorkspace,
482     build_scripts: &WorkspaceBuildScripts,
483     sysroot: Option<&Sysroot>,
484     rustc: &Option<CargoWorkspace>,
485 ) -> CrateGraph {
486     let _p = profile::span("cargo_to_crate_graph");
487     let mut crate_graph = CrateGraph::default();
488     let (public_deps, libproc_macro) = match sysroot {
489         Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
490         None => (Vec::new(), None),
491     };
492
493     let mut cfg_options = CfgOptions::default();
494     cfg_options.extend(rustc_cfg);
495
496     let mut pkg_to_lib_crate = FxHashMap::default();
497
498     // Add test cfg for non-sysroot crates
499     cfg_options.insert_atom("test".into());
500     cfg_options.insert_atom("debug_assertions".into());
501
502     let mut pkg_crates = FxHashMap::default();
503     // Does any crate signal to rust-analyzer that they need the rustc_private crates?
504     let mut has_private = false;
505     // Next, create crates for each package, target pair
506     for pkg in cargo.packages() {
507         let mut cfg_options = &cfg_options;
508         let mut replaced_cfg_options;
509         if let Some(overrides) = override_cfg.get(&cargo[pkg].name) {
510             // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
511             // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
512             // working on rust-lang/rust as that's the only time it appears outside sysroot).
513             //
514             // A more ideal solution might be to reanalyze crates based on where the cursor is and
515             // figure out the set of cfgs that would have to apply to make it active.
516
517             replaced_cfg_options = cfg_options.clone();
518             replaced_cfg_options.apply_diff(overrides.clone());
519             cfg_options = &replaced_cfg_options;
520         };
521
522         has_private |= cargo[pkg].metadata.rustc_private;
523         let mut lib_tgt = None;
524         for &tgt in cargo[pkg].targets.iter() {
525             if let Some(file_id) = load(&cargo[tgt].root) {
526                 let crate_id = add_target_crate_root(
527                     &mut crate_graph,
528                     &cargo[pkg],
529                     build_scripts.outputs.get(pkg),
530                     &cfg_options,
531                     proc_macro_loader,
532                     file_id,
533                     &cargo[tgt].name,
534                 );
535                 if cargo[tgt].kind == TargetKind::Lib {
536                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
537                     pkg_to_lib_crate.insert(pkg, crate_id);
538                 }
539                 if let Some(proc_macro) = libproc_macro {
540                     add_dep(
541                         &mut crate_graph,
542                         crate_id,
543                         CrateName::new("proc_macro").unwrap(),
544                         proc_macro,
545                     );
546                 }
547
548                 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
549             }
550         }
551
552         // Set deps to the core, std and to the lib target of the current package
553         for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
554             if let Some((to, name)) = lib_tgt.clone() {
555                 if to != *from && *kind != TargetKind::BuildScript {
556                     // (build script can not depend on its library target)
557
558                     // For root projects with dashes in their name,
559                     // cargo metadata does not do any normalization,
560                     // so we do it ourselves currently
561                     let name = CrateName::normalize_dashes(&name);
562                     add_dep(&mut crate_graph, *from, name, to);
563                 }
564             }
565             for (name, krate) in public_deps.iter() {
566                 add_dep(&mut crate_graph, *from, name.clone(), *krate);
567             }
568         }
569     }
570
571     // Now add a dep edge from all targets of upstream to the lib
572     // target of downstream.
573     for pkg in cargo.packages() {
574         for dep in cargo[pkg].dependencies.iter() {
575             let name = CrateName::new(&dep.name).unwrap();
576             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
577                 for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
578                     if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
579                         // Only build scripts may depend on build dependencies.
580                         continue;
581                     }
582                     if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
583                         // Build scripts may only depend on build dependencies.
584                         continue;
585                     }
586
587                     add_dep(&mut crate_graph, *from, name.clone(), to)
588                 }
589             }
590         }
591     }
592
593     if has_private {
594         // If the user provided a path to rustc sources, we add all the rustc_private crates
595         // and create dependencies on them for the crates which opt-in to that
596         if let Some(rustc_workspace) = rustc {
597             handle_rustc_crates(
598                 rustc_workspace,
599                 load,
600                 &mut crate_graph,
601                 &cfg_options,
602                 proc_macro_loader,
603                 &mut pkg_to_lib_crate,
604                 &public_deps,
605                 cargo,
606                 &pkg_crates,
607             );
608         }
609     }
610     crate_graph
611 }
612
613 fn detached_files_to_crate_graph(
614     rustc_cfg: Vec<CfgFlag>,
615     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
616     detached_files: &[AbsPathBuf],
617     sysroot: &Sysroot,
618 ) -> CrateGraph {
619     let _p = profile::span("detached_files_to_crate_graph");
620     let mut crate_graph = CrateGraph::default();
621     let (public_deps, _libproc_macro) =
622         sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
623
624     let mut cfg_options = CfgOptions::default();
625     cfg_options.extend(rustc_cfg);
626
627     for detached_file in detached_files {
628         let file_id = match load(detached_file) {
629             Some(file_id) => file_id,
630             None => {
631                 log::error!("Failed to load detached file {:?}", detached_file);
632                 continue;
633             }
634         };
635         let display_name = detached_file
636             .file_stem()
637             .and_then(|os_str| os_str.to_str())
638             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
639         let detached_file_crate = crate_graph.add_crate_root(
640             file_id,
641             Edition::CURRENT,
642             display_name,
643             cfg_options.clone(),
644             cfg_options.clone(),
645             Env::default(),
646             Vec::new(),
647         );
648
649         for (name, krate) in public_deps.iter() {
650             add_dep(&mut crate_graph, detached_file_crate, name.clone(), *krate);
651         }
652     }
653     crate_graph
654 }
655
656 fn handle_rustc_crates(
657     rustc_workspace: &CargoWorkspace,
658     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
659     crate_graph: &mut CrateGraph,
660     cfg_options: &CfgOptions,
661     proc_macro_loader: &dyn Fn(&AbsPath) -> Vec<ProcMacro>,
662     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
663     public_deps: &[(CrateName, CrateId)],
664     cargo: &CargoWorkspace,
665     pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
666 ) {
667     let mut rustc_pkg_crates = FxHashMap::default();
668     // The root package of the rustc-dev component is rustc_driver, so we match that
669     let root_pkg =
670         rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
671     // The rustc workspace might be incomplete (such as if rustc-dev is not
672     // installed for the current toolchain) and `rustcSource` is set to discover.
673     if let Some(root_pkg) = root_pkg {
674         // Iterate through every crate in the dependency subtree of rustc_driver using BFS
675         let mut queue = VecDeque::new();
676         queue.push_back(root_pkg);
677         while let Some(pkg) = queue.pop_front() {
678             // Don't duplicate packages if they are dependended on a diamond pattern
679             // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates
680             // which is not ideal
681             if rustc_pkg_crates.contains_key(&pkg) {
682                 continue;
683             }
684             for dep in &rustc_workspace[pkg].dependencies {
685                 queue.push_back(dep.pkg);
686             }
687             for &tgt in rustc_workspace[pkg].targets.iter() {
688                 if rustc_workspace[tgt].kind != TargetKind::Lib {
689                     continue;
690                 }
691                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
692                     let crate_id = add_target_crate_root(
693                         crate_graph,
694                         &rustc_workspace[pkg],
695                         None,
696                         cfg_options,
697                         proc_macro_loader,
698                         file_id,
699                         &rustc_workspace[tgt].name,
700                     );
701                     pkg_to_lib_crate.insert(pkg, crate_id);
702                     // Add dependencies on core / std / alloc for this crate
703                     for (name, krate) in public_deps.iter() {
704                         add_dep(crate_graph, crate_id, name.clone(), *krate);
705                     }
706                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
707                 }
708             }
709         }
710     }
711     // Now add a dep edge from all targets of upstream to the lib
712     // target of downstream.
713     for pkg in rustc_pkg_crates.keys().copied() {
714         for dep in rustc_workspace[pkg].dependencies.iter() {
715             let name = CrateName::new(&dep.name).unwrap();
716             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
717                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
718                     add_dep(crate_graph, from, name.clone(), to);
719                 }
720             }
721         }
722     }
723     // Add a dependency on the rustc_private crates for all targets of each package
724     // which opts in
725     for dep in rustc_workspace.packages() {
726         let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
727
728         if let Some(&to) = pkg_to_lib_crate.get(&dep) {
729             for pkg in cargo.packages() {
730                 let package = &cargo[pkg];
731                 if !package.metadata.rustc_private {
732                     continue;
733                 }
734                 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
735                     // Avoid creating duplicate dependencies
736                     // This avoids the situation where `from` depends on e.g. `arrayvec`, but
737                     // `rust_analyzer` thinks that it should use the one from the `rustcSource`
738                     // instead of the one from `crates.io`
739                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
740                         add_dep(crate_graph, *from, name.clone(), to);
741                     }
742                 }
743             }
744         }
745     }
746 }
747
748 fn add_target_crate_root(
749     crate_graph: &mut CrateGraph,
750     pkg: &PackageData,
751     build_data: Option<&BuildScriptOutput>,
752     cfg_options: &CfgOptions,
753     proc_macro_loader: &dyn Fn(&AbsPath) -> Vec<ProcMacro>,
754     file_id: FileId,
755     cargo_name: &str,
756 ) -> CrateId {
757     let edition = pkg.edition;
758     let cfg_options = {
759         let mut opts = cfg_options.clone();
760         for feature in pkg.active_features.iter() {
761             opts.insert_key_value("feature".into(), feature.into());
762         }
763         if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
764             opts.extend(cfgs.iter().cloned());
765         }
766         opts
767     };
768
769     let mut env = Env::default();
770     inject_cargo_env(pkg, &mut env);
771
772     if let Some(envs) = build_data.map(|it| &it.envs) {
773         for (k, v) in envs {
774             env.set(k, v.clone());
775         }
776     }
777
778     let proc_macro = build_data
779         .as_ref()
780         .and_then(|it| it.proc_macro_dylib_path.as_ref())
781         .map(|it| proc_macro_loader(it))
782         .unwrap_or_default();
783
784     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
785     let mut potential_cfg_options = cfg_options.clone();
786     potential_cfg_options.extend(
787         pkg.features
788             .iter()
789             .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
790     );
791
792     let crate_id = crate_graph.add_crate_root(
793         file_id,
794         edition,
795         Some(display_name),
796         cfg_options,
797         potential_cfg_options,
798         env,
799         proc_macro,
800     );
801
802     crate_id
803 }
804
805 fn sysroot_to_crate_graph(
806     crate_graph: &mut CrateGraph,
807     sysroot: &Sysroot,
808     rustc_cfg: Vec<CfgFlag>,
809     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
810 ) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
811     let _p = profile::span("sysroot_to_crate_graph");
812     let mut cfg_options = CfgOptions::default();
813     cfg_options.extend(rustc_cfg);
814     let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
815         .crates()
816         .filter_map(|krate| {
817             let file_id = load(&sysroot[krate].root)?;
818
819             let env = Env::default();
820             let proc_macro = vec![];
821             let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
822             let crate_id = crate_graph.add_crate_root(
823                 file_id,
824                 Edition::CURRENT,
825                 Some(display_name),
826                 cfg_options.clone(),
827                 cfg_options.clone(),
828                 env,
829                 proc_macro,
830             );
831             Some((krate, crate_id))
832         })
833         .collect();
834
835     for from in sysroot.crates() {
836         for &to in sysroot[from].deps.iter() {
837             let name = CrateName::new(&sysroot[to].name).unwrap();
838             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
839                 add_dep(crate_graph, from, name, to);
840             }
841         }
842     }
843
844     let public_deps = sysroot
845         .public_deps()
846         .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
847         .collect::<Vec<_>>();
848
849     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
850     (public_deps, libproc_macro)
851 }
852
853 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
854     if let Err(err) = graph.add_dep(from, name, to) {
855         log::error!("{}", err)
856     }
857 }
858
859 /// Recreates the compile-time environment variables that Cargo sets.
860 ///
861 /// Should be synced with
862 /// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
863 ///
864 /// FIXME: ask Cargo to provide this data instead of re-deriving.
865 fn inject_cargo_env(package: &PackageData, env: &mut Env) {
866     // FIXME: Missing variables:
867     // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
868
869     let manifest_dir = package.manifest.parent();
870     env.set("CARGO_MANIFEST_DIR".into(), manifest_dir.as_os_str().to_string_lossy().into_owned());
871
872     // Not always right, but works for common cases.
873     env.set("CARGO".into(), "cargo".into());
874
875     env.set("CARGO_PKG_VERSION".into(), package.version.to_string());
876     env.set("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string());
877     env.set("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string());
878     env.set("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string());
879     env.set("CARGO_PKG_VERSION_PRE".into(), package.version.pre.to_string());
880
881     env.set("CARGO_PKG_AUTHORS".into(), String::new());
882
883     env.set("CARGO_PKG_NAME".into(), package.name.clone());
884     // FIXME: This isn't really correct (a package can have many crates with different names), but
885     // it's better than leaving the variable unset.
886     env.set("CARGO_CRATE_NAME".into(), CrateName::normalize_dashes(&package.name).to_string());
887     env.set("CARGO_PKG_DESCRIPTION".into(), String::new());
888     env.set("CARGO_PKG_HOMEPAGE".into(), String::new());
889     env.set("CARGO_PKG_REPOSITORY".into(), String::new());
890     env.set("CARGO_PKG_LICENSE".into(), String::new());
891
892     env.set("CARGO_PKG_LICENSE_FILE".into(), String::new());
893 }