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