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