project_model/
workspace.rs

1//! Handles lowering of build-system specific workspace information (`cargo
2//! metadata` or `rust-project.json`) into representation stored in the salsa
3//! database -- `CrateGraph`.
4
5use std::{collections::VecDeque, fmt, fs, iter, ops::Deref, sync, thread};
6
7use anyhow::Context;
8use base_db::{
9    CrateBuilderId, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin,
10    CrateWorkspaceData, DependencyBuilder, Env, LangCrateOrigin, ProcMacroLoadingError,
11    ProcMacroPaths, TargetLayoutLoadResult,
12};
13use cfg::{CfgAtom, CfgDiff, CfgOptions};
14use intern::{Symbol, sym};
15use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
16use rustc_hash::{FxHashMap, FxHashSet};
17use semver::Version;
18use span::{Edition, FileId};
19use toolchain::Tool;
20use tracing::instrument;
21use triomphe::Arc;
22
23use crate::{
24    CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package,
25    ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind,
26    WorkspaceBuildScripts,
27    build_dependencies::{BuildScriptOutput, ProcMacroDylibPath},
28    cargo_config_file,
29    cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource},
30    env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
31    project_json::{Crate, CrateArrayIdx},
32    sysroot::RustLibSrcWorkspace,
33    toolchain_info::{QueryConfig, rustc_cfg, target_data_layout, target_tuple, version},
34    utf8_stdout,
35};
36use tracing::{debug, error, info};
37
38pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>;
39
40/// `PackageRoot` describes a package root folder.
41/// Which may be an external dependency, or a member of
42/// the current workspace.
43#[derive(Debug, Clone, Eq, PartialEq, Hash)]
44pub struct PackageRoot {
45    /// Is from the local filesystem and may be edited
46    pub is_local: bool,
47    /// Directories to include
48    pub include: Vec<AbsPathBuf>,
49    /// Directories to exclude
50    pub exclude: Vec<AbsPathBuf>,
51}
52
53#[derive(Clone)]
54pub struct ProjectWorkspace {
55    pub kind: ProjectWorkspaceKind,
56    /// The sysroot loaded for this workspace.
57    pub sysroot: Sysroot,
58    /// Holds cfg flags for the current target. We get those by running
59    /// `rustc --print cfg`.
60    // FIXME: make this a per-crate map, as, eg, build.rs might have a
61    // different target.
62    pub rustc_cfg: Vec<CfgAtom>,
63    /// The toolchain version used by this workspace.
64    pub toolchain: Option<Version>,
65    /// The target data layout queried for workspace.
66    pub target_layout: TargetLayoutLoadResult,
67    /// A set of cfg overrides for this workspace.
68    pub cfg_overrides: CfgOverrides,
69    /// Additional includes to add for the VFS.
70    pub extra_includes: Vec<AbsPathBuf>,
71    /// Set `cfg(test)` for local crates
72    pub set_test: bool,
73}
74
75#[derive(Clone)]
76#[allow(clippy::large_enum_variant)]
77pub enum ProjectWorkspaceKind {
78    /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
79    Cargo {
80        /// The workspace as returned by `cargo metadata`.
81        cargo: CargoWorkspace,
82        /// Additional `cargo metadata` error. (only populated if retried fetching via `--no-deps` succeeded).
83        error: Option<Arc<anyhow::Error>>,
84        /// The build script results for the workspace.
85        build_scripts: WorkspaceBuildScripts,
86        /// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been
87        /// disabled or was otherwise not requested.
88        rustc: Result<Box<(CargoWorkspace, WorkspaceBuildScripts)>, Option<String>>,
89    },
90    /// Project workspace was specified using a `rust-project.json` file.
91    Json(ProjectJson),
92    // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
93    // That's not the end user experience we should strive for.
94    // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
95    // That needs some changes on the salsa-level though.
96    // 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).
97    // 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.
98    // 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.
99    // //
100    /// Project with a set of disjoint files, not belonging to any particular workspace.
101    /// Backed by basic sysroot crates for basic completion and highlighting.
102    DetachedFile {
103        /// The file in question.
104        file: ManifestPath,
105        /// Is this file a cargo script file?
106        cargo: Option<(CargoWorkspace, WorkspaceBuildScripts, Option<Arc<anyhow::Error>>)>,
107    },
108}
109
110impl fmt::Debug for ProjectWorkspace {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        // Make sure this isn't too verbose.
113        let Self {
114            kind,
115            sysroot,
116            rustc_cfg,
117            toolchain,
118            target_layout,
119            cfg_overrides,
120            extra_includes,
121            set_test,
122        } = self;
123        match kind {
124            ProjectWorkspaceKind::Cargo { cargo, error: _, build_scripts, rustc } => f
125                .debug_struct("Cargo")
126                .field("root", &cargo.workspace_root().file_name())
127                .field("n_packages", &cargo.packages().len())
128                .field("n_sysroot_crates", &sysroot.num_packages())
129                .field(
130                    "n_rustc_compiler_crates",
131                    &rustc.as_ref().map(|a| a.as_ref()).map_or(0, |(rc, _)| rc.packages().len()),
132                )
133                .field("n_rustc_cfg", &rustc_cfg.len())
134                .field("n_cfg_overrides", &cfg_overrides.len())
135                .field("n_extra_includes", &extra_includes.len())
136                .field("toolchain", &toolchain)
137                .field("data_layout", &target_layout)
138                .field("set_test", set_test)
139                .field("build_scripts", &build_scripts.error().unwrap_or("ok"))
140                .finish(),
141            ProjectWorkspaceKind::Json(project) => {
142                let mut debug_struct = f.debug_struct("Json");
143                debug_struct
144                    .field("n_crates", &project.n_crates())
145                    .field("n_sysroot_crates", &sysroot.num_packages())
146                    .field("n_rustc_cfg", &rustc_cfg.len())
147                    .field("toolchain", &toolchain)
148                    .field("data_layout", &target_layout)
149                    .field("n_cfg_overrides", &cfg_overrides.len())
150                    .field("n_extra_includes", &extra_includes.len())
151                    .field("set_test", set_test);
152
153                debug_struct.finish()
154            }
155            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script } => f
156                .debug_struct("DetachedFiles")
157                .field("file", &file)
158                .field("cargo_script", &cargo_script.is_some())
159                .field("n_sysroot_crates", &sysroot.num_packages())
160                .field("n_rustc_cfg", &rustc_cfg.len())
161                .field("toolchain", &toolchain)
162                .field("data_layout", &target_layout)
163                .field("n_cfg_overrides", &cfg_overrides.len())
164                .field("n_extra_includes", &extra_includes.len())
165                .field("set_test", set_test)
166                .finish(),
167        }
168    }
169}
170
171impl ProjectWorkspace {
172    pub fn load(
173        manifest: ProjectManifest,
174        config: &CargoConfig,
175        progress: &(dyn Fn(String) + Sync),
176    ) -> anyhow::Result<ProjectWorkspace> {
177        ProjectWorkspace::load_inner(&manifest, config, progress)
178            .with_context(|| format!("Failed to load the project at {manifest}"))
179    }
180
181    fn load_inner(
182        manifest: &ProjectManifest,
183        config: &CargoConfig,
184        progress: &(dyn Fn(String) + Sync),
185    ) -> anyhow::Result<ProjectWorkspace> {
186        let res = match manifest {
187            ProjectManifest::ProjectJson(project_json) => {
188                let file = fs::read_to_string(project_json)
189                    .with_context(|| format!("Failed to read json file {project_json}"))?;
190                let data = serde_json::from_str(&file)
191                    .with_context(|| format!("Failed to deserialize json file {project_json}"))?;
192                let project_location = project_json.parent().to_path_buf();
193                let project_json: ProjectJson =
194                    ProjectJson::new(Some(project_json.clone()), &project_location, data);
195                ProjectWorkspace::load_inline(project_json, config, progress)
196            }
197            ProjectManifest::CargoScript(rust_file) => {
198                ProjectWorkspace::load_detached_file(rust_file, config)?
199            }
200            ProjectManifest::CargoToml(cargo_toml) => {
201                ProjectWorkspace::load_cargo(cargo_toml, config, progress)?
202            }
203        };
204
205        Ok(res)
206    }
207
208    fn load_cargo(
209        cargo_toml: &ManifestPath,
210        config: &CargoConfig,
211        progress: &(dyn Fn(String) + Sync),
212    ) -> Result<ProjectWorkspace, anyhow::Error> {
213        progress("discovering sysroot".to_owned());
214        let CargoConfig {
215            features,
216            rustc_source,
217            extra_args,
218            extra_env,
219            set_test,
220            cfg_overrides,
221            extra_includes,
222            sysroot,
223            sysroot_src,
224            target,
225            no_deps,
226            ..
227        } = config;
228        let workspace_dir = cargo_toml.parent();
229        let mut sysroot = match (sysroot, sysroot_src) {
230            (Some(RustLibSource::Discover), None) => Sysroot::discover(workspace_dir, extra_env),
231            (Some(RustLibSource::Discover), Some(sysroot_src)) => {
232                Sysroot::discover_with_src_override(workspace_dir, extra_env, sysroot_src.clone())
233            }
234            (Some(RustLibSource::Path(path)), None) => {
235                Sysroot::discover_rust_lib_src_dir(path.clone())
236            }
237            (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
238                Sysroot::new(Some(sysroot.clone()), Some(sysroot_src.clone()))
239            }
240            (None, _) => Sysroot::empty(),
241        };
242
243        // Resolve the Cargo.toml to the workspace root as we base the `target` dir off of it.
244        let mut cmd = sysroot.tool(Tool::Cargo, workspace_dir, extra_env);
245        cmd.args(["locate-project", "--workspace", "--manifest-path", cargo_toml.as_str()]);
246        let cargo_toml = &match utf8_stdout(&mut cmd) {
247            Ok(output) => {
248                #[derive(serde_derive::Deserialize)]
249                struct Root {
250                    root: Utf8PathBuf,
251                }
252                match serde_json::from_str::<Root>(&output) {
253                    Ok(object) => ManifestPath::try_from(AbsPathBuf::assert(object.root))
254                        .expect("manifest path should be absolute"),
255                    Err(e) => {
256                        tracing::error!(%e, %cargo_toml, "failed fetching cargo workspace root");
257                        cargo_toml.clone()
258                    }
259                }
260            }
261            Err(e) => {
262                tracing::error!(%e, %cargo_toml, "failed fetching cargo workspace root");
263                cargo_toml.clone()
264            }
265        };
266        let workspace_dir = cargo_toml.parent();
267
268        tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.rust_lib_src_root(), root = ?sysroot.root(), "Using sysroot");
269        progress("querying project metadata".to_owned());
270        let config_file = cargo_config_file::read(cargo_toml, extra_env, &sysroot);
271        let config_file_ = config_file.clone();
272        let toolchain_config = QueryConfig::Cargo(&sysroot, cargo_toml, &config_file_);
273        let targets =
274            target_tuple::get(toolchain_config, target.as_deref(), extra_env).unwrap_or_default();
275        let toolchain = version::get(toolchain_config, extra_env)
276            .inspect_err(|e| {
277                tracing::error!(%e,
278                    "failed fetching toolchain version for {cargo_toml:?} workspace"
279                )
280            })
281            .ok()
282            .flatten();
283
284        let fetch_metadata = FetchMetadata::new(
285            cargo_toml,
286            workspace_dir,
287            &CargoMetadataConfig {
288                features: features.clone(),
289                targets: targets.clone(),
290                extra_args: extra_args.clone(),
291                extra_env: extra_env.clone(),
292                toolchain_version: toolchain.clone(),
293                kind: "workspace",
294            },
295            &sysroot,
296            *no_deps,
297        );
298        let target_dir = config
299            .target_dir
300            .clone()
301            .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone()))
302            .unwrap_or_else(|| workspace_dir.join("target").into());
303
304        // We spawn a bunch of processes to query various information about the workspace's
305        // toolchain and sysroot
306        // We can speed up loading a bit by spawning all of these processes in parallel (especially
307        // on systems were process spawning is delayed)
308        let join = thread::scope(|s| {
309            let rustc_cfg = s.spawn(|| {
310                rustc_cfg::get(toolchain_config, targets.first().map(Deref::deref), extra_env)
311            });
312            let data_layout = s.spawn(|| {
313                target_data_layout::get(
314                    toolchain_config,
315                    targets.first().map(Deref::deref),
316                    extra_env,
317                ).inspect_err(|e| {
318                    tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace")
319                })
320            });
321
322            let rustc_dir = s.spawn(|| {
323                let rustc_dir = match rustc_source {
324                    Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
325                        .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
326                    Some(RustLibSource::Discover) => {
327                        sysroot.discover_rustc_src().ok_or_else(|| {
328                            Some("Failed to discover rustc source for sysroot.".to_owned())
329                        })
330                    }
331                    None => Err(None),
332                };
333                rustc_dir.and_then(|rustc_dir| {
334                    info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
335                    match FetchMetadata::new(
336                        &rustc_dir,
337                        workspace_dir,
338                        &CargoMetadataConfig {
339                            features: crate::CargoFeatures::default(),
340                            targets: targets.clone(),
341                            extra_args: extra_args.clone(),
342                            extra_env: extra_env.clone(),
343                            toolchain_version: toolchain.clone(),
344                            kind: "rustc-dev"
345                        },
346                        &sysroot,
347                        *no_deps,
348                    ).exec(&target_dir, true, progress) {
349                        Ok((meta, _error)) => {
350                            let workspace = CargoWorkspace::new(
351                                meta,
352                                cargo_toml.clone(),
353                                Env::default(),
354                                false,
355                            );
356                            let build_scripts = WorkspaceBuildScripts::rustc_crates(
357                                &workspace,
358                                workspace_dir,
359                                extra_env,
360                                &sysroot,
361                            );
362                            Ok(Box::new((workspace, build_scripts)))
363                        }
364                        Err(e) => {
365                            tracing::error!(
366                                %e,
367                                "Failed to read Cargo metadata from rustc source at {rustc_dir}",
368                            );
369                            Err(Some(format!(
370                                "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}"
371                            )))
372                        }
373                    }
374                })
375            });
376
377            let cargo_metadata = s.spawn(|| fetch_metadata.exec(&target_dir, false, progress));
378            let loaded_sysroot = s.spawn(|| {
379                sysroot.load_workspace(
380                    &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
381                        config,
382                        &targets,
383                        toolchain.clone(),
384                    )),
385                    config.no_deps,
386                    workspace_dir,
387                    &target_dir,
388                    progress,
389                )
390            });
391            let cargo_config_extra_env =
392                s.spawn(move || cargo_config_env(cargo_toml, &config_file));
393            thread::Result::Ok((
394                rustc_cfg.join()?,
395                data_layout.join()?,
396                rustc_dir.join()?,
397                loaded_sysroot.join()?,
398                cargo_metadata.join()?,
399                cargo_config_extra_env.join()?,
400            ))
401        });
402
403        let (
404            rustc_cfg,
405            data_layout,
406            mut rustc,
407            loaded_sysroot,
408            cargo_metadata,
409            cargo_config_extra_env,
410        ) = match join {
411            Ok(it) => it,
412            Err(e) => std::panic::resume_unwind(e),
413        };
414
415        let (meta, error) = cargo_metadata.with_context(|| {
416            format!(
417                "Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
418            )
419        })?;
420        let cargo = CargoWorkspace::new(meta, cargo_toml.clone(), cargo_config_extra_env, false);
421        if let Some(loaded_sysroot) = loaded_sysroot {
422            tracing::info!(src_root = ?sysroot.rust_lib_src_root(), root = %loaded_sysroot, "Loaded sysroot");
423            sysroot.set_workspace(loaded_sysroot);
424        }
425
426        if !cargo.requires_rustc_private()
427            && let Err(e) = &mut rustc
428        {
429            // We don't need the rustc sources here,
430            // so just discard the error.
431            _ = e.take();
432        }
433
434        Ok(ProjectWorkspace {
435            kind: ProjectWorkspaceKind::Cargo {
436                cargo,
437                build_scripts: WorkspaceBuildScripts::default(),
438                rustc,
439                error: error.map(Arc::new),
440            },
441            sysroot,
442            rustc_cfg,
443            cfg_overrides: cfg_overrides.clone(),
444            toolchain,
445            target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
446            extra_includes: extra_includes.clone(),
447            set_test: *set_test,
448        })
449    }
450
451    pub fn load_inline(
452        mut project_json: ProjectJson,
453        config: &CargoConfig,
454        progress: &(dyn Fn(String) + Sync),
455    ) -> ProjectWorkspace {
456        progress("discovering sysroot".to_owned());
457        let mut sysroot =
458            Sysroot::new(project_json.sysroot.clone(), project_json.sysroot_src.clone());
459
460        tracing::info!(workspace = %project_json.manifest_or_root(), src_root = ?sysroot.rust_lib_src_root(), root = ?sysroot.root(), "Using sysroot");
461        progress("querying project metadata".to_owned());
462        let sysroot_project = project_json.sysroot_project.take();
463        let query_config = QueryConfig::Rustc(&sysroot, project_json.path().as_ref());
464        let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env)
465            .unwrap_or_default();
466        let toolchain = version::get(query_config, &config.extra_env).ok().flatten();
467        let project_root = project_json.project_root();
468        let target_dir = config
469            .target_dir
470            .clone()
471            .or_else(|| cargo_target_dir(project_json.manifest()?, &config.extra_env, &sysroot))
472            .unwrap_or_else(|| project_root.join("target").into());
473
474        // We spawn a bunch of processes to query various information about the workspace's
475        // toolchain and sysroot
476        // We can speed up loading a bit by spawning all of these processes in parallel (especially
477        // on systems were process spawning is delayed)
478        let join = thread::scope(|s| {
479            let rustc_cfg = s.spawn(|| {
480                rustc_cfg::get(query_config, targets.first().map(Deref::deref), &config.extra_env)
481            });
482            let data_layout = s.spawn(|| {
483                target_data_layout::get(
484                    query_config,
485                    targets.first().map(Deref::deref),
486                    &config.extra_env,
487                )
488            });
489            let loaded_sysroot = s.spawn(|| {
490                if let Some(sysroot_project) = sysroot_project {
491                    sysroot.load_workspace(
492                        &RustSourceWorkspaceConfig::Json(*sysroot_project),
493                        config.no_deps,
494                        project_root,
495                        &target_dir,
496                        progress,
497                    )
498                } else {
499                    sysroot.load_workspace(
500                        &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
501                            config,
502                            &targets,
503                            toolchain.clone(),
504                        )),
505                        config.no_deps,
506                        project_root,
507                        &target_dir,
508                        progress,
509                    )
510                }
511            });
512
513            thread::Result::Ok((rustc_cfg.join()?, data_layout.join()?, loaded_sysroot.join()?))
514        });
515
516        let (rustc_cfg, target_layout, loaded_sysroot) = match join {
517            Ok(it) => it,
518            Err(e) => std::panic::resume_unwind(e),
519        };
520
521        if let Some(loaded_sysroot) = loaded_sysroot {
522            sysroot.set_workspace(loaded_sysroot);
523        }
524
525        ProjectWorkspace {
526            kind: ProjectWorkspaceKind::Json(project_json),
527            sysroot,
528            rustc_cfg,
529            toolchain,
530            target_layout: target_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
531            cfg_overrides: config.cfg_overrides.clone(),
532            extra_includes: config.extra_includes.clone(),
533            set_test: config.set_test,
534        }
535    }
536
537    pub fn load_detached_file(
538        detached_file: &ManifestPath,
539        config: &CargoConfig,
540    ) -> anyhow::Result<ProjectWorkspace> {
541        let dir = detached_file.parent();
542        let mut sysroot = match &config.sysroot {
543            Some(RustLibSource::Path(path)) => Sysroot::discover_rust_lib_src_dir(path.clone()),
544            Some(RustLibSource::Discover) => Sysroot::discover(dir, &config.extra_env),
545            None => Sysroot::empty(),
546        };
547
548        let config_file = cargo_config_file::read(detached_file, &config.extra_env, &sysroot);
549        let query_config = QueryConfig::Cargo(&sysroot, detached_file, &config_file);
550        let toolchain = version::get(query_config, &config.extra_env).ok().flatten();
551        let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env)
552            .unwrap_or_default();
553        let rustc_cfg = rustc_cfg::get(query_config, None, &config.extra_env);
554        let data_layout = target_data_layout::get(query_config, None, &config.extra_env);
555        let target_dir = config
556            .target_dir
557            .clone()
558            .or_else(|| cargo_target_dir(detached_file, &config.extra_env, &sysroot))
559            .unwrap_or_else(|| dir.join("target").into());
560
561        let loaded_sysroot = sysroot.load_workspace(
562            &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config(
563                config,
564                &targets,
565                toolchain.clone(),
566            )),
567            config.no_deps,
568            dir,
569            &target_dir,
570            &|_| (),
571        );
572        if let Some(loaded_sysroot) = loaded_sysroot {
573            sysroot.set_workspace(loaded_sysroot);
574        }
575
576        let fetch_metadata = FetchMetadata::new(
577            detached_file,
578            dir,
579            &CargoMetadataConfig {
580                features: config.features.clone(),
581                targets,
582                extra_args: config.extra_args.clone(),
583                extra_env: config.extra_env.clone(),
584                toolchain_version: toolchain.clone(),
585                kind: "detached-file",
586            },
587            &sysroot,
588            config.no_deps,
589        );
590        let target_dir = config
591            .target_dir
592            .clone()
593            .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone()))
594            .unwrap_or_else(|| dir.join("target").into());
595        let cargo_script =
596            fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| {
597                let cargo_config_extra_env = cargo_config_env(detached_file, &config_file);
598                (
599                    CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false),
600                    WorkspaceBuildScripts::default(),
601                    error.map(Arc::new),
602                )
603            });
604
605        Ok(ProjectWorkspace {
606            kind: ProjectWorkspaceKind::DetachedFile {
607                file: detached_file.to_owned(),
608                cargo: cargo_script,
609            },
610            sysroot,
611            rustc_cfg,
612            toolchain,
613            target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
614            cfg_overrides: config.cfg_overrides.clone(),
615            extra_includes: config.extra_includes.clone(),
616            set_test: config.set_test,
617        })
618    }
619
620    pub fn load_detached_files(
621        detached_files: Vec<ManifestPath>,
622        config: &CargoConfig,
623    ) -> Vec<anyhow::Result<ProjectWorkspace>> {
624        detached_files.into_iter().map(|file| Self::load_detached_file(&file, config)).collect()
625    }
626
627    /// Runs the build scripts for this [`ProjectWorkspace`].
628    pub fn run_build_scripts(
629        &self,
630        config: &CargoConfig,
631        progress: &dyn Fn(String),
632    ) -> anyhow::Result<WorkspaceBuildScripts> {
633        match &self.kind {
634            ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, None)), .. }
635            | ProjectWorkspaceKind::Cargo { cargo, error: None, .. } => {
636                WorkspaceBuildScripts::run_for_workspace(
637                    config,
638                    cargo,
639                    progress,
640                    &self.sysroot,
641                    self.toolchain.as_ref(),
642                )
643                .with_context(|| {
644                    format!("Failed to run build scripts for {}", cargo.workspace_root())
645                })
646            }
647            _ => Ok(WorkspaceBuildScripts::default()),
648        }
649    }
650
651    /// Runs the build scripts for the given [`ProjectWorkspace`]s. Depending on the invocation
652    /// strategy this may run a single build process for all project workspaces.
653    pub fn run_all_build_scripts(
654        workspaces: &[ProjectWorkspace],
655        config: &CargoConfig,
656        progress: &dyn Fn(String),
657        working_directory: &AbsPathBuf,
658    ) -> Vec<anyhow::Result<WorkspaceBuildScripts>> {
659        if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace)
660            || config.run_build_script_command.is_none()
661        {
662            return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect();
663        }
664
665        let cargo_ws: Vec<_> = workspaces
666            .iter()
667            .filter_map(|it| match &it.kind {
668                ProjectWorkspaceKind::Cargo { cargo, .. } => Some(cargo),
669                _ => None,
670            })
671            .collect();
672        let outputs = &mut match WorkspaceBuildScripts::run_once(
673            config,
674            &cargo_ws,
675            progress,
676            working_directory,
677        ) {
678            Ok(it) => Ok(it.into_iter()),
679            // io::Error is not Clone?
680            Err(e) => Err(sync::Arc::new(e)),
681        };
682
683        workspaces
684            .iter()
685            .map(|it| match &it.kind {
686                ProjectWorkspaceKind::Cargo { cargo, .. } => match outputs {
687                    Ok(outputs) => Ok(outputs.next().unwrap()),
688                    Err(e) => Err(e.clone()).with_context(|| {
689                        format!("Failed to run build scripts for {}", cargo.workspace_root())
690                    }),
691                },
692                _ => Ok(WorkspaceBuildScripts::default()),
693            })
694            .collect()
695    }
696
697    pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
698        match &mut self.kind {
699            ProjectWorkspaceKind::Cargo { build_scripts, .. }
700            | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts, _)), .. } => {
701                *build_scripts = bs
702            }
703            _ => assert_eq!(bs, WorkspaceBuildScripts::default()),
704        }
705    }
706
707    pub fn manifest_or_root(&self) -> &AbsPath {
708        match &self.kind {
709            ProjectWorkspaceKind::Cargo { cargo, .. } => cargo.manifest_path(),
710            ProjectWorkspaceKind::Json(project) => project.manifest_or_root(),
711            ProjectWorkspaceKind::DetachedFile { file, .. } => file,
712        }
713    }
714
715    pub fn workspace_root(&self) -> &AbsPath {
716        match &self.kind {
717            ProjectWorkspaceKind::Cargo { cargo, .. } => cargo.workspace_root(),
718            ProjectWorkspaceKind::Json(project) => project.project_root(),
719            ProjectWorkspaceKind::DetachedFile { file, .. } => file.parent(),
720        }
721    }
722
723    pub fn manifest(&self) -> Option<&ManifestPath> {
724        match &self.kind {
725            ProjectWorkspaceKind::Cargo { cargo, .. } => Some(cargo.manifest_path()),
726            ProjectWorkspaceKind::Json(project) => project.manifest(),
727            ProjectWorkspaceKind::DetachedFile { cargo, .. } => {
728                Some(cargo.as_ref()?.0.manifest_path())
729            }
730        }
731    }
732
733    pub fn buildfiles(&self) -> Vec<AbsPathBuf> {
734        match &self.kind {
735            ProjectWorkspaceKind::Json(project) => project
736                .crates()
737                .filter_map(|(_, krate)| krate.build.as_ref().map(|build| build.build_file.clone()))
738                .map(|build_file| self.workspace_root().join(build_file))
739                .collect(),
740            _ => vec![],
741        }
742    }
743
744    pub fn find_sysroot_proc_macro_srv(&self) -> Option<anyhow::Result<AbsPathBuf>> {
745        self.sysroot.discover_proc_macro_srv()
746    }
747
748    /// Returns the roots for the current `ProjectWorkspace`
749    /// The return type contains the path and whether or not
750    /// the root is a member of the current workspace
751    pub fn to_roots(&self) -> Vec<PackageRoot> {
752        let mk_sysroot = || {
753            let mut r = match self.sysroot.workspace() {
754                RustLibSrcWorkspace::Workspace(ws) => ws
755                    .packages()
756                    .filter_map(|pkg| {
757                        if ws[pkg].is_local {
758                            // the local ones are included in the main `PackageRoot`` below
759                            return None;
760                        }
761                        let pkg_root = ws[pkg].manifest.parent().to_path_buf();
762
763                        let include = vec![pkg_root.clone()];
764
765                        let exclude = vec![
766                            pkg_root.join(".git"),
767                            pkg_root.join("target"),
768                            pkg_root.join("tests"),
769                            pkg_root.join("examples"),
770                            pkg_root.join("benches"),
771                        ];
772                        Some(PackageRoot { is_local: false, include, exclude })
773                    })
774                    .collect(),
775                RustLibSrcWorkspace::Json(project_json) => project_json
776                    .crates()
777                    .map(|(_, krate)| PackageRoot {
778                        is_local: false,
779                        include: krate.include.clone(),
780                        exclude: krate.exclude.clone(),
781                    })
782                    .collect(),
783                RustLibSrcWorkspace::Stitched(_) => vec![],
784                RustLibSrcWorkspace::Empty => vec![],
785            };
786
787            r.push(PackageRoot {
788                is_local: false,
789                include: self
790                    .sysroot
791                    .rust_lib_src_root()
792                    .map(|it| it.to_path_buf())
793                    .into_iter()
794                    .collect(),
795                exclude: Vec::new(),
796            });
797            r
798        };
799        match &self.kind {
800            ProjectWorkspaceKind::Json(project) => project
801                .crates()
802                .map(|(_, krate)| PackageRoot {
803                    is_local: krate.is_workspace_member,
804                    include: krate
805                        .include
806                        .iter()
807                        .cloned()
808                        .chain(self.extra_includes.iter().cloned())
809                        .collect(),
810                    exclude: krate.exclude.clone(),
811                })
812                .collect::<FxHashSet<_>>()
813                .into_iter()
814                .chain(mk_sysroot())
815                .collect::<Vec<_>>(),
816            ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _ } => {
817                cargo
818                    .packages()
819                    .map(|pkg| {
820                        let is_local = cargo[pkg].is_local;
821                        let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
822
823                        let mut include = vec![pkg_root.clone()];
824                        let out_dir =
825                            build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
826                        include.extend(out_dir);
827
828                        // In case target's path is manually set in Cargo.toml to be
829                        // outside the package root, add its parent as an extra include.
830                        // An example of this situation would look like this:
831                        //
832                        // ```toml
833                        // [lib]
834                        // path = "../../src/lib.rs"
835                        // ```
836                        let extra_targets = cargo[pkg]
837                            .targets
838                            .iter()
839                            .filter(|&&tgt| matches!(cargo[tgt].kind, TargetKind::Lib { .. }))
840                            .filter_map(|&tgt| cargo[tgt].root.parent())
841                            .map(|tgt| tgt.normalize().to_path_buf())
842                            .filter(|path| !path.starts_with(&pkg_root));
843                        include.extend(extra_targets);
844
845                        let mut exclude = vec![pkg_root.join(".git")];
846                        if is_local {
847                            include.extend(self.extra_includes.iter().cloned());
848
849                            exclude.push(pkg_root.join("target"));
850                        } else {
851                            exclude.push(pkg_root.join("tests"));
852                            exclude.push(pkg_root.join("examples"));
853                            exclude.push(pkg_root.join("benches"));
854                        }
855                        PackageRoot { is_local, include, exclude }
856                    })
857                    .chain(mk_sysroot())
858                    .chain(rustc.iter().map(|a| a.as_ref()).flat_map(|(rustc, _)| {
859                        rustc.packages().map(move |krate| PackageRoot {
860                            is_local: false,
861                            include: vec![rustc[krate].manifest.parent().to_path_buf()],
862                            exclude: Vec::new(),
863                        })
864                    }))
865                    .collect()
866            }
867            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => {
868                iter::once(PackageRoot {
869                    is_local: true,
870                    include: vec![file.to_path_buf()],
871                    exclude: Vec::new(),
872                })
873                .chain(cargo_script.iter().flat_map(|(cargo, build_scripts, _)| {
874                    cargo.packages().map(|pkg| {
875                        let is_local = cargo[pkg].is_local;
876                        let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
877
878                        let mut include = vec![pkg_root.clone()];
879                        let out_dir =
880                            build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
881                        include.extend(out_dir);
882
883                        // In case target's path is manually set in Cargo.toml to be
884                        // outside the package root, add its parent as an extra include.
885                        // An example of this situation would look like this:
886                        //
887                        // ```toml
888                        // [lib]
889                        // path = "../../src/lib.rs"
890                        // ```
891                        let extra_targets = cargo[pkg]
892                            .targets
893                            .iter()
894                            .filter(|&&tgt| matches!(cargo[tgt].kind, TargetKind::Lib { .. }))
895                            .filter_map(|&tgt| cargo[tgt].root.parent())
896                            .map(|tgt| tgt.normalize().to_path_buf())
897                            .filter(|path| !path.starts_with(&pkg_root));
898                        include.extend(extra_targets);
899
900                        let mut exclude = vec![pkg_root.join(".git")];
901                        if is_local {
902                            include.extend(self.extra_includes.iter().cloned());
903
904                            exclude.push(pkg_root.join("target"));
905                        } else {
906                            exclude.push(pkg_root.join("tests"));
907                            exclude.push(pkg_root.join("examples"));
908                            exclude.push(pkg_root.join("benches"));
909                        }
910                        PackageRoot { is_local, include, exclude }
911                    })
912                }))
913                .chain(mk_sysroot())
914                .collect()
915            }
916        }
917    }
918
919    pub fn n_packages(&self) -> usize {
920        let sysroot_package_len = self.sysroot.num_packages();
921        match &self.kind {
922            ProjectWorkspaceKind::Json(project) => sysroot_package_len + project.n_crates(),
923            ProjectWorkspaceKind::Cargo { cargo, rustc, .. } => {
924                let rustc_package_len =
925                    rustc.as_ref().map(|a| a.as_ref()).map_or(0, |(it, _)| it.packages().len());
926                cargo.packages().len() + sysroot_package_len + rustc_package_len
927            }
928            ProjectWorkspaceKind::DetachedFile { cargo: cargo_script, .. } => {
929                sysroot_package_len
930                    + cargo_script.as_ref().map_or(1, |(cargo, _, _)| cargo.packages().len())
931            }
932        }
933    }
934
935    pub fn to_crate_graph(
936        &self,
937        load: FileLoader<'_>,
938        extra_env: &FxHashMap<String, Option<String>>,
939    ) -> (CrateGraphBuilder, ProcMacroPaths) {
940        let _p = tracing::info_span!("ProjectWorkspace::to_crate_graph").entered();
941
942        let Self { kind, sysroot, cfg_overrides, rustc_cfg, .. } = self;
943        let crate_ws_data = Arc::new(CrateWorkspaceData {
944            toolchain: self.toolchain.clone(),
945            data_layout: self.target_layout.clone(),
946        });
947        let (crate_graph, proc_macros) = match kind {
948            ProjectWorkspaceKind::Json(project) => project_json_to_crate_graph(
949                rustc_cfg.clone(),
950                load,
951                project,
952                sysroot,
953                extra_env,
954                cfg_overrides,
955                self.set_test,
956                false,
957                crate_ws_data,
958            ),
959            ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts, error: _ } => {
960                cargo_to_crate_graph(
961                    load,
962                    rustc.as_ref().map(|a| a.as_ref()).ok(),
963                    cargo,
964                    sysroot,
965                    rustc_cfg.clone(),
966                    cfg_overrides,
967                    build_scripts,
968                    self.set_test,
969                    crate_ws_data,
970                )
971            }
972            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => {
973                if let Some((cargo, build_scripts, _)) = cargo_script {
974                    cargo_to_crate_graph(
975                        &mut |path| load(path),
976                        None,
977                        cargo,
978                        sysroot,
979                        rustc_cfg.clone(),
980                        cfg_overrides,
981                        build_scripts,
982                        self.set_test,
983                        crate_ws_data,
984                    )
985                } else {
986                    detached_file_to_crate_graph(
987                        rustc_cfg.clone(),
988                        load,
989                        file,
990                        sysroot,
991                        cfg_overrides,
992                        self.set_test,
993                        crate_ws_data,
994                    )
995                }
996            }
997        };
998
999        (crate_graph, proc_macros)
1000    }
1001
1002    pub fn eq_ignore_build_data(&self, other: &Self) -> bool {
1003        let Self { kind, sysroot, rustc_cfg, toolchain, target_layout, cfg_overrides, .. } = self;
1004        let Self {
1005            kind: o_kind,
1006            sysroot: o_sysroot,
1007            rustc_cfg: o_rustc_cfg,
1008            toolchain: o_toolchain,
1009            target_layout: o_target_layout,
1010            cfg_overrides: o_cfg_overrides,
1011            ..
1012        } = other;
1013        (match (kind, o_kind) {
1014            (
1015                ProjectWorkspaceKind::Cargo { cargo, rustc, build_scripts: _, error: _ },
1016                ProjectWorkspaceKind::Cargo {
1017                    cargo: o_cargo,
1018                    rustc: o_rustc,
1019                    build_scripts: _,
1020                    error: _,
1021                },
1022            ) => cargo == o_cargo && rustc == o_rustc,
1023            (ProjectWorkspaceKind::Json(project), ProjectWorkspaceKind::Json(o_project)) => {
1024                project == o_project
1025            }
1026            (
1027                ProjectWorkspaceKind::DetachedFile { file, cargo: Some((cargo_script, _, _)) },
1028                ProjectWorkspaceKind::DetachedFile {
1029                    file: o_file,
1030                    cargo: Some((o_cargo_script, _, _)),
1031                },
1032            ) => file == o_file && cargo_script == o_cargo_script,
1033            _ => return false,
1034        }) && sysroot == o_sysroot
1035            && rustc_cfg == o_rustc_cfg
1036            && toolchain == o_toolchain
1037            && target_layout == o_target_layout
1038            && cfg_overrides == o_cfg_overrides
1039    }
1040
1041    /// Returns `true` if the project workspace is [`Json`].
1042    ///
1043    /// [`Json`]: ProjectWorkspace::Json
1044    #[must_use]
1045    pub fn is_json(&self) -> bool {
1046        matches!(self.kind, ProjectWorkspaceKind::Json { .. })
1047    }
1048}
1049
1050#[instrument(skip_all)]
1051fn project_json_to_crate_graph(
1052    rustc_cfg: Vec<CfgAtom>,
1053    load: FileLoader<'_>,
1054    project: &ProjectJson,
1055    sysroot: &Sysroot,
1056    extra_env: &FxHashMap<String, Option<String>>,
1057    override_cfg: &CfgOverrides,
1058    set_test: bool,
1059    is_sysroot: bool,
1060    crate_ws_data: Arc<CrateWorkspaceData>,
1061) -> (CrateGraphBuilder, ProcMacroPaths) {
1062    let mut res = (CrateGraphBuilder::default(), ProcMacroPaths::default());
1063    let (crate_graph, proc_macros) = &mut res;
1064    let (public_deps, libproc_macro) = sysroot_to_crate_graph(
1065        crate_graph,
1066        sysroot,
1067        rustc_cfg.clone(),
1068        load,
1069        // FIXME: This looks incorrect but I don't think this matters.
1070        crate_ws_data.clone(),
1071    );
1072
1073    let mut cfg_cache: FxHashMap<&str, Vec<CfgAtom>> = FxHashMap::default();
1074    let project_root = Arc::new(project.project_root().to_path_buf());
1075
1076    let idx_to_crate_id: FxHashMap<CrateArrayIdx, _> = project
1077        .crates()
1078        .filter_map(|(idx, krate)| Some((idx, krate, load(&krate.root_module)?)))
1079        .map(
1080            |(
1081                idx,
1082                Crate {
1083                    display_name,
1084                    edition,
1085                    version,
1086                    cfg,
1087                    target,
1088                    env,
1089                    proc_macro_dylib_path,
1090                    is_proc_macro,
1091                    repository,
1092                    is_workspace_member,
1093                    proc_macro_cwd,
1094                    ..
1095                },
1096                file_id,
1097            )| {
1098                let env = env.clone().into_iter().collect();
1099
1100                let target_cfgs = match target.as_deref() {
1101                    Some(target) => cfg_cache.entry(target).or_insert_with(|| {
1102                        rustc_cfg::get(
1103                            QueryConfig::Rustc(sysroot, project.project_root().as_ref()),
1104                            Some(target),
1105                            extra_env,
1106                        )
1107                    }),
1108                    None => &rustc_cfg,
1109                };
1110
1111                let cfg_options = {
1112                    let mut cfg_options: CfgOptions =
1113                        target_cfgs.iter().chain(cfg.iter()).cloned().collect();
1114
1115                    if *is_workspace_member {
1116                        if set_test && !is_sysroot {
1117                            // Add test cfg for local crates
1118                            cfg_options.insert_atom(sym::test);
1119                        }
1120                        cfg_options.insert_atom(sym::rust_analyzer);
1121                    }
1122
1123                    override_cfg.apply(
1124                        &mut cfg_options,
1125                        display_name
1126                            .as_ref()
1127                            .map(|it| it.canonical_name().as_str())
1128                            .unwrap_or_default(),
1129                    );
1130                    cfg_options
1131                };
1132
1133                let crate_graph_crate_id = crate_graph.add_crate_root(
1134                    file_id,
1135                    *edition,
1136                    display_name.clone(),
1137                    version.clone(),
1138                    cfg_options,
1139                    None,
1140                    env,
1141                    if let Some(name) = display_name.clone() {
1142                        if is_sysroot {
1143                            CrateOrigin::Lang(LangCrateOrigin::from(name.canonical_name().as_str()))
1144                        } else {
1145                            CrateOrigin::Local {
1146                                repo: repository.clone(),
1147                                name: Some(name.canonical_name().to_owned()),
1148                            }
1149                        }
1150                    } else {
1151                        CrateOrigin::Local { repo: None, name: None }
1152                    },
1153                    *is_proc_macro,
1154                    match proc_macro_cwd {
1155                        Some(path) => Arc::new(path.clone()),
1156                        None => project_root.clone(),
1157                    },
1158                    crate_ws_data.clone(),
1159                );
1160                debug!(
1161                    ?crate_graph_crate_id,
1162                    crate = display_name.as_ref().map(|name| name.canonical_name().as_str()),
1163                    "added root to crate graph"
1164                );
1165                if *is_proc_macro && let Some(path) = proc_macro_dylib_path.clone() {
1166                    let node = Ok((
1167                        display_name
1168                            .as_ref()
1169                            .map(|it| it.canonical_name().as_str().to_owned())
1170                            .unwrap_or_else(|| format!("crate{}", idx.0)),
1171                        path,
1172                    ));
1173                    proc_macros.insert(crate_graph_crate_id, node);
1174                }
1175                (idx, crate_graph_crate_id)
1176            },
1177        )
1178        .collect();
1179
1180    debug!(map = ?idx_to_crate_id);
1181    for (from_idx, krate) in project.crates() {
1182        if let Some(&from) = idx_to_crate_id.get(&from_idx) {
1183            public_deps.add_to_crate_graph(crate_graph, from);
1184            if let Some(proc_macro) = libproc_macro {
1185                add_proc_macro_dep(crate_graph, from, proc_macro, krate.is_proc_macro);
1186            }
1187
1188            for dep in &krate.deps {
1189                if let Some(&to) = idx_to_crate_id.get(&dep.krate) {
1190                    add_dep(crate_graph, from, dep.name.clone(), to);
1191                }
1192            }
1193        }
1194    }
1195    res
1196}
1197
1198fn cargo_to_crate_graph(
1199    load: FileLoader<'_>,
1200    rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
1201    cargo: &CargoWorkspace,
1202    sysroot: &Sysroot,
1203    rustc_cfg: Vec<CfgAtom>,
1204    override_cfg: &CfgOverrides,
1205    build_scripts: &WorkspaceBuildScripts,
1206    set_test: bool,
1207    crate_ws_data: Arc<CrateWorkspaceData>,
1208) -> (CrateGraphBuilder, ProcMacroPaths) {
1209    let _p = tracing::info_span!("cargo_to_crate_graph").entered();
1210    let mut res = (CrateGraphBuilder::default(), ProcMacroPaths::default());
1211    let (crate_graph, proc_macros) = &mut res;
1212    let (public_deps, libproc_macro) = sysroot_to_crate_graph(
1213        crate_graph,
1214        sysroot,
1215        rustc_cfg.clone(),
1216        load,
1217        crate_ws_data.clone(),
1218    );
1219
1220    let cfg_options = CfgOptions::from_iter(rustc_cfg);
1221
1222    // Mapping of a package to its library target
1223    let mut pkg_to_lib_crate = FxHashMap::default();
1224    let mut pkg_crates = FxHashMap::default();
1225    let workspace_proc_macro_cwd = Arc::new(cargo.workspace_root().to_path_buf());
1226
1227    // Next, create crates for each package, target pair
1228    for pkg in cargo.packages() {
1229        let cfg_options = {
1230            let mut cfg_options = cfg_options.clone();
1231
1232            if cargo[pkg].is_local {
1233                if set_test && !cargo.is_sysroot() {
1234                    // Add test cfg for local crates
1235                    cfg_options.insert_atom(sym::test);
1236                }
1237                cfg_options.insert_atom(sym::rust_analyzer);
1238            }
1239
1240            override_cfg.apply(&mut cfg_options, &cargo[pkg].name);
1241            cfg_options
1242        };
1243
1244        let mut lib_tgt = None;
1245        for &tgt in cargo[pkg].targets.iter() {
1246            let pkg_data = &cargo[pkg];
1247            if !matches!(cargo[tgt].kind, TargetKind::Lib { .. })
1248                && (!pkg_data.is_member || cargo.is_sysroot())
1249            {
1250                // For non-workspace-members, Cargo does not resolve dev-dependencies, so we don't
1251                // add any targets except the library target, since those will not work correctly if
1252                // they use dev-dependencies.
1253                // In fact, they can break quite badly if multiple client workspaces get merged:
1254                // https://github.com/rust-lang/rust-analyzer/issues/11300
1255                continue;
1256            }
1257            let &TargetData { ref name, kind, ref root, .. } = &cargo[tgt];
1258
1259            let Some(file_id) = load(root) else { continue };
1260
1261            let build_data = build_scripts.get_output(pkg);
1262            let crate_id = add_target_crate_root(
1263                crate_graph,
1264                proc_macros,
1265                cargo,
1266                pkg_data,
1267                build_data.zip(Some(build_scripts.error().is_some())),
1268                cfg_options.clone(),
1269                file_id,
1270                name,
1271                kind,
1272                if pkg_data.is_local {
1273                    if cargo.is_sysroot() {
1274                        CrateOrigin::Lang(LangCrateOrigin::from(&*pkg_data.name))
1275                    } else {
1276                        CrateOrigin::Local {
1277                            repo: pkg_data.repository.clone(),
1278                            name: Some(Symbol::intern(&pkg_data.name)),
1279                        }
1280                    }
1281                } else {
1282                    CrateOrigin::Library {
1283                        repo: pkg_data.repository.clone(),
1284                        name: Symbol::intern(&pkg_data.name),
1285                    }
1286                },
1287                crate_ws_data.clone(),
1288                if pkg_data.is_member {
1289                    workspace_proc_macro_cwd.clone()
1290                } else {
1291                    Arc::new(pkg_data.manifest.parent().to_path_buf())
1292                },
1293            );
1294            if let TargetKind::Lib { .. } = kind {
1295                lib_tgt = Some((crate_id, name.clone()));
1296                pkg_to_lib_crate.insert(pkg, crate_id);
1297            }
1298            // Even crates that don't set proc-macro = true are allowed to depend on proc_macro
1299            // (just none of the APIs work when called outside of a proc macro).
1300            if let Some(proc_macro) = libproc_macro {
1301                add_proc_macro_dep(
1302                    crate_graph,
1303                    crate_id,
1304                    proc_macro,
1305                    matches!(kind, TargetKind::Lib { is_proc_macro: true }),
1306                );
1307            }
1308
1309            pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
1310        }
1311
1312        // Set deps to the core, std and to the lib target of the current package
1313        for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
1314            // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
1315            public_deps.add_to_crate_graph(crate_graph, from);
1316
1317            // Add dep edge of all targets to the package's lib target
1318            if let Some((to, name)) = lib_tgt.clone()
1319                && to != from
1320                && kind != TargetKind::BuildScript
1321            {
1322                // (build script can not depend on its library target)
1323
1324                // For root projects with dashes in their name,
1325                // cargo metadata does not do any normalization,
1326                // so we do it ourselves currently
1327                let name = CrateName::normalize_dashes(&name);
1328                add_dep(crate_graph, from, name, to);
1329            }
1330        }
1331    }
1332
1333    let mut delayed_dev_deps = vec![];
1334
1335    // Now add a dep edge from all targets of upstream to the lib
1336    // target of downstream.
1337    for pkg in cargo.packages() {
1338        for dep in &cargo[pkg].dependencies {
1339            let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) else { continue };
1340            let Some(targets) = pkg_crates.get(&pkg) else { continue };
1341
1342            let name = CrateName::new(&dep.name).unwrap();
1343            for &(from, kind) in targets {
1344                // Build scripts may only depend on build dependencies.
1345                if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
1346                    continue;
1347                }
1348
1349                // If the dependency is a dev-dependency with both crates being member libraries of
1350                // the workspace we delay adding the edge. The reason can be read up on in
1351                // https://github.com/rust-lang/rust-analyzer/issues/14167
1352                // but in short, such an edge is able to cause some form of cycle in the crate graph
1353                // for normal dependencies. If we do run into a cycle like this, we want to prefer
1354                // the non dev-dependency edge, and so the easiest way to do that is by adding the
1355                // dev-dependency edges last.
1356                if dep.kind == DepKind::Dev
1357                    && matches!(kind, TargetKind::Lib { .. })
1358                    && cargo[dep.pkg].is_member
1359                    && cargo[pkg].is_member
1360                {
1361                    delayed_dev_deps.push((from, name.clone(), to));
1362                    continue;
1363                }
1364
1365                add_dep(crate_graph, from, name.clone(), to)
1366            }
1367        }
1368    }
1369
1370    for (from, name, to) in delayed_dev_deps {
1371        add_dep(crate_graph, from, name, to);
1372    }
1373
1374    if cargo.requires_rustc_private() {
1375        // If the user provided a path to rustc sources, we add all the rustc_private crates
1376        // and create dependencies on them for the crates which opt-in to that
1377        if let Some((rustc_workspace, rustc_build_scripts)) = rustc {
1378            handle_rustc_crates(
1379                crate_graph,
1380                proc_macros,
1381                &mut pkg_to_lib_crate,
1382                load,
1383                rustc_workspace,
1384                cargo,
1385                &public_deps,
1386                libproc_macro,
1387                &pkg_crates,
1388                &cfg_options,
1389                override_cfg,
1390                // FIXME: Remove this once rustc switched over to rust-project.json
1391                if rustc_workspace.workspace_root() == cargo.workspace_root() {
1392                    // the rustc workspace does not use the installed toolchain's proc-macro server
1393                    // so we need to make sure we don't use the pre compiled proc-macros there either
1394                    build_scripts
1395                } else {
1396                    rustc_build_scripts
1397                },
1398                // FIXME: This looks incorrect but I don't think this causes problems.
1399                crate_ws_data,
1400            );
1401        }
1402    }
1403    res
1404}
1405
1406fn detached_file_to_crate_graph(
1407    rustc_cfg: Vec<CfgAtom>,
1408    load: FileLoader<'_>,
1409    detached_file: &ManifestPath,
1410    sysroot: &Sysroot,
1411    override_cfg: &CfgOverrides,
1412    set_test: bool,
1413    crate_ws_data: Arc<CrateWorkspaceData>,
1414) -> (CrateGraphBuilder, ProcMacroPaths) {
1415    let _p = tracing::info_span!("detached_file_to_crate_graph").entered();
1416    let mut crate_graph = CrateGraphBuilder::default();
1417    let (public_deps, _libproc_macro) = sysroot_to_crate_graph(
1418        &mut crate_graph,
1419        sysroot,
1420        rustc_cfg.clone(),
1421        load,
1422        // FIXME: This looks incorrect but I don't think this causes problems.
1423        crate_ws_data.clone(),
1424    );
1425
1426    let mut cfg_options = CfgOptions::from_iter(rustc_cfg);
1427    if set_test {
1428        cfg_options.insert_atom(sym::test);
1429    }
1430    cfg_options.insert_atom(sym::rust_analyzer);
1431    override_cfg.apply(&mut cfg_options, "");
1432    let cfg_options = cfg_options;
1433
1434    let file_id = match load(detached_file) {
1435        Some(file_id) => file_id,
1436        None => {
1437            error!("Failed to load detached file {:?}", detached_file);
1438            return (crate_graph, FxHashMap::default());
1439        }
1440    };
1441    let display_name = detached_file.file_stem().map(CrateDisplayName::from_canonical_name);
1442    let detached_file_crate = crate_graph.add_crate_root(
1443        file_id,
1444        Edition::CURRENT,
1445        display_name.clone(),
1446        None,
1447        cfg_options,
1448        None,
1449        Env::default(),
1450        CrateOrigin::Local {
1451            repo: None,
1452            name: display_name.map(|n| n.canonical_name().to_owned()),
1453        },
1454        false,
1455        Arc::new(detached_file.parent().to_path_buf()),
1456        crate_ws_data,
1457    );
1458
1459    public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate);
1460    (crate_graph, FxHashMap::default())
1461}
1462
1463// FIXME: There shouldn't really be a need for duplicating all of this?
1464fn handle_rustc_crates(
1465    crate_graph: &mut CrateGraphBuilder,
1466    proc_macros: &mut ProcMacroPaths,
1467    pkg_to_lib_crate: &mut FxHashMap<Package, CrateBuilderId>,
1468    load: FileLoader<'_>,
1469    rustc_workspace: &CargoWorkspace,
1470    cargo: &CargoWorkspace,
1471    public_deps: &SysrootPublicDeps,
1472    libproc_macro: Option<CrateBuilderId>,
1473    pkg_crates: &FxHashMap<Package, Vec<(CrateBuilderId, TargetKind)>>,
1474    cfg_options: &CfgOptions,
1475    override_cfg: &CfgOverrides,
1476    build_scripts: &WorkspaceBuildScripts,
1477    crate_ws_data: Arc<CrateWorkspaceData>,
1478) {
1479    let mut rustc_pkg_crates = FxHashMap::default();
1480    // The root package of the rustc-dev component is rustc_driver, so we match that
1481    let root_pkg =
1482        rustc_workspace.packages().find(|&package| rustc_workspace[package].name == "rustc_driver");
1483    let workspace_proc_macro_cwd = Arc::new(cargo.workspace_root().to_path_buf());
1484    // The rustc workspace might be incomplete (such as if rustc-dev is not
1485    // installed for the current toolchain) and `rustc_source` is set to discover.
1486    if let Some(root_pkg) = root_pkg {
1487        // Iterate through every crate in the dependency subtree of rustc_driver using BFS
1488        let mut queue = VecDeque::new();
1489        queue.push_back(root_pkg);
1490        while let Some(pkg) = queue.pop_front() {
1491            // Don't duplicate packages if they are dependent on a diamond pattern
1492            // N.B. if this line is omitted, we try to analyze over 4_800_000 crates
1493            // which is not ideal
1494            if rustc_pkg_crates.contains_key(&pkg) {
1495                continue;
1496            }
1497            let pkg_data = &rustc_workspace[pkg];
1498            for dep in &pkg_data.dependencies {
1499                queue.push_back(dep.pkg);
1500            }
1501
1502            let mut cfg_options = cfg_options.clone();
1503            override_cfg.apply(&mut cfg_options, &pkg_data.name);
1504
1505            for &tgt in pkg_data.targets.iter() {
1506                let kind @ TargetKind::Lib { is_proc_macro } = rustc_workspace[tgt].kind else {
1507                    continue;
1508                };
1509                let pkg_crates = &mut rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new);
1510                if let Some(file_id) = load(&rustc_workspace[tgt].root) {
1511                    let crate_id = add_target_crate_root(
1512                        crate_graph,
1513                        proc_macros,
1514                        rustc_workspace,
1515                        pkg_data,
1516                        build_scripts.get_output(pkg).zip(Some(build_scripts.error().is_some())),
1517                        cfg_options.clone(),
1518                        file_id,
1519                        &rustc_workspace[tgt].name,
1520                        kind,
1521                        CrateOrigin::Rustc { name: Symbol::intern(&pkg_data.name) },
1522                        crate_ws_data.clone(),
1523                        if pkg_data.is_member {
1524                            workspace_proc_macro_cwd.clone()
1525                        } else {
1526                            Arc::new(pkg_data.manifest.parent().to_path_buf())
1527                        },
1528                    );
1529                    pkg_to_lib_crate.insert(pkg, crate_id);
1530                    // Add dependencies on core / std / alloc for this crate
1531                    public_deps.add_to_crate_graph(crate_graph, crate_id);
1532                    if let Some(proc_macro) = libproc_macro {
1533                        add_proc_macro_dep(crate_graph, crate_id, proc_macro, is_proc_macro);
1534                    }
1535                    pkg_crates.push(crate_id);
1536                }
1537            }
1538        }
1539    }
1540    // Now add a dep edge from all targets of upstream to the lib
1541    // target of downstream.
1542    for pkg in rustc_pkg_crates.keys().copied() {
1543        for dep in rustc_workspace[pkg].dependencies.iter() {
1544            let name = CrateName::new(&dep.name).unwrap();
1545            if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
1546                for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
1547                    add_dep(crate_graph, from, name.clone(), to);
1548                }
1549            }
1550        }
1551    }
1552    // Add a dependency on the rustc_private crates for all targets of each package
1553    // which opts in
1554    for dep in rustc_workspace.packages() {
1555        let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
1556
1557        if let Some(&to) = pkg_to_lib_crate.get(&dep) {
1558            for pkg in cargo.packages() {
1559                let package = &cargo[pkg];
1560                if !package.metadata.rustc_private {
1561                    continue;
1562                }
1563                for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
1564                    // Avoid creating duplicate dependencies
1565                    // This avoids the situation where `from` depends on e.g. `arrayvec`, but
1566                    // `rust_analyzer` thinks that it should use the one from the `rustc_source`
1567                    // instead of the one from `crates.io`
1568                    if !crate_graph[*from].basic.dependencies.iter().any(|d| d.name == name) {
1569                        add_dep(crate_graph, *from, name.clone(), to);
1570                    }
1571                }
1572            }
1573        }
1574    }
1575}
1576
1577fn add_target_crate_root(
1578    crate_graph: &mut CrateGraphBuilder,
1579    proc_macros: &mut ProcMacroPaths,
1580    cargo: &CargoWorkspace,
1581    pkg: &PackageData,
1582    build_data: Option<(&BuildScriptOutput, bool)>,
1583    cfg_options: CfgOptions,
1584    file_id: FileId,
1585    cargo_name: &str,
1586    kind: TargetKind,
1587    origin: CrateOrigin,
1588    crate_ws_data: Arc<CrateWorkspaceData>,
1589    proc_macro_cwd: Arc<AbsPathBuf>,
1590) -> CrateBuilderId {
1591    let edition = pkg.edition;
1592    let potential_cfg_options = if pkg.features.is_empty() {
1593        None
1594    } else {
1595        let mut potential_cfg_options = cfg_options.clone();
1596        potential_cfg_options.extend(
1597            pkg.features
1598                .iter()
1599                .map(|feat| CfgAtom::KeyValue { key: sym::feature, value: Symbol::intern(feat.0) }),
1600        );
1601        Some(potential_cfg_options)
1602    };
1603    let cfg_options = {
1604        let mut opts = cfg_options;
1605        for feature in pkg.active_features.iter() {
1606            opts.insert_key_value(sym::feature, Symbol::intern(feature));
1607        }
1608        if let Some(cfgs) = build_data.map(|(it, _)| &it.cfgs) {
1609            opts.extend(cfgs.iter().cloned());
1610        }
1611        opts
1612    };
1613
1614    let mut env = cargo.env().clone();
1615    inject_cargo_package_env(&mut env, pkg);
1616    inject_cargo_env(&mut env);
1617    inject_rustc_tool_env(&mut env, cargo_name, kind);
1618
1619    if let Some(envs) = build_data.map(|(it, _)| &it.envs) {
1620        env.extend_from_other(envs);
1621    }
1622    let crate_id = crate_graph.add_crate_root(
1623        file_id,
1624        edition,
1625        Some(CrateDisplayName::from_canonical_name(cargo_name)),
1626        Some(pkg.version.to_string()),
1627        cfg_options,
1628        potential_cfg_options,
1629        env,
1630        origin,
1631        matches!(kind, TargetKind::Lib { is_proc_macro: true }),
1632        proc_macro_cwd,
1633        crate_ws_data,
1634    );
1635    if let TargetKind::Lib { is_proc_macro: true } = kind {
1636        let proc_macro = match build_data {
1637            Some((BuildScriptOutput { proc_macro_dylib_path, .. }, has_errors)) => {
1638                match proc_macro_dylib_path {
1639                    ProcMacroDylibPath::Path(path) => Ok((cargo_name.to_owned(), path.clone())),
1640                    ProcMacroDylibPath::NotBuilt => Err(ProcMacroLoadingError::NotYetBuilt),
1641                    ProcMacroDylibPath::NotProcMacro | ProcMacroDylibPath::DylibNotFound
1642                        if has_errors =>
1643                    {
1644                        Err(ProcMacroLoadingError::FailedToBuild)
1645                    }
1646                    ProcMacroDylibPath::NotProcMacro => {
1647                        Err(ProcMacroLoadingError::ExpectedProcMacroArtifact)
1648                    }
1649                    ProcMacroDylibPath::DylibNotFound => {
1650                        Err(ProcMacroLoadingError::MissingDylibPath)
1651                    }
1652                }
1653            }
1654            None => Err(ProcMacroLoadingError::NotYetBuilt),
1655        };
1656        proc_macros.insert(crate_id, proc_macro);
1657    }
1658
1659    crate_id
1660}
1661
1662#[derive(Default, Debug)]
1663struct SysrootPublicDeps {
1664    deps: Vec<(CrateName, CrateBuilderId, bool)>,
1665}
1666
1667impl SysrootPublicDeps {
1668    /// Makes `from` depend on the public sysroot crates.
1669    fn add_to_crate_graph(&self, crate_graph: &mut CrateGraphBuilder, from: CrateBuilderId) {
1670        for (name, krate, prelude) in &self.deps {
1671            add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude, true);
1672        }
1673    }
1674}
1675
1676fn extend_crate_graph_with_sysroot(
1677    crate_graph: &mut CrateGraphBuilder,
1678    mut sysroot_crate_graph: CrateGraphBuilder,
1679    mut sysroot_proc_macros: ProcMacroPaths,
1680) -> (SysrootPublicDeps, Option<CrateBuilderId>) {
1681    let mut pub_deps = vec![];
1682    let mut libproc_macro = None;
1683    for cid in sysroot_crate_graph.iter() {
1684        if let CrateOrigin::Lang(lang_crate) = sysroot_crate_graph[cid].basic.origin {
1685            match lang_crate {
1686                LangCrateOrigin::Test
1687                | LangCrateOrigin::Alloc
1688                | LangCrateOrigin::Core
1689                | LangCrateOrigin::Std => pub_deps.push((
1690                    CrateName::normalize_dashes(&lang_crate.to_string()),
1691                    cid,
1692                    !matches!(lang_crate, LangCrateOrigin::Test | LangCrateOrigin::Alloc),
1693                )),
1694                LangCrateOrigin::ProcMacro => libproc_macro = Some(cid),
1695                LangCrateOrigin::Other => (),
1696            }
1697        }
1698    }
1699
1700    let mut marker_set = vec![];
1701    for &(_, cid, _) in pub_deps.iter() {
1702        marker_set.extend(sysroot_crate_graph.transitive_deps(cid));
1703    }
1704    if let Some(cid) = libproc_macro {
1705        marker_set.extend(sysroot_crate_graph.transitive_deps(cid));
1706    }
1707
1708    marker_set.sort();
1709    marker_set.dedup();
1710
1711    // Remove all crates except the ones we are interested in to keep the sysroot graph small.
1712    let removed_mapping = sysroot_crate_graph.remove_crates_except(&marker_set);
1713    sysroot_proc_macros = sysroot_proc_macros
1714        .into_iter()
1715        .filter_map(|(k, v)| Some((removed_mapping[k.into_raw().into_u32() as usize]?, v)))
1716        .collect();
1717    let mapping = crate_graph.extend(sysroot_crate_graph, &mut sysroot_proc_macros);
1718
1719    // Map the id through the removal mapping first, then through the crate graph extension mapping.
1720    pub_deps.iter_mut().for_each(|(_, cid, _)| {
1721        *cid = mapping[&removed_mapping[cid.into_raw().into_u32() as usize].unwrap()]
1722    });
1723    if let Some(libproc_macro) = &mut libproc_macro {
1724        *libproc_macro =
1725            mapping[&removed_mapping[libproc_macro.into_raw().into_u32() as usize].unwrap()];
1726    }
1727
1728    (SysrootPublicDeps { deps: pub_deps }, libproc_macro)
1729}
1730
1731fn sysroot_to_crate_graph(
1732    crate_graph: &mut CrateGraphBuilder,
1733    sysroot: &Sysroot,
1734    rustc_cfg: Vec<CfgAtom>,
1735    load: FileLoader<'_>,
1736    crate_ws_data: Arc<CrateWorkspaceData>,
1737) -> (SysrootPublicDeps, Option<CrateBuilderId>) {
1738    let _p = tracing::info_span!("sysroot_to_crate_graph").entered();
1739    match sysroot.workspace() {
1740        RustLibSrcWorkspace::Workspace(cargo) => {
1741            let (sysroot_cg, sysroot_pm) = cargo_to_crate_graph(
1742                load,
1743                None,
1744                cargo,
1745                &Sysroot::empty(),
1746                rustc_cfg,
1747                &CfgOverrides {
1748                    global: CfgDiff::new(
1749                        vec![
1750                            CfgAtom::Flag(sym::debug_assertions),
1751                            CfgAtom::Flag(sym::miri),
1752                            CfgAtom::Flag(sym::bootstrap),
1753                        ],
1754                        vec![CfgAtom::Flag(sym::test)],
1755                    ),
1756                    ..Default::default()
1757                },
1758                &WorkspaceBuildScripts::default(),
1759                false,
1760                crate_ws_data,
1761            );
1762
1763            extend_crate_graph_with_sysroot(crate_graph, sysroot_cg, sysroot_pm)
1764        }
1765        RustLibSrcWorkspace::Json(project_json) => {
1766            let (sysroot_cg, sysroot_pm) = project_json_to_crate_graph(
1767                rustc_cfg,
1768                load,
1769                project_json,
1770                &Sysroot::empty(),
1771                &FxHashMap::default(),
1772                &CfgOverrides {
1773                    global: CfgDiff::new(
1774                        vec![CfgAtom::Flag(sym::debug_assertions), CfgAtom::Flag(sym::miri)],
1775                        vec![],
1776                    ),
1777                    ..Default::default()
1778                },
1779                false,
1780                true,
1781                crate_ws_data,
1782            );
1783
1784            extend_crate_graph_with_sysroot(crate_graph, sysroot_cg, sysroot_pm)
1785        }
1786        RustLibSrcWorkspace::Stitched(stitched) => {
1787            let cfg_options = {
1788                let mut cfg_options = CfgOptions::default();
1789                cfg_options.extend(rustc_cfg);
1790                cfg_options.insert_atom(sym::debug_assertions);
1791                cfg_options.insert_atom(sym::miri);
1792                cfg_options
1793            };
1794            let sysroot_crates: FxHashMap<
1795                crate::sysroot::stitched::RustLibSrcCrate,
1796                CrateBuilderId,
1797            > = stitched
1798                .crates()
1799                .filter_map(|krate| {
1800                    let file_id = load(&stitched[krate].root)?;
1801
1802                    let display_name = CrateDisplayName::from_canonical_name(&stitched[krate].name);
1803                    let crate_id = crate_graph.add_crate_root(
1804                        file_id,
1805                        Edition::CURRENT_FIXME,
1806                        Some(display_name),
1807                        None,
1808                        cfg_options.clone(),
1809                        None,
1810                        Env::default(),
1811                        CrateOrigin::Lang(LangCrateOrigin::from(&*stitched[krate].name)),
1812                        false,
1813                        Arc::new(stitched[krate].root.parent().to_path_buf()),
1814                        crate_ws_data.clone(),
1815                    );
1816                    Some((krate, crate_id))
1817                })
1818                .collect();
1819
1820            for from in stitched.crates() {
1821                for &to in stitched[from].deps.iter() {
1822                    let name = CrateName::new(&stitched[to].name).unwrap();
1823                    if let (Some(&from), Some(&to)) =
1824                        (sysroot_crates.get(&from), sysroot_crates.get(&to))
1825                    {
1826                        add_dep(crate_graph, from, name, to);
1827                    }
1828                }
1829            }
1830
1831            let public_deps = SysrootPublicDeps {
1832                deps: stitched
1833                    .public_deps()
1834                    .filter_map(|(name, idx, prelude)| {
1835                        Some((name, *sysroot_crates.get(&idx)?, prelude))
1836                    })
1837                    .collect::<Vec<_>>(),
1838            };
1839
1840            let libproc_macro =
1841                stitched.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
1842            (public_deps, libproc_macro)
1843        }
1844        RustLibSrcWorkspace::Empty => (SysrootPublicDeps { deps: vec![] }, None),
1845    }
1846}
1847
1848fn add_dep(
1849    graph: &mut CrateGraphBuilder,
1850    from: CrateBuilderId,
1851    name: CrateName,
1852    to: CrateBuilderId,
1853) {
1854    add_dep_inner(graph, from, DependencyBuilder::new(name, to))
1855}
1856
1857fn add_dep_with_prelude(
1858    graph: &mut CrateGraphBuilder,
1859    from: CrateBuilderId,
1860    name: CrateName,
1861    to: CrateBuilderId,
1862    prelude: bool,
1863    sysroot: bool,
1864) {
1865    add_dep_inner(graph, from, DependencyBuilder::with_prelude(name, to, prelude, sysroot))
1866}
1867
1868fn add_proc_macro_dep(
1869    crate_graph: &mut CrateGraphBuilder,
1870    from: CrateBuilderId,
1871    to: CrateBuilderId,
1872    prelude: bool,
1873) {
1874    add_dep_with_prelude(
1875        crate_graph,
1876        from,
1877        CrateName::new("proc_macro").unwrap(),
1878        to,
1879        prelude,
1880        true,
1881    );
1882}
1883
1884fn add_dep_inner(graph: &mut CrateGraphBuilder, from: CrateBuilderId, dep: DependencyBuilder) {
1885    if let Err(err) = graph.add_dep(from, dep) {
1886        tracing::warn!("{}", err)
1887    }
1888}
1889
1890fn sysroot_metadata_config(
1891    config: &CargoConfig,
1892    targets: &[String],
1893    toolchain_version: Option<Version>,
1894) -> CargoMetadataConfig {
1895    CargoMetadataConfig {
1896        features: Default::default(),
1897        targets: targets.to_vec(),
1898        extra_args: Default::default(),
1899        extra_env: config.extra_env.clone(),
1900        toolchain_version,
1901        kind: "sysroot",
1902    }
1903}
1904
1905fn cargo_target_dir(
1906    manifest: &ManifestPath,
1907    extra_env: &FxHashMap<String, Option<String>>,
1908    sysroot: &Sysroot,
1909) -> Option<Utf8PathBuf> {
1910    let cargo = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env);
1911    let mut meta = cargo_metadata::MetadataCommand::new();
1912    meta.cargo_path(cargo.get_program());
1913    meta.manifest_path(manifest);
1914    // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve.
1915    // So we can use it to get `target_directory` before copying lockfiles
1916    meta.no_deps();
1917    let mut other_options = vec![];
1918    if manifest.is_rust_manifest() {
1919        meta.env("RUSTC_BOOTSTRAP", "1");
1920        other_options.push("-Zscript".to_owned());
1921    }
1922    meta.other_options(other_options);
1923    meta.exec().map(|m| m.target_directory).ok()
1924}