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