about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-01-13 17:22:39 +0100
committerLukas Wirth <lukastw97@gmail.com>2024-01-15 09:59:32 +0100
commitc7eb52dd7b2a8222d4a09934bacc1b2b07deafb0 (patch)
tree135da1dad4d8be040af35b89f1d3418fb4e6748b
parent9d8889cdfcc3aa0302353fc988ed21ff9bc9925c (diff)
downloadrust-c7eb52dd7b2a8222d4a09934bacc1b2b07deafb0.tar.gz
rust-c7eb52dd7b2a8222d4a09934bacc1b2b07deafb0.zip
internal: Add unstable config for loading the sysroot sources via `cargo metadata`
-rw-r--r--crates/base-db/src/input.rs42
-rw-r--r--crates/project-model/src/cargo_workspace.rs2
-rw-r--r--crates/project-model/src/sysroot.rs275
-rw-r--r--crates/project-model/src/tests.rs63
-rw-r--r--crates/project-model/src/workspace.rs317
-rw-r--r--crates/project-model/test_data/output/rust_project_hello_world_project_model.txt2
-rw-r--r--crates/rust-analyzer/src/cli/rustc_tests.rs9
-rw-r--r--crates/rust-analyzer/src/config.rs9
-rw-r--r--crates/rust-analyzer/src/reload.rs2
-rw-r--r--docs/user/generated_config.adoc10
-rw-r--r--editors/code/package.json5
11 files changed, 497 insertions, 239 deletions
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index e45a81238ac..8c43f97b91f 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -9,7 +9,7 @@
 use std::{fmt, mem, ops, str::FromStr};
 
 use cfg::CfgOptions;
-use la_arena::{Arena, Idx};
+use la_arena::{Arena, Idx, RawIdx};
 use rustc_hash::{FxHashMap, FxHashSet};
 use semver::Version;
 use syntax::SmolStr;
@@ -157,6 +157,10 @@ impl CrateOrigin {
     pub fn is_lib(&self) -> bool {
         matches!(self, CrateOrigin::Library { .. })
     }
+
+    pub fn is_lang(&self) -> bool {
+        matches!(self, CrateOrigin::Lang { .. })
+    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -174,7 +178,7 @@ impl From<&str> for LangCrateOrigin {
         match s {
             "alloc" => LangCrateOrigin::Alloc,
             "core" => LangCrateOrigin::Core,
-            "proc-macro" => LangCrateOrigin::ProcMacro,
+            "proc-macro" | "proc_macro" => LangCrateOrigin::ProcMacro,
             "std" => LangCrateOrigin::Std,
             "test" => LangCrateOrigin::Test,
             _ => LangCrateOrigin::Other,
@@ -522,7 +526,7 @@ impl CrateGraph {
         self.arena.iter().map(|(idx, _)| idx)
     }
 
-    // FIXME: used for `handle_hack_cargo_workspace`, should be removed later
+    // FIXME: used for fixing up the toolchain sysroot, should be removed and done differently
     #[doc(hidden)]
     pub fn iter_mut(&mut self) -> impl Iterator<Item = (CrateId, &mut CrateData)> + '_ {
         self.arena.iter_mut()
@@ -619,7 +623,12 @@ impl CrateGraph {
     /// This will deduplicate the crates of the graph where possible.
     /// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id.
     /// If the crate dependencies were sorted, the resulting graph from this `extend` call will also have the crate dependencies sorted.
-    pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) {
+    pub fn extend(
+        &mut self,
+        mut other: CrateGraph,
+        proc_macros: &mut ProcMacroPaths,
+        on_finished: impl FnOnce(&FxHashMap<CrateId, CrateId>),
+    ) {
         let topo = other.crates_in_topological_order();
         let mut id_map: FxHashMap<CrateId, CrateId> = FxHashMap::default();
         for topo in topo {
@@ -670,6 +679,8 @@ impl CrateGraph {
 
         *proc_macros =
             mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect();
+
+        on_finished(&id_map);
     }
 
     fn find_path(
@@ -721,6 +732,29 @@ impl CrateGraph {
     fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a {
         self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
     }
+
+    /// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
+    /// Returns a mapping from old crate ids to new crate ids.
+    pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> {
+        let mut id_map = vec![None; self.arena.len()];
+        self.arena = std::mem::take(&mut self.arena)
+            .into_iter()
+            .filter_map(|(id, data)| if to_keep.contains(&id) { Some((id, data)) } else { None })
+            .enumerate()
+            .map(|(new_id, (id, data))| {
+                id_map[id.into_raw().into_u32() as usize] =
+                    Some(CrateId::from_raw(RawIdx::from_u32(new_id as u32)));
+                data
+            })
+            .collect();
+        for (_, data) in self.arena.iter_mut() {
+            data.dependencies.iter_mut().for_each(|dep| {
+                dep.crate_id =
+                    id_map[dep.crate_id.into_raw().into_u32() as usize].expect("crate was filtered")
+            });
+        }
+        id_map
+    }
 }
 
 impl ops::Index<CrateId> for CrateGraph {
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index bc1fcd08e20..dba9edab3c9 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -82,6 +82,8 @@ pub struct CargoConfig {
     pub target: Option<String>,
     /// Sysroot loading behavior
     pub sysroot: Option<RustLibSource>,
+    /// Whether to invoke `cargo metadata` on the sysroot crate.
+    pub sysroot_query_metadata: bool,
     pub sysroot_src: Option<AbsPathBuf>,
     /// rustc private crate source
     pub rustc_source: Option<RustLibSource>,
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index d52e448d747..fb5e8c365a3 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -8,6 +8,7 @@ use std::{env, fs, iter, ops, path::PathBuf, process::Command};
 
 use anyhow::{format_err, Context, Result};
 use base_db::CrateName;
+use itertools::Itertools;
 use la_arena::{Arena, Idx};
 use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::FxHashMap;
@@ -18,42 +19,29 @@ use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath};
 pub struct Sysroot {
     root: AbsPathBuf,
     src_root: AbsPathBuf,
-    crates: Arena<SysrootCrateData>,
-    /// Stores the result of `cargo metadata` of the `RA_UNSTABLE_SYSROOT_HACK` workspace.
-    pub hack_cargo_workspace: Option<CargoWorkspace>,
+    mode: SysrootMode,
 }
 
-pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub(crate) enum SysrootMode {
+    Workspace(CargoWorkspace),
+    Stitched(Stitched),
+}
 
 #[derive(Debug, Clone, Eq, PartialEq)]
-pub struct SysrootCrateData {
-    pub name: String,
-    pub root: ManifestPath,
-    pub deps: Vec<SysrootCrate>,
+pub(crate) struct Stitched {
+    crates: Arena<SysrootCrateData>,
 }
 
-impl ops::Index<SysrootCrate> for Sysroot {
+impl ops::Index<SysrootCrate> for Stitched {
     type Output = SysrootCrateData;
     fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
         &self.crates[index]
     }
 }
 
-impl Sysroot {
-    /// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
-    /// subfolder live, like:
-    /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu`
-    pub fn root(&self) -> &AbsPath {
-        &self.root
-    }
-
-    /// Returns the sysroot "source" directory, where stdlib sources are located, like:
-    /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
-    pub fn src_root(&self) -> &AbsPath {
-        &self.src_root
-    }
-
-    pub fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
+impl Stitched {
+    pub(crate) fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
         // core is added as a dependency before std in order to
         // mimic rustcs dependency order
         ["core", "alloc", "std"]
@@ -65,20 +53,56 @@ impl Sysroot {
             })
     }
 
-    pub fn proc_macro(&self) -> Option<SysrootCrate> {
+    pub(crate) fn proc_macro(&self) -> Option<SysrootCrate> {
         self.by_name("proc_macro")
     }
 
-    pub fn crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_ {
+    pub(crate) fn crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_ {
         self.crates.iter().map(|(id, _data)| id)
     }
 
+    fn by_name(&self, name: &str) -> Option<SysrootCrate> {
+        let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
+        Some(id)
+    }
+}
+
+pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub(crate) struct SysrootCrateData {
+    pub(crate) name: String,
+    pub(crate) root: ManifestPath,
+    pub(crate) deps: Vec<SysrootCrate>,
+}
+
+impl Sysroot {
+    /// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
+    /// subfolder live, like:
+    /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu`
+    pub fn root(&self) -> &AbsPath {
+        &self.root
+    }
+
+    /// Returns the sysroot "source" directory, where stdlib sources are located, like:
+    /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
+    pub fn src_root(&self) -> &AbsPath {
+        &self.src_root
+    }
+
     pub fn is_empty(&self) -> bool {
-        self.crates.is_empty()
+        match &self.mode {
+            SysrootMode::Workspace(ws) => ws.packages().next().is_none(),
+            SysrootMode::Stitched(stitched) => stitched.crates.is_empty(),
+        }
     }
 
     pub fn loading_warning(&self) -> Option<String> {
-        if self.by_name("core").is_none() {
+        let has_core = match &self.mode {
+            SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
+            SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
+        };
+        if !has_core {
             let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
                 " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
             } else {
@@ -92,27 +116,43 @@ impl Sysroot {
             None
         }
     }
+
+    pub fn num_packages(&self) -> usize {
+        match &self.mode {
+            SysrootMode::Workspace(ws) => ws.packages().count(),
+            SysrootMode::Stitched(c) => c.crates().count(),
+        }
+    }
+
+    pub(crate) fn mode(&self) -> &SysrootMode {
+        &self.mode
+    }
 }
 
 // FIXME: Expose a builder api as loading the sysroot got way too modular and complicated.
 impl Sysroot {
     /// Attempts to discover the toolchain's sysroot from the given `dir`.
-    pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
+    pub fn discover(
+        dir: &AbsPath,
+        extra_env: &FxHashMap<String, String>,
+        metadata: bool,
+    ) -> Result<Sysroot> {
         tracing::debug!("discovering sysroot for {dir}");
         let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
         let sysroot_src_dir =
             discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
-        Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
+        Ok(Sysroot::load(sysroot_dir, sysroot_src_dir, metadata))
     }
 
     pub fn discover_with_src_override(
         current_dir: &AbsPath,
         extra_env: &FxHashMap<String, String>,
         src: AbsPathBuf,
+        metadata: bool,
     ) -> Result<Sysroot> {
         tracing::debug!("discovering sysroot for {current_dir}");
         let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?;
-        Ok(Sysroot::load(sysroot_dir, src))
+        Ok(Sysroot::load(sysroot_dir, src, metadata))
     }
 
     pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
@@ -131,49 +171,132 @@ impl Sysroot {
         }
     }
 
-    pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
+    pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf, metadata: bool) -> Result<Sysroot> {
         let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
             format_err!("can't load standard library from sysroot path {sysroot_dir}")
         })?;
-        Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
+        Ok(Sysroot::load(sysroot_dir, sysroot_src_dir, metadata))
     }
 
-    pub fn load(sysroot_dir: AbsPathBuf, mut sysroot_src_dir: AbsPathBuf) -> Sysroot {
-        // FIXME: Remove this `hack_cargo_workspace` field completely once we support sysroot dependencies
-        let hack_cargo_workspace = if let Ok(path) = std::env::var("RA_UNSTABLE_SYSROOT_HACK") {
-            let cargo_toml = ManifestPath::try_from(
-                AbsPathBuf::try_from(&*format!("{path}/Cargo.toml")).unwrap(),
-            )
-            .unwrap();
-            sysroot_src_dir = AbsPathBuf::try_from(&*path).unwrap().join("library");
-            CargoWorkspace::fetch_metadata(
-                &cargo_toml,
-                &AbsPathBuf::try_from("/").unwrap(),
-                &CargoConfig::default(),
-                &|_| (),
-            )
-            .map(CargoWorkspace::new)
-            .ok()
-        } else {
-            None
-        };
-        let mut sysroot = Sysroot {
-            root: sysroot_dir,
-            src_root: sysroot_src_dir,
-            crates: Arena::default(),
-            hack_cargo_workspace,
-        };
+    pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf, metadata: bool) -> Sysroot {
+        if metadata {
+            let sysroot: Option<_> = (|| {
+                let sysroot_cargo_toml = ManifestPath::try_from(
+                    AbsPathBuf::try_from(&*format!("{sysroot_src_dir}/sysroot/Cargo.toml")).ok()?,
+                )
+                .ok()?;
+                let current_dir =
+                    AbsPathBuf::try_from(&*format!("{sysroot_src_dir}/sysroot")).ok()?;
+                let res = CargoWorkspace::fetch_metadata(
+                    &sysroot_cargo_toml,
+                    &current_dir,
+                    &CargoConfig::default(),
+                    &|_| (),
+                )
+                .map_err(|e| {
+                    tracing::error!(
+                        "failed to load sysroot `{sysroot_src_dir}/sysroot/Cargo.toml`: {}",
+                        e
+                    );
+                    e
+                });
+                if let Err(e) =
+                    std::fs::remove_file(&format!("{sysroot_src_dir}/sysroot/Cargo.lock"))
+                {
+                    tracing::error!(
+                        "failed to remove sysroot `{sysroot_src_dir}/sysroot/Cargo.lock`: {}",
+                        e
+                    )
+                }
+                let mut res = res.ok()?;
+
+                // Patch out `rustc-std-workspace-*` crates to point to the real crates.
+                // This is done prior to `CrateGraph` construction to avoid having duplicate `std` targets.
+
+                let mut fake_core = None;
+                let mut fake_alloc = None;
+                let mut fake_std = None;
+                let mut real_core = None;
+                let mut real_alloc = None;
+                let mut real_std = None;
+                res.packages.iter().enumerate().for_each(|(idx, package)| {
+                    match package.name.strip_prefix("rustc-std-workspace-") {
+                        Some("core") => fake_core = Some((idx, package.id.clone())),
+                        Some("alloc") => fake_alloc = Some((idx, package.id.clone())),
+                        Some("std") => fake_std = Some((idx, package.id.clone())),
+                        Some(_) => {
+                            tracing::warn!("unknown rustc-std-workspace-* crate: {}", package.name)
+                        }
+                        None => match &*package.name {
+                            "core" => real_core = Some(package.id.clone()),
+                            "alloc" => real_alloc = Some(package.id.clone()),
+                            "std" => real_std = Some(package.id.clone()),
+                            _ => (),
+                        },
+                    }
+                });
+
+                let patches =
+                    [fake_core.zip(real_core), fake_alloc.zip(real_alloc), fake_std.zip(real_std)]
+                        .into_iter()
+                        .flatten();
+
+                let resolve = res.resolve.as_mut().expect("metadata executed with deps");
+                let mut remove_nodes = vec![];
+                for (idx, node) in resolve.nodes.iter_mut().enumerate() {
+                    // Replace them in the dependency list
+                    node.deps.iter_mut().for_each(|dep| {
+                        if let Some((_, real)) =
+                            patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg)
+                        {
+                            dep.pkg = real;
+                        }
+                    });
+                    if patches.clone().any(|((_, fake), _)| fake == node.id) {
+                        remove_nodes.push(idx);
+                    }
+                }
+                // Remove the fake ones from the resolve data
+                remove_nodes.into_iter().rev().for_each(|r| {
+                    resolve.nodes.remove(r);
+                });
+                // Remove the fake ones from the packages
+                patches.map(|((r, _), _)| r).sorted().rev().for_each(|r| {
+                    res.packages.remove(r);
+                });
+
+                res.workspace_members = res
+                    .packages
+                    .iter()
+                    .filter_map(|package| {
+                        RELEVANT_SYSROOT_CRATES
+                            .contains(&&*package.name)
+                            .then(|| package.id.clone())
+                    })
+                    .collect();
+                let cargo_workspace = CargoWorkspace::new(res);
+                Some(Sysroot {
+                    root: sysroot_dir.clone(),
+                    src_root: sysroot_src_dir.clone(),
+                    mode: SysrootMode::Workspace(cargo_workspace),
+                })
+            })();
+            if let Some(sysroot) = sysroot {
+                return sysroot;
+            }
+        }
+        let mut stitched = Stitched { crates: Arena::default() };
 
         for path in SYSROOT_CRATES.trim().lines() {
             let name = path.split('/').last().unwrap();
             let root = [format!("{path}/src/lib.rs"), format!("lib{path}/lib.rs")]
                 .into_iter()
-                .map(|it| sysroot.src_root.join(it))
+                .map(|it| sysroot_src_dir.join(it))
                 .filter_map(|it| ManifestPath::try_from(it).ok())
                 .find(|it| fs::metadata(it).is_ok());
 
             if let Some(root) = root {
-                sysroot.crates.alloc(SysrootCrateData {
+                stitched.crates.alloc(SysrootCrateData {
                     name: name.into(),
                     root,
                     deps: Vec::new(),
@@ -181,36 +304,34 @@ impl Sysroot {
             }
         }
 
-        if let Some(std) = sysroot.by_name("std") {
+        if let Some(std) = stitched.by_name("std") {
             for dep in STD_DEPS.trim().lines() {
-                if let Some(dep) = sysroot.by_name(dep) {
-                    sysroot.crates[std].deps.push(dep)
+                if let Some(dep) = stitched.by_name(dep) {
+                    stitched.crates[std].deps.push(dep)
                 }
             }
         }
 
-        if let Some(alloc) = sysroot.by_name("alloc") {
+        if let Some(alloc) = stitched.by_name("alloc") {
             for dep in ALLOC_DEPS.trim().lines() {
-                if let Some(dep) = sysroot.by_name(dep) {
-                    sysroot.crates[alloc].deps.push(dep)
+                if let Some(dep) = stitched.by_name(dep) {
+                    stitched.crates[alloc].deps.push(dep)
                 }
             }
         }
 
-        if let Some(proc_macro) = sysroot.by_name("proc_macro") {
+        if let Some(proc_macro) = stitched.by_name("proc_macro") {
             for dep in PROC_MACRO_DEPS.trim().lines() {
-                if let Some(dep) = sysroot.by_name(dep) {
-                    sysroot.crates[proc_macro].deps.push(dep)
+                if let Some(dep) = stitched.by_name(dep) {
+                    stitched.crates[proc_macro].deps.push(dep)
                 }
             }
         }
-
-        sysroot
-    }
-
-    fn by_name(&self, name: &str) -> Option<SysrootCrate> {
-        let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
-        Some(id)
+        Sysroot {
+            root: sysroot_dir,
+            src_root: sysroot_src_dir,
+            mode: SysrootMode::Stitched(stitched),
+        }
     }
 }
 
@@ -318,3 +439,5 @@ test";
 const PROC_MACRO_DEPS: &str = "
 std
 core";
+
+const RELEVANT_SYSROOT_CRATES: &[&str] = &["core", "alloc", "std", "test", "proc_macro"];
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 4887b29815a..9e6b00d9380 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -38,7 +38,7 @@ fn load_cargo_with_overrides(
     to_crate_graph(project_workspace)
 }
 
-fn load_cargo_with_sysroot(
+fn load_cargo_with_fake_sysroot(
     file_map: &mut FxHashMap<AbsPathBuf, FileId>,
     file: &str,
 ) -> (CrateGraph, ProcMacroPaths) {
@@ -125,7 +125,7 @@ fn get_fake_sysroot() -> Sysroot {
     // fake sysroot, so we give them both the same path:
     let sysroot_dir = AbsPathBuf::assert(sysroot_path);
     let sysroot_src_dir = sysroot_dir.clone();
-    Sysroot::load(sysroot_dir, sysroot_src_dir)
+    Sysroot::load(sysroot_dir, sysroot_src_dir, false)
 }
 
 fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
@@ -225,12 +225,12 @@ fn rust_project_is_proc_macro_has_proc_macro_dep() {
 #[test]
 fn crate_graph_dedup_identical() {
     let (mut crate_graph, proc_macros) =
-        load_cargo_with_sysroot(&mut Default::default(), "regex-metadata.json");
+        load_cargo_with_fake_sysroot(&mut Default::default(), "regex-metadata.json");
     crate_graph.sort_deps();
 
     let (d_crate_graph, mut d_proc_macros) = (crate_graph.clone(), proc_macros.clone());
 
-    crate_graph.extend(d_crate_graph.clone(), &mut d_proc_macros);
+    crate_graph.extend(d_crate_graph.clone(), &mut d_proc_macros, |_| ());
     assert!(crate_graph.iter().eq(d_crate_graph.iter()));
     assert_eq!(proc_macros, d_proc_macros);
 }
@@ -239,14 +239,14 @@ fn crate_graph_dedup_identical() {
 fn crate_graph_dedup() {
     let path_map = &mut Default::default();
     let (mut crate_graph, _proc_macros) =
-        load_cargo_with_sysroot(path_map, "ripgrep-metadata.json");
+        load_cargo_with_fake_sysroot(path_map, "ripgrep-metadata.json");
     assert_eq!(crate_graph.iter().count(), 81);
     crate_graph.sort_deps();
     let (regex_crate_graph, mut regex_proc_macros) =
-        load_cargo_with_sysroot(path_map, "regex-metadata.json");
+        load_cargo_with_fake_sysroot(path_map, "regex-metadata.json");
     assert_eq!(regex_crate_graph.iter().count(), 60);
 
-    crate_graph.extend(regex_crate_graph, &mut regex_proc_macros);
+    crate_graph.extend(regex_crate_graph, &mut regex_proc_macros, |_| ());
     assert_eq!(crate_graph.iter().count(), 118);
 }
 
@@ -254,12 +254,12 @@ fn crate_graph_dedup() {
 fn test_deduplicate_origin_dev() {
     let path_map = &mut Default::default();
     let (mut crate_graph, _proc_macros) =
-        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
+        load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_A.json");
     crate_graph.sort_deps();
     let (crate_graph_1, mut _proc_macros_2) =
-        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
+        load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_B.json");
 
-    crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
+    crate_graph.extend(crate_graph_1, &mut _proc_macros_2, |_| ());
 
     let mut crates_named_p2 = vec![];
     for id in crate_graph.iter() {
@@ -280,12 +280,12 @@ fn test_deduplicate_origin_dev() {
 fn test_deduplicate_origin_dev_rev() {
     let path_map = &mut Default::default();
     let (mut crate_graph, _proc_macros) =
-        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
+        load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_B.json");
     crate_graph.sort_deps();
     let (crate_graph_1, mut _proc_macros_2) =
-        load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
+        load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_A.json");
 
-    crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
+    crate_graph.extend(crate_graph_1, &mut _proc_macros_2, |_| ());
 
     let mut crates_named_p2 = vec![];
     for id in crate_graph.iter() {
@@ -301,3 +301,40 @@ fn test_deduplicate_origin_dev_rev() {
     let p2 = crates_named_p2[0];
     assert!(p2.origin.is_local());
 }
+
+#[test]
+fn smoke_test_real_sysroot_cargo() {
+    if std::env::var("SYSROOT_CARGO_METADATA").is_err() {
+        return;
+    }
+    let file_map = &mut FxHashMap::<AbsPathBuf, FileId>::default();
+    let meta = get_test_json_file("hello-world-metadata.json");
+
+    let cargo_workspace = CargoWorkspace::new(meta);
+    let sysroot = Ok(Sysroot::discover(
+        AbsPath::assert(Path::new(env!("CARGO_MANIFEST_DIR"))),
+        &Default::default(),
+        true,
+    )
+    .unwrap());
+
+    let project_workspace = ProjectWorkspace::Cargo {
+        cargo: cargo_workspace,
+        build_scripts: WorkspaceBuildScripts::default(),
+        sysroot,
+        rustc: Err(None),
+        rustc_cfg: Vec::new(),
+        cfg_overrides: Default::default(),
+        toolchain: None,
+        target_layout: Err("target_data_layout not loaded".into()),
+    };
+    project_workspace.to_crate_graph(
+        &mut {
+            |path| {
+                let len = file_map.len();
+                Some(*file_map.entry(path.to_path_buf()).or_insert(FileId::from_raw(len as u32)))
+            }
+        },
+        &Default::default(),
+    );
+}
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index c04eddc56fb..679f219dcc4 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -9,7 +9,7 @@ use base_db::{
     CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, DependencyKind,
     Edition, Env, FileId, LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult,
 };
-use cfg::{CfgDiff, CfgOptions};
+use cfg::{CfgAtom, CfgDiff, CfgOptions};
 use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
 use semver::Version;
@@ -22,7 +22,7 @@ use crate::{
     cfg_flag::CfgFlag,
     project_json::Crate,
     rustc_cfg::{self, RustcCfgConfig},
-    sysroot::SysrootCrate,
+    sysroot::{SysrootCrate, SysrootMode},
     target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
     Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
 };
@@ -130,7 +130,7 @@ impl fmt::Debug for ProjectWorkspace {
                 let mut debug_struct = f.debug_struct("Json");
                 debug_struct.field("n_crates", &project.n_crates());
                 if let Ok(sysroot) = sysroot {
-                    debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
+                    debug_struct.field("n_sysroot_crates", &sysroot.num_packages());
                 }
                 debug_struct.field("toolchain", &toolchain);
                 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
@@ -208,23 +208,23 @@ impl ProjectWorkspace {
 
                 let sysroot = match (&config.sysroot, &config.sysroot_src) {
                     (Some(RustLibSource::Path(path)), None) => {
-                        Sysroot::with_sysroot_dir(path.clone()).map_err(|e| {
+                        Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata).map_err(|e| {
                           Some(format!("Failed to find sysroot at {path}:{e}"))
                         })
                     }
                     (Some(RustLibSource::Discover), None) => {
-                        Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| {
+                        Sysroot::discover(cargo_toml.parent(), &config.extra_env, config.sysroot_query_metadata).map_err(|e| {
                             Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
                         })
                     }
                     (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
-                        Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone()))
+                        Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone(), config.sysroot_query_metadata))
                     }
                     (Some(RustLibSource::Discover), Some(sysroot_src)) => {
                         Sysroot::discover_with_src_override(
                             cargo_toml.parent(),
                             &config.extra_env,
-                            sysroot_src.clone(),
+                            sysroot_src.clone(), config.sysroot_query_metadata,
                         ).map_err(|e| {
                             Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
                         })
@@ -317,12 +317,12 @@ impl ProjectWorkspace {
         toolchain: Option<Version>,
     ) -> ProjectWorkspace {
         let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
-            (Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src)),
+            (Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src, false)),
             (Some(sysroot), None) => {
                 // assume sysroot is structured like rustup's and guess `sysroot_src`
                 let sysroot_src =
                     sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
-                Ok(Sysroot::load(sysroot, sysroot_src))
+                Ok(Sysroot::load(sysroot, sysroot_src, false))
             }
             (None, Some(sysroot_src)) => {
                 // assume sysroot is structured like rustup's and guess `sysroot`
@@ -330,7 +330,7 @@ impl ProjectWorkspace {
                 for _ in 0..5 {
                     sysroot.pop();
                 }
-                Ok(Sysroot::load(sysroot, sysroot_src))
+                Ok(Sysroot::load(sysroot, sysroot_src, false))
             }
             (None, None) => Err(None),
         };
@@ -354,16 +354,22 @@ impl ProjectWorkspace {
         config: &CargoConfig,
     ) -> anyhow::Result<ProjectWorkspace> {
         let sysroot = match &config.sysroot {
-            Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone())
-                .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))),
+            Some(RustLibSource::Path(path)) => {
+                Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata)
+                    .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}")))
+            }
             Some(RustLibSource::Discover) => {
                 let dir = &detached_files
                     .first()
                     .and_then(|it| it.parent())
                     .ok_or_else(|| format_err!("No detached files to load"))?;
-                Sysroot::discover(dir, &config.extra_env).map_err(|e| {
-                    Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
-                })
+                Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata).map_err(
+                    |e| {
+                        Some(format!(
+                            "Failed to find sysroot for {dir}. Is rust-src installed? {e}"
+                        ))
+                    },
+                )
             }
             None => Err(None),
         };
@@ -494,13 +500,43 @@ impl ProjectWorkspace {
     /// The return type contains the path and whether or not
     /// the root is a member of the current workspace
     pub fn to_roots(&self) -> Vec<PackageRoot> {
-        let mk_sysroot = |sysroot: Result<&Sysroot, _>, project_root: Option<&AbsPath>| {
-            sysroot.map(|sysroot| PackageRoot {
-                // mark the sysroot as mutable if it is located inside of the project
-                is_local: project_root
-                    .map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
-                include: vec![sysroot.src_root().to_path_buf()],
-                exclude: Vec::new(),
+        let mk_sysroot = |sysroot: Result<_, _>, project_root: Option<&AbsPath>| {
+            let project_root = project_root.map(ToOwned::to_owned);
+            sysroot.into_iter().flat_map(move |sysroot: &Sysroot| {
+                let mut r = match sysroot.mode() {
+                    SysrootMode::Workspace(ws) => ws
+                        .packages()
+                        .filter_map(|pkg| {
+                            if ws[pkg].is_local {
+                                // the local ones are included in the main `PackageRoot`` below
+                                return None;
+                            }
+                            let pkg_root = ws[pkg].manifest.parent().to_path_buf();
+
+                            let include = vec![pkg_root.clone()];
+
+                            let exclude = vec![
+                                pkg_root.join(".git"),
+                                pkg_root.join("target"),
+                                pkg_root.join("tests"),
+                                pkg_root.join("examples"),
+                                pkg_root.join("benches"),
+                            ];
+                            Some(PackageRoot { is_local: false, include, exclude })
+                        })
+                        .collect(),
+                    SysrootMode::Stitched(_) => vec![],
+                };
+
+                r.push(PackageRoot {
+                    // mark the sysroot as mutable if it is located inside of the project
+                    is_local: project_root
+                        .as_ref()
+                        .map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
+                    include: vec![sysroot.src_root().to_path_buf()],
+                    exclude: Vec::new(),
+                });
+                r
             })
         };
         match self {
@@ -588,16 +624,16 @@ impl ProjectWorkspace {
     pub fn n_packages(&self) -> usize {
         match self {
             ProjectWorkspace::Json { project, sysroot, .. } => {
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
+                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
                 sysroot_package_len + project.n_crates()
             }
             ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
                 let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len());
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
+                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
                 cargo.packages().len() + sysroot_package_len + rustc_package_len
             }
             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
+                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
                 sysroot_package_len + files.len()
             }
         }
@@ -638,7 +674,6 @@ impl ProjectWorkspace {
                 sysroot.as_ref().ok(),
                 rustc_cfg.clone(),
                 cfg_overrides,
-                None,
                 build_scripts,
                 match target_layout.as_ref() {
                     Ok(it) => Ok(Arc::from(it.as_str())),
@@ -849,8 +884,6 @@ fn cargo_to_crate_graph(
     sysroot: Option<&Sysroot>,
     rustc_cfg: Vec<CfgFlag>,
     override_cfg: &CfgOverrides,
-    // Don't compute cfg and use this if present, only used for the sysroot experiment hack
-    forced_cfg: Option<CfgOptions>,
     build_scripts: &WorkspaceBuildScripts,
     target_layout: TargetLayoutLoadResult,
     toolchain: Option<&Version>,
@@ -883,7 +916,7 @@ fn cargo_to_crate_graph(
     for pkg in cargo.packages() {
         has_private |= cargo[pkg].metadata.rustc_private;
 
-        let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
+        let cfg_options = {
             let mut cfg_options = cfg_options.clone();
 
             // Add test cfg for local crates
@@ -908,7 +941,7 @@ fn cargo_to_crate_graph(
                 cfg_options.apply_diff(diff.clone());
             };
             cfg_options
-        });
+        };
 
         let mut lib_tgt = None;
         for &tgt in cargo[pkg].targets.iter() {
@@ -1349,124 +1382,126 @@ fn sysroot_to_crate_graph(
     toolchain: Option<&Version>,
 ) -> (SysrootPublicDeps, Option<CrateId>) {
     let _p = profile::span("sysroot_to_crate_graph");
-    let cfg_options = create_cfg_options(rustc_cfg.clone());
-    let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = match &sysroot.hack_cargo_workspace {
-        Some(cargo) => handle_hack_cargo_workspace(
-            load,
-            cargo,
-            rustc_cfg,
-            cfg_options,
-            target_layout,
-            toolchain,
-            crate_graph,
-            sysroot,
-        ),
-        None => sysroot
-            .crates()
-            .filter_map(|krate| {
-                let file_id = load(&sysroot[krate].root)?;
-
-                let env = Env::default();
-                let display_name =
-                    CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
-                let crate_id = crate_graph.add_crate_root(
-                    file_id,
-                    Edition::CURRENT,
-                    Some(display_name),
-                    None,
-                    cfg_options.clone(),
-                    None,
-                    env,
-                    false,
-                    CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
-                    target_layout.clone(),
-                    toolchain.cloned(),
-                );
-                Some((krate, crate_id))
-            })
-            .collect(),
-    };
-    for from in sysroot.crates() {
-        for &to in sysroot[from].deps.iter() {
-            let name = CrateName::new(&sysroot[to].name).unwrap();
-            if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
-                add_dep(crate_graph, from, name, to, DependencyKind::Normal);
+    match sysroot.mode() {
+        SysrootMode::Workspace(cargo) => {
+            let (mut cg, mut pm) = cargo_to_crate_graph(
+                load,
+                None,
+                cargo,
+                None,
+                rustc_cfg,
+                &CfgOverrides::default(),
+                &WorkspaceBuildScripts::default(),
+                target_layout,
+                toolchain,
+            );
+
+            let mut pub_deps = vec![];
+            let mut libproc_macro = None;
+            let diff = CfgDiff::new(vec![], vec![CfgAtom::Flag("test".into())]).unwrap();
+            for (cid, c) in cg.iter_mut() {
+                // uninject `test` flag so `core` keeps working.
+                c.cfg_options.apply_diff(diff.clone());
+                // patch the origin
+                if c.origin.is_local() {
+                    let lang_crate = LangCrateOrigin::from(
+                        c.display_name.as_ref().map_or("", |it| it.canonical_name()),
+                    );
+                    c.origin = CrateOrigin::Lang(lang_crate);
+                    match lang_crate {
+                        LangCrateOrigin::Test
+                        | LangCrateOrigin::Alloc
+                        | LangCrateOrigin::Core
+                        | LangCrateOrigin::Std => pub_deps.push((
+                            CrateName::normalize_dashes(&lang_crate.to_string()),
+                            cid,
+                            !matches!(lang_crate, LangCrateOrigin::Test),
+                        )),
+                        LangCrateOrigin::ProcMacro => libproc_macro = Some(cid),
+                        LangCrateOrigin::Other => (),
+                    }
+                }
+            }
+
+            let mut marker_set = vec![];
+            for &(_, cid, _) in pub_deps.iter() {
+                marker_set.extend(cg.transitive_deps(cid));
             }
+            if let Some(cid) = libproc_macro {
+                marker_set.extend(cg.transitive_deps(cid));
+            }
+
+            marker_set.sort();
+            marker_set.dedup();
+
+            // Remove all crates except the ones we are interested in to keep the sysroot graph small.
+            let removed_mapping = cg.remove_crates_except(&marker_set);
+
+            crate_graph.extend(cg, &mut pm, |mapping| {
+                // Map the id through the removal mapping first, then through the crate graph extension mapping.
+                pub_deps.iter_mut().for_each(|(_, cid, _)| {
+                    *cid = mapping[&removed_mapping[cid.into_raw().into_u32() as usize].unwrap()]
+                });
+                if let Some(libproc_macro) = &mut libproc_macro {
+                    *libproc_macro = mapping
+                        [&removed_mapping[libproc_macro.into_raw().into_u32() as usize].unwrap()];
+                }
+            });
+
+            (SysrootPublicDeps { deps: pub_deps }, libproc_macro)
         }
-    }
+        SysrootMode::Stitched(stitched) => {
+            let cfg_options = create_cfg_options(rustc_cfg.clone());
+            let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = stitched
+                .crates()
+                .filter_map(|krate| {
+                    let file_id = load(&stitched[krate].root)?;
 
-    let public_deps = SysrootPublicDeps {
-        deps: sysroot
-            .public_deps()
-            .filter_map(|(name, idx, prelude)| Some((name, *sysroot_crates.get(&idx)?, prelude)))
-            .collect::<Vec<_>>(),
-    };
+                    let env = Env::default();
+                    let display_name =
+                        CrateDisplayName::from_canonical_name(stitched[krate].name.clone());
+                    let crate_id = crate_graph.add_crate_root(
+                        file_id,
+                        Edition::CURRENT,
+                        Some(display_name),
+                        None,
+                        cfg_options.clone(),
+                        None,
+                        env,
+                        false,
+                        CrateOrigin::Lang(LangCrateOrigin::from(&*stitched[krate].name)),
+                        target_layout.clone(),
+                        toolchain.cloned(),
+                    );
+                    Some((krate, crate_id))
+                })
+                .collect();
+
+            for from in stitched.crates() {
+                for &to in stitched[from].deps.iter() {
+                    let name = CrateName::new(&stitched[to].name).unwrap();
+                    if let (Some(&from), Some(&to)) =
+                        (sysroot_crates.get(&from), sysroot_crates.get(&to))
+                    {
+                        add_dep(crate_graph, from, name, to, DependencyKind::Normal);
+                    }
+                }
+            }
 
-    let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
-    (public_deps, libproc_macro)
-}
+            let public_deps = SysrootPublicDeps {
+                deps: stitched
+                    .public_deps()
+                    .filter_map(|(name, idx, prelude)| {
+                        Some((name, *sysroot_crates.get(&idx)?, prelude))
+                    })
+                    .collect::<Vec<_>>(),
+            };
 
-fn handle_hack_cargo_workspace(
-    load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
-    cargo: &CargoWorkspace,
-    rustc_cfg: Vec<CfgFlag>,
-    cfg_options: CfgOptions,
-    target_layout: Result<Arc<str>, Arc<str>>,
-    toolchain: Option<&Version>,
-    crate_graph: &mut CrateGraph,
-    sysroot: &Sysroot,
-) -> FxHashMap<SysrootCrate, CrateId> {
-    let (cg, mut pm) = cargo_to_crate_graph(
-        load,
-        None,
-        cargo,
-        None,
-        rustc_cfg,
-        &CfgOverrides::default(),
-        Some(cfg_options),
-        &WorkspaceBuildScripts::default(),
-        target_layout,
-        toolchain,
-    );
-    crate_graph.extend(cg, &mut pm);
-    for crate_name in ["std", "alloc", "core"] {
-        let original = crate_graph
-            .iter()
-            .find(|x| {
-                crate_graph[*x]
-                    .display_name
-                    .as_ref()
-                    .map(|x| x.canonical_name() == crate_name)
-                    .unwrap_or(false)
-            })
-            .unwrap();
-        let fake_crate_name = format!("rustc-std-workspace-{}", crate_name);
-        let fake = crate_graph
-            .iter()
-            .find(|x| {
-                crate_graph[*x]
-                    .display_name
-                    .as_ref()
-                    .map(|x| x.canonical_name() == fake_crate_name)
-                    .unwrap_or(false)
-            })
-            .unwrap();
-        crate_graph.remove_and_replace(fake, original).unwrap();
-    }
-    for (_, c) in crate_graph.iter_mut() {
-        if c.origin.is_local() {
-            // LangCrateOrigin::Other is good enough for a hack.
-            c.origin = CrateOrigin::Lang(LangCrateOrigin::Other);
+            let libproc_macro =
+                stitched.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
+            (public_deps, libproc_macro)
         }
     }
-    sysroot
-        .crates()
-        .filter_map(|krate| {
-            let file_id = load(&sysroot[krate].root)?;
-            let crate_id = crate_graph.crate_id_for_crate_root(file_id)?;
-            Some((krate, crate_id))
-        })
-        .collect()
 }
 
 fn add_dep(
diff --git a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
index e35f0fc7327..0df99534c5b 100644
--- a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
+++ b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt
@@ -182,7 +182,7 @@
             },
         ],
         origin: Lang(
-            Other,
+            ProcMacro,
         ),
         is_proc_macro: false,
         target_layout: Err(
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index b8f6138161e..87bb3cbd341 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -61,9 +61,12 @@ impl Tester {
         cargo_config.sysroot = Some(RustLibSource::Discover);
         let workspace = ProjectWorkspace::DetachedFiles {
             files: vec![tmp_file.clone()],
-            sysroot: Ok(
-                Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env).unwrap()
-            ),
+            sysroot: Ok(Sysroot::discover(
+                tmp_file.parent().unwrap(),
+                &cargo_config.extra_env,
+                false,
+            )
+            .unwrap()),
             rustc_cfg: vec![],
         };
         let load_cargo_config = LoadCargoConfig {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index fe009f82a71..3ec5d869661 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -135,6 +135,13 @@ config_data! {
         ///
         /// This option does not take effect until rust-analyzer is restarted.
         cargo_sysroot: Option<String>    = "\"discover\"",
+        /// Whether to run cargo metadata on the sysroot library allowing rust-analyzer to analyze
+        /// third-party dependencies of the standard libraries.
+        ///
+        /// This will cause `cargo` to create a lockfile in your sysroot directory. rust-analyzer
+        /// will attempt to clean up afterwards, but nevertheless requires the location to be
+        /// writable to.
+        cargo_sysrootQueryMetadata: bool     = "false",
         /// Relative path to the sysroot library sources. If left unset, this will default to
         /// `{cargo.sysroot}/lib/rustlib/src/rust/library`.
         ///
@@ -1233,6 +1240,7 @@ impl Config {
         });
         let sysroot_src =
             self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
+        let sysroot_query_metadata = self.data.cargo_sysrootQueryMetadata;
 
         CargoConfig {
             features: match &self.data.cargo_features {
@@ -1244,6 +1252,7 @@ impl Config {
             },
             target: self.data.cargo_target.clone(),
             sysroot,
+            sysroot_query_metadata,
             sysroot_src,
             rustc_source,
             cfg_overrides: project_model::CfgOverrides {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 8e3fa7fa4da..24af5fb49c0 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -516,7 +516,7 @@ impl GlobalState {
             for ws in &**self.workspaces {
                 let (other, mut crate_proc_macros) =
                     ws.to_crate_graph(&mut load, &self.config.extra_env());
-                crate_graph.extend(other, &mut crate_proc_macros);
+                crate_graph.extend(other, &mut crate_proc_macros, |_| {});
                 proc_macros.push(crate_proc_macros);
             }
             (crate_graph, proc_macros, crate_graph_file_dependencies)
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index ecc90abff13..1a2791954e6 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -121,6 +121,16 @@ Unsetting this disables sysroot loading.
 
 This option does not take effect until rust-analyzer is restarted.
 --
+[[rust-analyzer.cargo.sysrootQueryMetadata]]rust-analyzer.cargo.sysrootQueryMetadata (default: `false`)::
++
+--
+Whether to run cargo metadata on the sysroot library allowing rust-analyzer to analyze
+third-party dependencies of the standard libraries.
+
+This will cause `cargo` to create a lockfile in your sysroot directory. rust-analyzer
+will attempt to clean up afterwards, but nevertheless requires the location to be
+writable to.
+--
 [[rust-analyzer.cargo.sysrootSrc]]rust-analyzer.cargo.sysrootSrc (default: `null`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 8307f6833e6..e7ceee165cd 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -648,6 +648,11 @@
                         "string"
                     ]
                 },
+                "rust-analyzer.cargo.sysrootQueryMetadata": {
+                    "markdownDescription": "Whether to run cargo metadata on the sysroot library allowing rust-analyzer to analyze\nthird-party dependencies of the standard libraries.\n\nThis will cause `cargo` to create a lockfile in your sysroot directory. rust-analyzer\nwill attempt to clean up afterwards, but nevertheless requires the location to be\nwritable to.",
+                    "default": false,
+                    "type": "boolean"
+                },
                 "rust-analyzer.cargo.sysrootSrc": {
                     "markdownDescription": "Relative path to the sysroot library sources. If left unset, this will default to\n`{cargo.sysroot}/lib/rustlib/src/rust/library`.\n\nThis option does not take effect until rust-analyzer is restarted.",
                     "default": null,