• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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};
6 
7 use anyhow::{format_err, Context, Result};
8 use base_db::{
9     CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
10     FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult,
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;
17 use triomphe::Arc;
18 
19 use crate::{
20     build_scripts::BuildScriptOutput,
21     cargo_workspace::{DepKind, PackageData, RustLibSource},
22     cfg_flag::CfgFlag,
23     project_json::Crate,
24     rustc_cfg,
25     sysroot::SysrootCrate,
26     target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
27     Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
28 };
29 
30 /// A set of cfg-overrides per crate.
31 #[derive(Default, Debug, Clone, Eq, PartialEq)]
32 pub struct CfgOverrides {
33     /// A global set of overrides matching all crates.
34     pub global: CfgDiff,
35     /// A set of overrides matching specific crates.
36     pub selective: FxHashMap<String, CfgDiff>,
37 }
38 
39 impl CfgOverrides {
len(&self) -> usize40     pub fn len(&self) -> usize {
41         self.global.len() + self.selective.iter().map(|(_, it)| it.len()).sum::<usize>()
42     }
43 }
44 
45 /// `PackageRoot` describes a package root folder.
46 /// Which may be an external dependency, or a member of
47 /// the current workspace.
48 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
49 pub struct PackageRoot {
50     /// Is from the local filesystem and may be edited
51     pub is_local: bool,
52     pub include: Vec<AbsPathBuf>,
53     pub exclude: Vec<AbsPathBuf>,
54 }
55 
56 #[derive(Clone)]
57 pub enum ProjectWorkspace {
58     /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
59     Cargo {
60         cargo: CargoWorkspace,
61         build_scripts: WorkspaceBuildScripts,
62         sysroot: Result<Sysroot, Option<String>>,
63         rustc: Result<(CargoWorkspace, WorkspaceBuildScripts), Option<String>>,
64         /// Holds cfg flags for the current target. We get those by running
65         /// `rustc --print cfg`.
66         ///
67         /// FIXME: make this a per-crate map, as, eg, build.rs might have a
68         /// different target.
69         rustc_cfg: Vec<CfgFlag>,
70         cfg_overrides: CfgOverrides,
71         toolchain: Option<Version>,
72         target_layout: Result<String, String>,
73     },
74     /// Project workspace was manually specified using a `rust-project.json` file.
75     Json {
76         project: ProjectJson,
77         sysroot: Result<Sysroot, Option<String>>,
78         /// Holds cfg flags for the current target. We get those by running
79         /// `rustc --print cfg`.
80         rustc_cfg: Vec<CfgFlag>,
81         toolchain: Option<Version>,
82     },
83     // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
84     // That's not the end user experience we should strive for.
85     // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
86     // That needs some changes on the salsa-level though.
87     // 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).
88     // 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.
89     // 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.
90     // //
91     /// Project with a set of disjoint files, not belonging to any particular workspace.
92     /// Backed by basic sysroot crates for basic completion and highlighting.
93     DetachedFiles {
94         files: Vec<AbsPathBuf>,
95         sysroot: Result<Sysroot, Option<String>>,
96         /// Holds cfg flags for the current target. We get those by running
97         /// `rustc --print cfg`.
98         rustc_cfg: Vec<CfgFlag>,
99     },
100 }
101 
102 impl fmt::Debug for ProjectWorkspace {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result103     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104         // Make sure this isn't too verbose.
105         match self {
106             ProjectWorkspace::Cargo {
107                 cargo,
108                 build_scripts: _,
109                 sysroot,
110                 rustc,
111                 rustc_cfg,
112                 cfg_overrides,
113                 toolchain,
114                 target_layout: data_layout,
115             } => f
116                 .debug_struct("Cargo")
117                 .field("root", &cargo.workspace_root().file_name())
118                 .field("n_packages", &cargo.packages().len())
119                 .field("sysroot", &sysroot.is_ok())
120                 .field(
121                     "n_rustc_compiler_crates",
122                     &rustc.as_ref().map_or(0, |(rc, _)| rc.packages().len()),
123                 )
124                 .field("n_rustc_cfg", &rustc_cfg.len())
125                 .field("n_cfg_overrides", &cfg_overrides.len())
126                 .field("toolchain", &toolchain)
127                 .field("data_layout", &data_layout)
128                 .finish(),
129             ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
130                 let mut debug_struct = f.debug_struct("Json");
131                 debug_struct.field("n_crates", &project.n_crates());
132                 if let Ok(sysroot) = sysroot {
133                     debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
134                 }
135                 debug_struct.field("toolchain", &toolchain);
136                 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
137                 debug_struct.finish()
138             }
139             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
140                 .debug_struct("DetachedFiles")
141                 .field("n_files", &files.len())
142                 .field("sysroot", &sysroot.is_ok())
143                 .field("n_rustc_cfg", &rustc_cfg.len())
144                 .finish(),
145         }
146     }
147 }
148 
149 impl ProjectWorkspace {
load( manifest: ProjectManifest, config: &CargoConfig, progress: &dyn Fn(String), ) -> Result<ProjectWorkspace>150     pub fn load(
151         manifest: ProjectManifest,
152         config: &CargoConfig,
153         progress: &dyn Fn(String),
154     ) -> Result<ProjectWorkspace> {
155         let version = |current_dir, cmd_path, prefix: &str| {
156             let cargo_version = utf8_stdout({
157                 let mut cmd = Command::new(cmd_path);
158                 cmd.envs(&config.extra_env);
159                 cmd.arg("--version").current_dir(current_dir);
160                 cmd
161             })?;
162             anyhow::Ok(
163                 cargo_version
164                     .get(prefix.len()..)
165                     .and_then(|it| Version::parse(it.split_whitespace().next()?).ok()),
166             )
167         };
168         let res = match manifest {
169             ProjectManifest::ProjectJson(project_json) => {
170                 let file = fs::read_to_string(&project_json).with_context(|| {
171                     format!("Failed to read json file {}", project_json.display())
172                 })?;
173                 let data = serde_json::from_str(&file).with_context(|| {
174                     format!("Failed to deserialize json file {}", project_json.display())
175                 })?;
176                 let project_location = project_json.parent().to_path_buf();
177                 let toolchain = version(&*project_location, toolchain::rustc(), "rustc ")?;
178                 let project_json = ProjectJson::new(&project_location, data);
179                 ProjectWorkspace::load_inline(
180                     project_json,
181                     config.target.as_deref(),
182                     &config.extra_env,
183                     toolchain,
184                 )
185             }
186             ProjectManifest::CargoToml(cargo_toml) => {
187                 let toolchain = version(cargo_toml.parent(), toolchain::cargo(), "cargo ")?;
188                 let meta = CargoWorkspace::fetch_metadata(
189                     &cargo_toml,
190                     cargo_toml.parent(),
191                     config,
192                     progress,
193                 )
194                 .with_context(|| {
195                     format!(
196                         "Failed to read Cargo metadata from Cargo.toml file {}, {:?}",
197                         cargo_toml.display(),
198                         toolchain
199                     )
200                 })?;
201                 let cargo = CargoWorkspace::new(meta);
202 
203                 let sysroot = match (&config.sysroot, &config.sysroot_src) {
204                     (Some(RustLibSource::Path(path)), None) => {
205                         Sysroot::with_sysroot_dir(path.clone()).map_err(|e| {
206                           Some(format!("Failed to find sysroot at {}:{e}", path.display()))
207                         })
208                     }
209                     (Some(RustLibSource::Discover), None) => {
210                         Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| {
211                             Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display()))
212                         })
213                     }
214                     (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
215                         Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone()))
216                     }
217                     (Some(RustLibSource::Discover), Some(sysroot_src)) => {
218                         Sysroot::discover_with_src_override(
219                             cargo_toml.parent(),
220                             &config.extra_env,
221                             sysroot_src.clone(),
222                         ).map_err(|e| {
223                             Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display()))
224                         })
225                     }
226                     (None, _) => Err(None),
227                 };
228 
229                 if let Ok(sysroot) = &sysroot {
230                     tracing::info!(workspace = %cargo_toml.display(), src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
231                 }
232 
233                 let rustc_dir = match &config.rustc_source {
234                     Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
235                         .map_err(|p| {
236                             Some(format!("rustc source path is not absolute: {}", p.display()))
237                         }),
238                     Some(RustLibSource::Discover) => {
239                         sysroot.as_ref().ok().and_then(Sysroot::discover_rustc).ok_or_else(|| {
240                             Some(format!("Failed to discover rustc source for sysroot."))
241                         })
242                     }
243                     None => Err(None),
244                 };
245 
246                 let rustc =  rustc_dir.and_then(|rustc_dir| {
247                     tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source");
248                     match CargoWorkspace::fetch_metadata(
249                         &rustc_dir,
250                         cargo_toml.parent(),
251                         &CargoConfig {
252                             features: crate::CargoFeatures::default(),
253                             ..config.clone()
254                         },
255                         progress,
256                     ) {
257                         Ok(meta) => {
258                             let workspace = CargoWorkspace::new(meta);
259                             let buildscripts = WorkspaceBuildScripts::rustc_crates(
260                                 &workspace,
261                                 cargo_toml.parent(),
262                                 &config.extra_env,
263                             );
264                             Ok((workspace, buildscripts))
265                         }
266                         Err(e) => {
267                             tracing::error!(
268                                 %e,
269                                 "Failed to read Cargo metadata from rustc source at {}",
270                                 rustc_dir.display()
271                             );
272                             Err(Some(format!(
273                                 "Failed to read Cargo metadata from rustc source at {}: {e}",
274                                 rustc_dir.display())
275                             ))
276                         }
277                     }
278                 });
279 
280                 let rustc_cfg =
281                     rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env);
282 
283                 let cfg_overrides = config.cfg_overrides.clone();
284                 let data_layout = target_data_layout::get(
285                     Some(&cargo_toml),
286                     config.target.as_deref(),
287                     &config.extra_env,
288                 );
289                 if let Err(e) = &data_layout {
290                     tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace");
291                 }
292                 ProjectWorkspace::Cargo {
293                     cargo,
294                     build_scripts: WorkspaceBuildScripts::default(),
295                     sysroot,
296                     rustc,
297                     rustc_cfg,
298                     cfg_overrides,
299                     toolchain,
300                     target_layout: data_layout.map_err(|it| it.to_string()),
301                 }
302             }
303         };
304 
305         Ok(res)
306     }
307 
load_inline( project_json: ProjectJson, target: Option<&str>, extra_env: &FxHashMap<String, String>, toolchain: Option<Version>, ) -> ProjectWorkspace308     pub fn load_inline(
309         project_json: ProjectJson,
310         target: Option<&str>,
311         extra_env: &FxHashMap<String, String>,
312         toolchain: Option<Version>,
313     ) -> ProjectWorkspace {
314         let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
315             (Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src)),
316             (Some(sysroot), None) => {
317                 // assume sysroot is structured like rustup's and guess `sysroot_src`
318                 let sysroot_src =
319                     sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
320                 Ok(Sysroot::load(sysroot, sysroot_src))
321             }
322             (None, Some(sysroot_src)) => {
323                 // assume sysroot is structured like rustup's and guess `sysroot`
324                 let mut sysroot = sysroot_src.clone();
325                 for _ in 0..5 {
326                     sysroot.pop();
327                 }
328                 Ok(Sysroot::load(sysroot, sysroot_src))
329             }
330             (None, None) => Err(None),
331         };
332         if let Ok(sysroot) = &sysroot {
333             tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
334         }
335 
336         let rustc_cfg = rustc_cfg::get(None, target, extra_env);
337         ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain }
338     }
339 
load_detached_files( detached_files: Vec<AbsPathBuf>, config: &CargoConfig, ) -> Result<ProjectWorkspace>340     pub fn load_detached_files(
341         detached_files: Vec<AbsPathBuf>,
342         config: &CargoConfig,
343     ) -> Result<ProjectWorkspace> {
344         let sysroot = match &config.sysroot {
345             Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone())
346                 .map_err(|e| Some(format!("Failed to find sysroot at {}:{e}", path.display()))),
347             Some(RustLibSource::Discover) => {
348                 let dir = &detached_files
349                     .first()
350                     .and_then(|it| it.parent())
351                     .ok_or_else(|| format_err!("No detached files to load"))?;
352                 Sysroot::discover(dir, &config.extra_env).map_err(|e| {
353                     Some(format!(
354                         "Failed to find sysroot for {}. Is rust-src installed? {e}",
355                         dir.display()
356                     ))
357                 })
358             }
359             None => Err(None),
360         };
361         if let Ok(sysroot) = &sysroot {
362             tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
363         }
364         let rustc_cfg = rustc_cfg::get(None, None, &Default::default());
365         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
366     }
367 
368     /// Runs the build scripts for this [`ProjectWorkspace`].
run_build_scripts( &self, config: &CargoConfig, progress: &dyn Fn(String), ) -> Result<WorkspaceBuildScripts>369     pub fn run_build_scripts(
370         &self,
371         config: &CargoConfig,
372         progress: &dyn Fn(String),
373     ) -> Result<WorkspaceBuildScripts> {
374         match self {
375             ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
376                 WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
377                     .with_context(|| {
378                         format!(
379                             "Failed to run build scripts for {}",
380                             &cargo.workspace_root().display()
381                         )
382                     })
383             }
384             ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
385                 Ok(WorkspaceBuildScripts::default())
386             }
387         }
388     }
389 
390     /// Runs the build scripts for the given [`ProjectWorkspace`]s. Depending on the invocation
391     /// strategy this may run a single build process for all project workspaces.
run_all_build_scripts( workspaces: &[ProjectWorkspace], config: &CargoConfig, progress: &dyn Fn(String), ) -> Vec<Result<WorkspaceBuildScripts>>392     pub fn run_all_build_scripts(
393         workspaces: &[ProjectWorkspace],
394         config: &CargoConfig,
395         progress: &dyn Fn(String),
396     ) -> Vec<Result<WorkspaceBuildScripts>> {
397         if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace)
398             || config.run_build_script_command.is_none()
399         {
400             return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
401         }
402 
403         let cargo_ws: Vec<_> = workspaces
404             .iter()
405             .filter_map(|it| match it {
406                 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
407                 _ => None,
408             })
409             .collect();
410         let outputs = &mut match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress) {
411             Ok(it) => Ok(it.into_iter()),
412             // io::Error is not Clone?
413             Err(e) => Err(sync::Arc::new(e)),
414         };
415 
416         workspaces
417             .iter()
418             .map(|it| match it {
419                 ProjectWorkspace::Cargo { cargo, .. } => match outputs {
420                     Ok(outputs) => Ok(outputs.next().unwrap()),
421                     Err(e) => Err(e.clone()).with_context(|| {
422                         format!(
423                             "Failed to run build scripts for {}",
424                             &cargo.workspace_root().display()
425                         )
426                     }),
427                 },
428                 _ => Ok(WorkspaceBuildScripts::default()),
429             })
430             .collect()
431     }
432 
set_build_scripts(&mut self, bs: WorkspaceBuildScripts)433     pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
434         match self {
435             ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
436             _ => {
437                 always!(bs == WorkspaceBuildScripts::default());
438             }
439         }
440     }
441 
workspace_definition_path(&self) -> Option<&AbsPath>442     pub fn workspace_definition_path(&self) -> Option<&AbsPath> {
443         match self {
444             ProjectWorkspace::Cargo { cargo, .. } => Some(cargo.workspace_root()),
445             ProjectWorkspace::Json { project, .. } => Some(project.path()),
446             ProjectWorkspace::DetachedFiles { .. } => None,
447         }
448     }
449 
find_sysroot_proc_macro_srv(&self) -> Result<AbsPathBuf>450     pub fn find_sysroot_proc_macro_srv(&self) -> Result<AbsPathBuf> {
451         match self {
452             ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
453             | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
454             | ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => {
455                 let standalone_server_name =
456                     format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
457                 ["libexec", "lib"]
458                     .into_iter()
459                     .map(|segment| sysroot.root().join(segment).join(&standalone_server_name))
460                     .find(|server_path| std::fs::metadata(server_path).is_ok())
461                     .ok_or_else(|| {
462                         anyhow::anyhow!(
463                             "cannot find proc-macro server in sysroot `{}`",
464                             sysroot.root().display()
465                         )
466                     })
467             }
468             ProjectWorkspace::DetachedFiles { .. } => {
469                 Err(anyhow::anyhow!("cannot find proc-macro server, no sysroot was found"))
470             }
471             ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::anyhow!(
472                 "cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
473                 cargo.workspace_root().display()
474             )),
475             ProjectWorkspace::Json { project, .. } => Err(anyhow::anyhow!(
476                 "cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
477                 project.path().display()
478             )),
479         }
480     }
481 
482     /// Returns the roots for the current `ProjectWorkspace`
483     /// The return type contains the path and whether or not
484     /// the root is a member of the current workspace
to_roots(&self) -> Vec<PackageRoot>485     pub fn to_roots(&self) -> Vec<PackageRoot> {
486         let mk_sysroot = |sysroot: Result<&Sysroot, _>, project_root: Option<&AbsPath>| {
487             sysroot.map(|sysroot| PackageRoot {
488                 // mark the sysroot as mutable if it is located inside of the project
489                 is_local: project_root
490                     .map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
491                 include: vec![sysroot.src_root().to_path_buf()],
492                 exclude: Vec::new(),
493             })
494         };
495         match self {
496             ProjectWorkspace::Json { project, sysroot, rustc_cfg: _, toolchain: _ } => project
497                 .crates()
498                 .map(|(_, krate)| PackageRoot {
499                     is_local: krate.is_workspace_member,
500                     include: krate.include.clone(),
501                     exclude: krate.exclude.clone(),
502                 })
503                 .collect::<FxHashSet<_>>()
504                 .into_iter()
505                 .chain(mk_sysroot(sysroot.as_ref(), Some(project.path())))
506                 .collect::<Vec<_>>(),
507             ProjectWorkspace::Cargo {
508                 cargo,
509                 sysroot,
510                 rustc,
511                 rustc_cfg: _,
512                 cfg_overrides: _,
513                 build_scripts,
514                 toolchain: _,
515                 target_layout: _,
516             } => {
517                 cargo
518                     .packages()
519                     .map(|pkg| {
520                         let is_local = cargo[pkg].is_local;
521                         let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
522 
523                         let mut include = vec![pkg_root.clone()];
524                         let out_dir =
525                             build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
526                         include.extend(out_dir);
527 
528                         // In case target's path is manually set in Cargo.toml to be
529                         // outside the package root, add its parent as an extra include.
530                         // An example of this situation would look like this:
531                         //
532                         // ```toml
533                         // [lib]
534                         // path = "../../src/lib.rs"
535                         // ```
536                         let extra_targets = cargo[pkg]
537                             .targets
538                             .iter()
539                             .filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
540                             .filter_map(|&tgt| cargo[tgt].root.parent())
541                             .map(|tgt| tgt.normalize().to_path_buf())
542                             .filter(|path| !path.starts_with(&pkg_root));
543                         include.extend(extra_targets);
544 
545                         let mut exclude = vec![pkg_root.join(".git")];
546                         if is_local {
547                             exclude.push(pkg_root.join("target"));
548                         } else {
549                             exclude.push(pkg_root.join("tests"));
550                             exclude.push(pkg_root.join("examples"));
551                             exclude.push(pkg_root.join("benches"));
552                         }
553                         PackageRoot { is_local, include, exclude }
554                     })
555                     .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root())))
556                     .chain(rustc.iter().flat_map(|(rustc, _)| {
557                         rustc.packages().map(move |krate| PackageRoot {
558                             is_local: false,
559                             include: vec![rustc[krate].manifest.parent().to_path_buf()],
560                             exclude: Vec::new(),
561                         })
562                     }))
563                     .collect()
564             }
565             ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
566                 .iter()
567                 .map(|detached_file| PackageRoot {
568                     is_local: true,
569                     include: vec![detached_file.clone()],
570                     exclude: Vec::new(),
571                 })
572                 .chain(mk_sysroot(sysroot.as_ref(), None))
573                 .collect(),
574         }
575     }
576 
n_packages(&self) -> usize577     pub fn n_packages(&self) -> usize {
578         match self {
579             ProjectWorkspace::Json { project, sysroot, .. } => {
580                 let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
581                 sysroot_package_len + project.n_crates()
582             }
583             ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
584                 let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len());
585                 let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
586                 cargo.packages().len() + sysroot_package_len + rustc_package_len
587             }
588             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
589                 let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
590                 sysroot_package_len + files.len()
591             }
592         }
593     }
594 
to_crate_graph( &self, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, extra_env: &FxHashMap<String, String>, ) -> (CrateGraph, ProcMacroPaths)595     pub fn to_crate_graph(
596         &self,
597         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
598         extra_env: &FxHashMap<String, String>,
599     ) -> (CrateGraph, ProcMacroPaths) {
600         let _p = profile::span("ProjectWorkspace::to_crate_graph");
601 
602         let (mut crate_graph, proc_macros) = match self {
603             ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
604                 project_json_to_crate_graph(
605                     rustc_cfg.clone(),
606                     load,
607                     project,
608                     sysroot.as_ref().ok(),
609                     extra_env,
610                     Err("rust-project.json projects have no target layout set".into()),
611                     toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())),
612                 )
613             }
614             ProjectWorkspace::Cargo {
615                 cargo,
616                 sysroot,
617                 rustc,
618                 rustc_cfg,
619                 cfg_overrides,
620                 build_scripts,
621                 toolchain,
622                 target_layout,
623             } => cargo_to_crate_graph(
624                 load,
625                 rustc.as_ref().ok(),
626                 cargo,
627                 sysroot.as_ref().ok(),
628                 rustc_cfg.clone(),
629                 cfg_overrides,
630                 None,
631                 build_scripts,
632                 match target_layout.as_ref() {
633                     Ok(it) => Ok(Arc::from(it.as_str())),
634                     Err(it) => Err(Arc::from(it.as_str())),
635                 },
636                 toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())),
637             ),
638             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
639                 detached_files_to_crate_graph(
640                     rustc_cfg.clone(),
641                     load,
642                     files,
643                     sysroot.as_ref().ok(),
644                     Err("detached file projects have no target layout set".into()),
645                 )
646             }
647         };
648         if crate_graph.patch_cfg_if() {
649             tracing::debug!("Patched std to depend on cfg-if")
650         } else {
651             tracing::debug!("Did not patch std to depend on cfg-if")
652         }
653         (crate_graph, proc_macros)
654     }
655 
eq_ignore_build_data(&self, other: &Self) -> bool656     pub fn eq_ignore_build_data(&self, other: &Self) -> bool {
657         match (self, other) {
658             (
659                 Self::Cargo {
660                     cargo,
661                     sysroot,
662                     rustc,
663                     rustc_cfg,
664                     cfg_overrides,
665                     toolchain,
666                     build_scripts: _,
667                     target_layout: _,
668                 },
669                 Self::Cargo {
670                     cargo: o_cargo,
671                     sysroot: o_sysroot,
672                     rustc: o_rustc,
673                     rustc_cfg: o_rustc_cfg,
674                     cfg_overrides: o_cfg_overrides,
675                     toolchain: o_toolchain,
676                     build_scripts: _,
677                     target_layout: _,
678                 },
679             ) => {
680                 cargo == o_cargo
681                     && rustc == o_rustc
682                     && rustc_cfg == o_rustc_cfg
683                     && cfg_overrides == o_cfg_overrides
684                     && toolchain == o_toolchain
685                     && sysroot == o_sysroot
686             }
687             (
688                 Self::Json { project, sysroot, rustc_cfg, toolchain },
689                 Self::Json {
690                     project: o_project,
691                     sysroot: o_sysroot,
692                     rustc_cfg: o_rustc_cfg,
693                     toolchain: o_toolchain,
694                 },
695             ) => {
696                 project == o_project
697                     && rustc_cfg == o_rustc_cfg
698                     && sysroot == o_sysroot
699                     && toolchain == o_toolchain
700             }
701             (
702                 Self::DetachedFiles { files, sysroot, rustc_cfg },
703                 Self::DetachedFiles { files: o_files, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg },
704             ) => files == o_files && sysroot == o_sysroot && rustc_cfg == o_rustc_cfg,
705             _ => false,
706         }
707     }
708 
709     /// Returns `true` if the project workspace is [`Json`].
710     ///
711     /// [`Json`]: ProjectWorkspace::Json
712     #[must_use]
is_json(&self) -> bool713     pub fn is_json(&self) -> bool {
714         matches!(self, Self::Json { .. })
715     }
716 }
717 
project_json_to_crate_graph( rustc_cfg: Vec<CfgFlag>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, project: &ProjectJson, sysroot: Option<&Sysroot>, extra_env: &FxHashMap<String, String>, target_layout: TargetLayoutLoadResult, channel: Option<ReleaseChannel>, ) -> (CrateGraph, ProcMacroPaths)718 fn project_json_to_crate_graph(
719     rustc_cfg: Vec<CfgFlag>,
720     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
721     project: &ProjectJson,
722     sysroot: Option<&Sysroot>,
723     extra_env: &FxHashMap<String, String>,
724     target_layout: TargetLayoutLoadResult,
725     channel: Option<ReleaseChannel>,
726 ) -> (CrateGraph, ProcMacroPaths) {
727     let mut res = (CrateGraph::default(), ProcMacroPaths::default());
728     let (crate_graph, proc_macros) = &mut res;
729     let sysroot_deps = sysroot.as_ref().map(|sysroot| {
730         sysroot_to_crate_graph(
731             crate_graph,
732             sysroot,
733             rustc_cfg.clone(),
734             target_layout.clone(),
735             load,
736             channel,
737         )
738     });
739 
740     let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
741     let crates: FxHashMap<CrateId, CrateId> = project
742         .crates()
743         .filter_map(|(crate_id, krate)| Some((crate_id, krate, load(&krate.root_module)?)))
744         .map(
745             |(
746                 crate_id,
747                 Crate {
748                     display_name,
749                     edition,
750                     version,
751                     cfg,
752                     target,
753                     env,
754                     proc_macro_dylib_path,
755                     is_proc_macro,
756                     repository,
757                     ..
758                 },
759                 file_id,
760             )| {
761                 let env = env.clone().into_iter().collect();
762 
763                 let target_cfgs = match target.as_deref() {
764                     Some(target) => cfg_cache
765                         .entry(target)
766                         .or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)),
767                     None => &rustc_cfg,
768                 };
769 
770                 let crate_graph_crate_id = crate_graph.add_crate_root(
771                     file_id,
772                     *edition,
773                     display_name.clone(),
774                     version.clone(),
775                     target_cfgs.iter().chain(cfg.iter()).cloned().collect(),
776                     None,
777                     env,
778                     *is_proc_macro,
779                     if let Some(name) = display_name.clone() {
780                         CrateOrigin::Local {
781                             repo: repository.clone(),
782                             name: Some(name.canonical_name().to_string()),
783                         }
784                     } else {
785                         CrateOrigin::Local { repo: None, name: None }
786                     },
787                     target_layout.clone(),
788                     channel,
789                 );
790                 if *is_proc_macro {
791                     if let Some(path) = proc_macro_dylib_path.clone() {
792                         let node = Ok((
793                             display_name.as_ref().map(|it| it.canonical_name().to_owned()),
794                             path,
795                         ));
796                         proc_macros.insert(crate_graph_crate_id, node);
797                     }
798                 }
799                 (crate_id, crate_graph_crate_id)
800             },
801         )
802         .collect();
803 
804     for (from, krate) in project.crates() {
805         if let Some(&from) = crates.get(&from) {
806             if let Some((public_deps, libproc_macro)) = &sysroot_deps {
807                 public_deps.add_to_crate_graph(crate_graph, from);
808                 if let Some(proc_macro) = libproc_macro {
809                     add_proc_macro_dep(crate_graph, from, *proc_macro, krate.is_proc_macro);
810                 }
811             }
812 
813             for dep in &krate.deps {
814                 if let Some(&to) = crates.get(&dep.crate_id) {
815                     add_dep(crate_graph, from, dep.name.clone(), to)
816                 }
817             }
818         }
819     }
820     res
821 }
822 
cargo_to_crate_graph( load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, cargo: &CargoWorkspace, sysroot: Option<&Sysroot>, rustc_cfg: Vec<CfgFlag>, override_cfg: &CfgOverrides, forced_cfg: Option<CfgOptions>, build_scripts: &WorkspaceBuildScripts, target_layout: TargetLayoutLoadResult, channel: Option<ReleaseChannel>, ) -> (CrateGraph, ProcMacroPaths)823 fn cargo_to_crate_graph(
824     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
825     rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
826     cargo: &CargoWorkspace,
827     sysroot: Option<&Sysroot>,
828     rustc_cfg: Vec<CfgFlag>,
829     override_cfg: &CfgOverrides,
830     // Don't compute cfg and use this if present
831     forced_cfg: Option<CfgOptions>,
832     build_scripts: &WorkspaceBuildScripts,
833     target_layout: TargetLayoutLoadResult,
834     channel: Option<ReleaseChannel>,
835 ) -> (CrateGraph, ProcMacroPaths) {
836     let _p = profile::span("cargo_to_crate_graph");
837     let mut res = (CrateGraph::default(), ProcMacroPaths::default());
838     let crate_graph = &mut res.0;
839     let proc_macros = &mut res.1;
840     let (public_deps, libproc_macro) = match sysroot {
841         Some(sysroot) => sysroot_to_crate_graph(
842             crate_graph,
843             sysroot,
844             rustc_cfg.clone(),
845             target_layout.clone(),
846             load,
847             channel,
848         ),
849         None => (SysrootPublicDeps::default(), None),
850     };
851 
852     let cfg_options = {
853         let mut cfg_options = CfgOptions::default();
854         cfg_options.extend(rustc_cfg);
855         cfg_options.insert_atom("debug_assertions".into());
856         cfg_options
857     };
858 
859     // Mapping of a package to its library target
860     let mut pkg_to_lib_crate = FxHashMap::default();
861     let mut pkg_crates = FxHashMap::default();
862     // Does any crate signal to rust-analyzer that they need the rustc_private crates?
863     let mut has_private = false;
864 
865     // Next, create crates for each package, target pair
866     for pkg in cargo.packages() {
867         has_private |= cargo[pkg].metadata.rustc_private;
868 
869         let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
870             let mut cfg_options = cfg_options.clone();
871 
872             // Add test cfg for local crates
873             if cargo[pkg].is_local {
874                 cfg_options.insert_atom("test".into());
875             }
876 
877             if !override_cfg.global.is_empty() {
878                 cfg_options.apply_diff(override_cfg.global.clone());
879             };
880             if let Some(diff) = override_cfg.selective.get(&cargo[pkg].name) {
881                 // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
882                 // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
883                 // working on rust-lang/rust as that's the only time it appears outside sysroot).
884                 //
885                 // A more ideal solution might be to reanalyze crates based on where the cursor is and
886                 // figure out the set of cfgs that would have to apply to make it active.
887 
888                 cfg_options.apply_diff(diff.clone());
889             };
890             cfg_options
891         });
892 
893         let mut lib_tgt = None;
894         for &tgt in cargo[pkg].targets.iter() {
895             if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
896                 // For non-workspace-members, Cargo does not resolve dev-dependencies, so we don't
897                 // add any targets except the library target, since those will not work correctly if
898                 // they use dev-dependencies.
899                 // In fact, they can break quite badly if multiple client workspaces get merged:
900                 // https://github.com/rust-lang/rust-analyzer/issues/11300
901                 continue;
902             }
903             let &TargetData { ref name, kind, is_proc_macro, ref root, .. } = &cargo[tgt];
904 
905             if kind == TargetKind::Lib
906                 && sysroot.map_or(false, |sysroot| root.starts_with(sysroot.src_root()))
907             {
908                 if let Some(&(_, crate_id, _)) =
909                     public_deps.deps.iter().find(|(dep_name, ..)| dep_name.as_smol_str() == name)
910                 {
911                     pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
912 
913                     lib_tgt = Some((crate_id, name.clone()));
914                     pkg_to_lib_crate.insert(pkg, crate_id);
915                     // sysroot is inside the workspace, prevent the sysroot crates from being duplicated here
916                     continue;
917                 }
918             }
919 
920             let Some(file_id) = load(root) else { continue };
921 
922             let crate_id = add_target_crate_root(
923                 crate_graph,
924                 proc_macros,
925                 &cargo[pkg],
926                 build_scripts.get_output(pkg),
927                 cfg_options.clone(),
928                 file_id,
929                 name,
930                 is_proc_macro,
931                 target_layout.clone(),
932                 false,
933                 channel,
934             );
935             if kind == TargetKind::Lib {
936                 lib_tgt = Some((crate_id, name.clone()));
937                 pkg_to_lib_crate.insert(pkg, crate_id);
938             }
939             // Even crates that don't set proc-macro = true are allowed to depend on proc_macro
940             // (just none of the APIs work when called outside of a proc macro).
941             if let Some(proc_macro) = libproc_macro {
942                 add_proc_macro_dep(crate_graph, crate_id, proc_macro, is_proc_macro);
943             }
944 
945             pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
946         }
947 
948         // Set deps to the core, std and to the lib target of the current package
949         for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
950             // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
951             public_deps.add_to_crate_graph(crate_graph, from);
952 
953             // Add dep edge of all targets to the package's lib target
954             if let Some((to, name)) = lib_tgt.clone() {
955                 if to != from && kind != TargetKind::BuildScript {
956                     // (build script can not depend on its library target)
957 
958                     // For root projects with dashes in their name,
959                     // cargo metadata does not do any normalization,
960                     // so we do it ourselves currently
961                     let name = CrateName::normalize_dashes(&name);
962                     add_dep(crate_graph, from, name, to);
963                 }
964             }
965         }
966     }
967 
968     // Now add a dep edge from all targets of upstream to the lib
969     // target of downstream.
970     for pkg in cargo.packages() {
971         for dep in &cargo[pkg].dependencies {
972             let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) else { continue };
973             let Some(targets) = pkg_crates.get(&pkg) else { continue };
974 
975             let name = CrateName::new(&dep.name).unwrap();
976             for &(from, kind) in targets {
977                 // Build scripts may only depend on build dependencies.
978                 if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
979                     continue;
980                 }
981 
982                 add_dep(crate_graph, from, name.clone(), to)
983             }
984         }
985     }
986 
987     if has_private {
988         // If the user provided a path to rustc sources, we add all the rustc_private crates
989         // and create dependencies on them for the crates which opt-in to that
990         if let Some((rustc_workspace, rustc_build_scripts)) = rustc {
991             handle_rustc_crates(
992                 crate_graph,
993                 proc_macros,
994                 &mut pkg_to_lib_crate,
995                 load,
996                 rustc_workspace,
997                 cargo,
998                 &public_deps,
999                 libproc_macro,
1000                 &pkg_crates,
1001                 &cfg_options,
1002                 override_cfg,
1003                 if rustc_workspace.workspace_root() == cargo.workspace_root() {
1004                     // the rustc workspace does not use the installed toolchain's proc-macro server
1005                     // so we need to make sure we don't use the pre compiled proc-macros there either
1006                     build_scripts
1007                 } else {
1008                     rustc_build_scripts
1009                 },
1010                 target_layout,
1011                 channel,
1012             );
1013         }
1014     }
1015     res
1016 }
1017 
detached_files_to_crate_graph( rustc_cfg: Vec<CfgFlag>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, detached_files: &[AbsPathBuf], sysroot: Option<&Sysroot>, target_layout: TargetLayoutLoadResult, ) -> (CrateGraph, ProcMacroPaths)1018 fn detached_files_to_crate_graph(
1019     rustc_cfg: Vec<CfgFlag>,
1020     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
1021     detached_files: &[AbsPathBuf],
1022     sysroot: Option<&Sysroot>,
1023     target_layout: TargetLayoutLoadResult,
1024 ) -> (CrateGraph, ProcMacroPaths) {
1025     let _p = profile::span("detached_files_to_crate_graph");
1026     let mut crate_graph = CrateGraph::default();
1027     let (public_deps, _libproc_macro) = match sysroot {
1028         Some(sysroot) => sysroot_to_crate_graph(
1029             &mut crate_graph,
1030             sysroot,
1031             rustc_cfg.clone(),
1032             target_layout.clone(),
1033             load,
1034             None,
1035         ),
1036         None => (SysrootPublicDeps::default(), None),
1037     };
1038 
1039     let mut cfg_options = CfgOptions::default();
1040     cfg_options.extend(rustc_cfg);
1041 
1042     for detached_file in detached_files {
1043         let file_id = match load(detached_file) {
1044             Some(file_id) => file_id,
1045             None => {
1046                 tracing::error!("Failed to load detached file {:?}", detached_file);
1047                 continue;
1048             }
1049         };
1050         let display_name = detached_file
1051             .file_stem()
1052             .and_then(|os_str| os_str.to_str())
1053             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
1054         let detached_file_crate = crate_graph.add_crate_root(
1055             file_id,
1056             Edition::CURRENT,
1057             display_name.clone(),
1058             None,
1059             cfg_options.clone(),
1060             None,
1061             Env::default(),
1062             false,
1063             CrateOrigin::Local {
1064                 repo: None,
1065                 name: display_name.map(|n| n.canonical_name().to_string()),
1066             },
1067             target_layout.clone(),
1068             None,
1069         );
1070 
1071         public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate);
1072     }
1073     (crate_graph, FxHashMap::default())
1074 }
1075 
handle_rustc_crates( crate_graph: &mut CrateGraph, proc_macros: &mut ProcMacroPaths, pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, rustc_workspace: &CargoWorkspace, cargo: &CargoWorkspace, public_deps: &SysrootPublicDeps, libproc_macro: Option<CrateId>, pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>, cfg_options: &CfgOptions, override_cfg: &CfgOverrides, build_scripts: &WorkspaceBuildScripts, target_layout: TargetLayoutLoadResult, channel: Option<ReleaseChannel>, )1076 fn handle_rustc_crates(
1077     crate_graph: &mut CrateGraph,
1078     proc_macros: &mut ProcMacroPaths,
1079     pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
1080     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
1081     rustc_workspace: &CargoWorkspace,
1082     cargo: &CargoWorkspace,
1083     public_deps: &SysrootPublicDeps,
1084     libproc_macro: Option<CrateId>,
1085     pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>,
1086     cfg_options: &CfgOptions,
1087     override_cfg: &CfgOverrides,
1088     build_scripts: &WorkspaceBuildScripts,
1089     target_layout: TargetLayoutLoadResult,
1090     channel: Option<ReleaseChannel>,
1091 ) {
1092     let mut rustc_pkg_crates = FxHashMap::default();
1093     // The root package of the rustc-dev component is rustc_driver, so we match that
1094     let root_pkg =
1095         rustc_workspace.packages().find(|&package| rustc_workspace[package].name == "rustc_driver");
1096     // The rustc workspace might be incomplete (such as if rustc-dev is not
1097     // installed for the current toolchain) and `rustc_source` is set to discover.
1098     if let Some(root_pkg) = root_pkg {
1099         // Iterate through every crate in the dependency subtree of rustc_driver using BFS
1100         let mut queue = VecDeque::new();
1101         queue.push_back(root_pkg);
1102         while let Some(pkg) = queue.pop_front() {
1103             // Don't duplicate packages if they are dependent on a diamond pattern
1104             // N.B. if this line is omitted, we try to analyze over 4_800_000 crates
1105             // which is not ideal
1106             if rustc_pkg_crates.contains_key(&pkg) {
1107                 continue;
1108             }
1109             for dep in &rustc_workspace[pkg].dependencies {
1110                 queue.push_back(dep.pkg);
1111             }
1112 
1113             let mut cfg_options = cfg_options.clone();
1114 
1115             if !override_cfg.global.is_empty() {
1116                 cfg_options.apply_diff(override_cfg.global.clone());
1117             };
1118             if let Some(diff) = override_cfg.selective.get(&rustc_workspace[pkg].name) {
1119                 // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
1120                 // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
1121                 // working on rust-lang/rust as that's the only time it appears outside sysroot).
1122                 //
1123                 // A more ideal solution might be to reanalyze crates based on where the cursor is and
1124                 // figure out the set of cfgs that would have to apply to make it active.
1125 
1126                 cfg_options.apply_diff(diff.clone());
1127             };
1128 
1129             for &tgt in rustc_workspace[pkg].targets.iter() {
1130                 if rustc_workspace[tgt].kind != TargetKind::Lib {
1131                     continue;
1132                 }
1133                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
1134                     let crate_id = add_target_crate_root(
1135                         crate_graph,
1136                         proc_macros,
1137                         &rustc_workspace[pkg],
1138                         build_scripts.get_output(pkg),
1139                         cfg_options.clone(),
1140                         file_id,
1141                         &rustc_workspace[tgt].name,
1142                         rustc_workspace[tgt].is_proc_macro,
1143                         target_layout.clone(),
1144                         true,
1145                         channel,
1146                     );
1147                     pkg_to_lib_crate.insert(pkg, crate_id);
1148                     // Add dependencies on core / std / alloc for this crate
1149                     public_deps.add_to_crate_graph(crate_graph, crate_id);
1150                     if let Some(proc_macro) = libproc_macro {
1151                         add_proc_macro_dep(
1152                             crate_graph,
1153                             crate_id,
1154                             proc_macro,
1155                             rustc_workspace[tgt].is_proc_macro,
1156                         );
1157                     }
1158                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
1159                 }
1160             }
1161         }
1162     }
1163     // Now add a dep edge from all targets of upstream to the lib
1164     // target of downstream.
1165     for pkg in rustc_pkg_crates.keys().copied() {
1166         for dep in rustc_workspace[pkg].dependencies.iter() {
1167             let name = CrateName::new(&dep.name).unwrap();
1168             if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
1169                 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
1170                     add_dep(crate_graph, from, name.clone(), to);
1171                 }
1172             }
1173         }
1174     }
1175     // Add a dependency on the rustc_private crates for all targets of each package
1176     // which opts in
1177     for dep in rustc_workspace.packages() {
1178         let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
1179 
1180         if let Some(&to) = pkg_to_lib_crate.get(&dep) {
1181             for pkg in cargo.packages() {
1182                 let package = &cargo[pkg];
1183                 if !package.metadata.rustc_private {
1184                     continue;
1185                 }
1186                 for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
1187                     // Avoid creating duplicate dependencies
1188                     // This avoids the situation where `from` depends on e.g. `arrayvec`, but
1189                     // `rust_analyzer` thinks that it should use the one from the `rustc_source`
1190                     // instead of the one from `crates.io`
1191                     if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
1192                         add_dep(crate_graph, *from, name.clone(), to);
1193                     }
1194                 }
1195             }
1196         }
1197     }
1198 }
1199 
add_target_crate_root( crate_graph: &mut CrateGraph, proc_macros: &mut ProcMacroPaths, pkg: &PackageData, build_data: Option<&BuildScriptOutput>, cfg_options: CfgOptions, file_id: FileId, cargo_name: &str, is_proc_macro: bool, target_layout: TargetLayoutLoadResult, rustc_crate: bool, channel: Option<ReleaseChannel>, ) -> CrateId1200 fn add_target_crate_root(
1201     crate_graph: &mut CrateGraph,
1202     proc_macros: &mut ProcMacroPaths,
1203     pkg: &PackageData,
1204     build_data: Option<&BuildScriptOutput>,
1205     cfg_options: CfgOptions,
1206     file_id: FileId,
1207     cargo_name: &str,
1208     is_proc_macro: bool,
1209     target_layout: TargetLayoutLoadResult,
1210     rustc_crate: bool,
1211     channel: Option<ReleaseChannel>,
1212 ) -> CrateId {
1213     let edition = pkg.edition;
1214     let potential_cfg_options = if pkg.features.is_empty() {
1215         None
1216     } else {
1217         let mut potential_cfg_options = cfg_options.clone();
1218         potential_cfg_options.extend(
1219             pkg.features
1220                 .iter()
1221                 .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
1222         );
1223         Some(potential_cfg_options)
1224     };
1225     let cfg_options = {
1226         let mut opts = cfg_options;
1227         for feature in pkg.active_features.iter() {
1228             opts.insert_key_value("feature".into(), feature.into());
1229         }
1230         if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
1231             opts.extend(cfgs.iter().cloned());
1232         }
1233         opts
1234     };
1235 
1236     let mut env = Env::default();
1237     inject_cargo_env(pkg, &mut env);
1238 
1239     if let Some(envs) = build_data.map(|it| &it.envs) {
1240         for (k, v) in envs {
1241             env.set(k, v.clone());
1242         }
1243     }
1244 
1245     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
1246     let crate_id = crate_graph.add_crate_root(
1247         file_id,
1248         edition,
1249         Some(display_name),
1250         Some(pkg.version.to_string()),
1251         cfg_options,
1252         potential_cfg_options,
1253         env,
1254         is_proc_macro,
1255         if rustc_crate {
1256             CrateOrigin::Rustc { name: pkg.name.clone() }
1257         } else if pkg.is_member {
1258             CrateOrigin::Local { repo: pkg.repository.clone(), name: Some(pkg.name.clone()) }
1259         } else {
1260             CrateOrigin::Library { repo: pkg.repository.clone(), name: pkg.name.clone() }
1261         },
1262         target_layout,
1263         channel,
1264     );
1265     if is_proc_macro {
1266         let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) {
1267             Some(it) => it.cloned().map(|path| Ok((Some(cargo_name.to_owned()), path))),
1268             None => Some(Err("crate has not yet been built".to_owned())),
1269         };
1270         if let Some(proc_macro) = proc_macro {
1271             proc_macros.insert(crate_id, proc_macro);
1272         }
1273     }
1274 
1275     crate_id
1276 }
1277 
1278 #[derive(Default)]
1279 struct SysrootPublicDeps {
1280     deps: Vec<(CrateName, CrateId, bool)>,
1281 }
1282 
1283 impl SysrootPublicDeps {
1284     /// Makes `from` depend on the public sysroot crates.
add_to_crate_graph(&self, crate_graph: &mut CrateGraph, from: CrateId)1285     fn add_to_crate_graph(&self, crate_graph: &mut CrateGraph, from: CrateId) {
1286         for (name, krate, prelude) in &self.deps {
1287             add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
1288         }
1289     }
1290 }
1291 
sysroot_to_crate_graph( crate_graph: &mut CrateGraph, sysroot: &Sysroot, rustc_cfg: Vec<CfgFlag>, target_layout: TargetLayoutLoadResult, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, channel: Option<ReleaseChannel>, ) -> (SysrootPublicDeps, Option<CrateId>)1292 fn sysroot_to_crate_graph(
1293     crate_graph: &mut CrateGraph,
1294     sysroot: &Sysroot,
1295     rustc_cfg: Vec<CfgFlag>,
1296     target_layout: TargetLayoutLoadResult,
1297     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
1298     channel: Option<ReleaseChannel>,
1299 ) -> (SysrootPublicDeps, Option<CrateId>) {
1300     let _p = profile::span("sysroot_to_crate_graph");
1301     let mut cfg_options = CfgOptions::default();
1302     cfg_options.extend(rustc_cfg.clone());
1303     let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = match &sysroot.hack_cargo_workspace {
1304         Some(cargo) => handle_hack_cargo_workspace(
1305             load,
1306             cargo,
1307             rustc_cfg,
1308             cfg_options,
1309             target_layout,
1310             channel,
1311             crate_graph,
1312             sysroot,
1313         ),
1314         None => sysroot
1315             .crates()
1316             .filter_map(|krate| {
1317                 let file_id = load(&sysroot[krate].root)?;
1318 
1319                 let env = Env::default();
1320                 let display_name =
1321                     CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
1322                 let crate_id = crate_graph.add_crate_root(
1323                     file_id,
1324                     Edition::CURRENT,
1325                     Some(display_name),
1326                     None,
1327                     cfg_options.clone(),
1328                     None,
1329                     env,
1330                     false,
1331                     CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
1332                     target_layout.clone(),
1333                     channel,
1334                 );
1335                 Some((krate, crate_id))
1336             })
1337             .collect(),
1338     };
1339     for from in sysroot.crates() {
1340         for &to in sysroot[from].deps.iter() {
1341             let name = CrateName::new(&sysroot[to].name).unwrap();
1342             if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
1343                 add_dep(crate_graph, from, name, to);
1344             }
1345         }
1346     }
1347 
1348     let public_deps = SysrootPublicDeps {
1349         deps: sysroot
1350             .public_deps()
1351             .map(|(name, idx, prelude)| (name, sysroot_crates[&idx], prelude))
1352             .collect::<Vec<_>>(),
1353     };
1354 
1355     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
1356     (public_deps, libproc_macro)
1357 }
1358 
handle_hack_cargo_workspace( load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, cargo: &CargoWorkspace, rustc_cfg: Vec<CfgFlag>, cfg_options: CfgOptions, target_layout: Result<Arc<str>, Arc<str>>, channel: Option<ReleaseChannel>, crate_graph: &mut CrateGraph, sysroot: &Sysroot, ) -> FxHashMap<SysrootCrate, CrateId>1359 fn handle_hack_cargo_workspace(
1360     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
1361     cargo: &CargoWorkspace,
1362     rustc_cfg: Vec<CfgFlag>,
1363     cfg_options: CfgOptions,
1364     target_layout: Result<Arc<str>, Arc<str>>,
1365     channel: Option<ReleaseChannel>,
1366     crate_graph: &mut CrateGraph,
1367     sysroot: &Sysroot,
1368 ) -> FxHashMap<SysrootCrate, CrateId> {
1369     let (cg, mut pm) = cargo_to_crate_graph(
1370         load,
1371         None,
1372         cargo,
1373         None,
1374         rustc_cfg,
1375         &CfgOverrides::default(),
1376         Some(cfg_options),
1377         &WorkspaceBuildScripts::default(),
1378         target_layout,
1379         channel,
1380     );
1381     crate_graph.extend(cg, &mut pm);
1382     for crate_name in ["std", "alloc", "core"] {
1383         let original = crate_graph
1384             .iter()
1385             .find(|x| {
1386                 crate_graph[*x]
1387                     .display_name
1388                     .as_ref()
1389                     .map(|x| x.canonical_name() == crate_name)
1390                     .unwrap_or(false)
1391             })
1392             .unwrap();
1393         let fake_crate_name = format!("rustc-std-workspace-{}", crate_name);
1394         let fake = crate_graph
1395             .iter()
1396             .find(|x| {
1397                 crate_graph[*x]
1398                     .display_name
1399                     .as_ref()
1400                     .map(|x| x.canonical_name() == fake_crate_name)
1401                     .unwrap_or(false)
1402             })
1403             .unwrap();
1404         crate_graph.remove_and_replace(fake, original).unwrap();
1405     }
1406     for (_, c) in crate_graph.iter_mut() {
1407         if c.origin.is_local() {
1408             // LangCrateOrigin::Other is good enough for a hack.
1409             c.origin = CrateOrigin::Lang(LangCrateOrigin::Other);
1410         }
1411     }
1412     sysroot
1413         .crates()
1414         .filter_map(|krate| {
1415             let file_id = load(&sysroot[krate].root)?;
1416             let crate_id = crate_graph.crate_id_for_crate_root(file_id)?;
1417             Some((krate, crate_id))
1418         })
1419         .collect()
1420 }
1421 
add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId)1422 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
1423     add_dep_inner(graph, from, Dependency::new(name, to))
1424 }
1425 
add_dep_with_prelude( graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId, prelude: bool, )1426 fn add_dep_with_prelude(
1427     graph: &mut CrateGraph,
1428     from: CrateId,
1429     name: CrateName,
1430     to: CrateId,
1431     prelude: bool,
1432 ) {
1433     add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
1434 }
1435 
add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool)1436 fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
1437     add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
1438 }
1439 
add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency)1440 fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
1441     if let Err(err) = graph.add_dep(from, dep) {
1442         tracing::error!("{}", err)
1443     }
1444 }
1445 
1446 /// Recreates the compile-time environment variables that Cargo sets.
1447 ///
1448 /// Should be synced with
1449 /// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
1450 ///
1451 /// FIXME: ask Cargo to provide this data instead of re-deriving.
inject_cargo_env(package: &PackageData, env: &mut Env)1452 fn inject_cargo_env(package: &PackageData, env: &mut Env) {
1453     // FIXME: Missing variables:
1454     // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
1455 
1456     let manifest_dir = package.manifest.parent();
1457     env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
1458 
1459     // Not always right, but works for common cases.
1460     env.set("CARGO", "cargo".into());
1461 
1462     env.set("CARGO_PKG_VERSION", package.version.to_string());
1463     env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
1464     env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
1465     env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
1466     env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
1467 
1468     env.set("CARGO_PKG_AUTHORS", String::new());
1469 
1470     env.set("CARGO_PKG_NAME", package.name.clone());
1471     // FIXME: This isn't really correct (a package can have many crates with different names), but
1472     // it's better than leaving the variable unset.
1473     env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
1474     env.set("CARGO_PKG_DESCRIPTION", String::new());
1475     env.set("CARGO_PKG_HOMEPAGE", String::new());
1476     env.set("CARGO_PKG_REPOSITORY", String::new());
1477     env.set("CARGO_PKG_LICENSE", String::new());
1478 
1479     env.set("CARGO_PKG_LICENSE_FILE", String::new());
1480 }
1481