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