Skip to main content

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