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