about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-02-12 12:08:18 +0100
committerLukas Wirth <lukastw97@gmail.com>2024-02-12 12:08:18 +0100
commit8f3209ba277c0022bb1cfb2f28bae0da5f1f1100 (patch)
tree7f2494180ffebecc9fa0af34e6cc3cdff5ed5d14
parentddf105b646c6749a2de2451c9a499a354eec79c2 (diff)
downloadrust-8f3209ba277c0022bb1cfb2f28bae0da5f1f1100.tar.gz
rust-8f3209ba277c0022bb1cfb2f28bae0da5f1f1100.zip
internal: tool discovery prefers sysroot tools
-rw-r--r--crates/flycheck/src/lib.rs9
-rw-r--r--crates/hir-ty/src/layout/tests.rs8
-rw-r--r--crates/project-model/src/build_scripts.rs19
-rw-r--r--crates/project-model/src/cargo_workspace.rs25
-rw-r--r--crates/project-model/src/rustc_cfg.rs53
-rw-r--r--crates/project-model/src/sysroot.rs126
-rw-r--r--crates/project-model/src/target_data_layout.rs46
-rw-r--r--crates/project-model/src/tests.rs11
-rw-r--r--crates/project-model/src/workspace.rs254
-rw-r--r--crates/project-model/test_data/output/rust_project_hello_world_project_model.txt22
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs24
-rw-r--r--crates/rust-analyzer/tests/slow-tests/main.rs16
-rw-r--r--crates/toolchain/src/lib.rs42
14 files changed, 406 insertions, 250 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index c59aff2a8bb..71ff3e7b0e6 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -89,9 +89,10 @@ impl FlycheckHandle {
         id: usize,
         sender: Box<dyn Fn(Message) + Send>,
         config: FlycheckConfig,
+        cargo: PathBuf,
         workspace_root: AbsPathBuf,
     ) -> FlycheckHandle {
-        let actor = FlycheckActor::new(id, sender, config, workspace_root);
+        let actor = FlycheckActor::new(id, sender, config, cargo, workspace_root);
         let (sender, receiver) = unbounded::<StateChange>();
         let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
             .name("Flycheck".to_owned())
@@ -171,6 +172,7 @@ struct FlycheckActor {
     /// Either the workspace root of the workspace we are flychecking,
     /// or the project root of the project.
     root: AbsPathBuf,
+    cargo: PathBuf,
     /// CargoHandle exists to wrap around the communication needed to be able to
     /// run `cargo check` without blocking. Currently the Rust standard library
     /// doesn't provide a way to read sub-process output without blocking, so we
@@ -189,10 +191,11 @@ impl FlycheckActor {
         id: usize,
         sender: Box<dyn Fn(Message) + Send>,
         config: FlycheckConfig,
+        cargo: PathBuf,
         workspace_root: AbsPathBuf,
     ) -> FlycheckActor {
         tracing::info!(%id, ?workspace_root, "Spawning flycheck");
-        FlycheckActor { id, sender, config, root: workspace_root, command_handle: None }
+        FlycheckActor { id, sender, config, cargo, root: workspace_root, command_handle: None }
     }
 
     fn report_progress(&self, progress: Progress) {
@@ -316,7 +319,7 @@ impl FlycheckActor {
                 ansi_color_output,
                 target_dir,
             } => {
-                let mut cmd = Command::new(toolchain::cargo());
+                let mut cmd = Command::new(&self.cargo);
                 cmd.arg(command);
                 cmd.current_dir(&self.root);
 
diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs
index ba3dfe8100d..ff53f144861 100644
--- a/crates/hir-ty/src/layout/tests.rs
+++ b/crates/hir-ty/src/layout/tests.rs
@@ -1,6 +1,7 @@
 use chalk_ir::{AdtId, TyKind};
 use either::Either;
 use hir_def::db::DefDatabase;
+use project_model::target_data_layout::RustcDataLayoutConfig;
 use rustc_hash::FxHashMap;
 use test_fixture::WithFixture;
 use triomphe::Arc;
@@ -15,7 +16,12 @@ use crate::{
 mod closure;
 
 fn current_machine_data_layout() -> String {
-    project_model::target_data_layout::get(None, None, &FxHashMap::default()).unwrap()
+    project_model::target_data_layout::get(
+        RustcDataLayoutConfig::Rustc(None),
+        None,
+        &FxHashMap::default(),
+    )
+    .unwrap()
 }
 
 fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutError> {
diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs
index a2c9856a3f7..51b9c8f5769 100644
--- a/crates/project-model/src/build_scripts.rs
+++ b/crates/project-model/src/build_scripts.rs
@@ -20,10 +20,11 @@ use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
 use semver::Version;
 use serde::Deserialize;
+use toolchain::Tool;
 
 use crate::{
     cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
-    InvocationStrategy, Package,
+    InvocationStrategy, Package, Sysroot,
 };
 
 #[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -61,6 +62,7 @@ impl WorkspaceBuildScripts {
         config: &CargoConfig,
         allowed_features: &FxHashSet<String>,
         workspace_root: &AbsPathBuf,
+        sysroot: Option<&Sysroot>,
     ) -> io::Result<Command> {
         let mut cmd = match config.run_build_script_command.as_deref() {
             Some([program, args @ ..]) => {
@@ -69,7 +71,10 @@ impl WorkspaceBuildScripts {
                 cmd
             }
             _ => {
-                let mut cmd = Command::new(toolchain::cargo());
+                let mut cmd = Command::new(
+                    Sysroot::discover_tool(sysroot, Tool::Cargo)
+                        .map_err(|e| io::Error::new(io::ErrorKind::NotFound, e))?,
+                );
 
                 cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
                 cmd.args(&config.extra_args);
@@ -133,6 +138,7 @@ impl WorkspaceBuildScripts {
         workspace: &CargoWorkspace,
         progress: &dyn Fn(String),
         toolchain: &Option<Version>,
+        sysroot: Option<&Sysroot>,
     ) -> io::Result<WorkspaceBuildScripts> {
         const RUST_1_62: Version = Version::new(1, 62, 0);
 
@@ -151,6 +157,7 @@ impl WorkspaceBuildScripts {
                 config,
                 &allowed_features,
                 &workspace.workspace_root().to_path_buf(),
+                sysroot,
             )?,
             workspace,
             current_dir,
@@ -165,6 +172,7 @@ impl WorkspaceBuildScripts {
                     config,
                     &allowed_features,
                     &workspace.workspace_root().to_path_buf(),
+                    sysroot,
                 )?;
                 cmd.args(["-Z", "unstable-options", "--keep-going"]).env("RUSTC_BOOTSTRAP", "1");
                 let mut res = Self::run_per_ws(cmd, workspace, current_dir, progress)?;
@@ -194,7 +202,7 @@ impl WorkspaceBuildScripts {
                 ))
             }
         };
-        let cmd = Self::build_command(config, &Default::default(), workspace_root)?;
+        let cmd = Self::build_command(config, &Default::default(), workspace_root, None)?;
         // NB: Cargo.toml could have been modified between `cargo metadata` and
         // `cargo check`. We shouldn't assume that package ids we see here are
         // exactly those from `config`.
@@ -415,6 +423,7 @@ impl WorkspaceBuildScripts {
         rustc: &CargoWorkspace,
         current_dir: &AbsPath,
         extra_env: &FxHashMap<String, String>,
+        sysroot: Option<&Sysroot>,
     ) -> Self {
         let mut bs = WorkspaceBuildScripts::default();
         for p in rustc.packages() {
@@ -422,7 +431,7 @@ impl WorkspaceBuildScripts {
         }
         let res = (|| {
             let target_libdir = (|| {
-                let mut cargo_config = Command::new(toolchain::cargo());
+                let mut cargo_config = Command::new(Sysroot::discover_tool(sysroot, Tool::Cargo)?);
                 cargo_config.envs(extra_env);
                 cargo_config
                     .current_dir(current_dir)
@@ -431,7 +440,7 @@ impl WorkspaceBuildScripts {
                 if let Ok(it) = utf8_stdout(cargo_config) {
                     return Ok(it);
                 }
-                let mut cmd = Command::new(toolchain::rustc());
+                let mut cmd = Command::new(Sysroot::discover_tool(sysroot, Tool::Rustc)?);
                 cmd.envs(extra_env);
                 cmd.args(["--print", "target-libdir"]);
                 utf8_stdout(cmd)
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index a99ee6e664c..dae03afde22 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -12,8 +12,9 @@ use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
 use serde::Deserialize;
 use serde_json::from_value;
+use toolchain::Tool;
 
-use crate::{utf8_stdout, InvocationLocation, ManifestPath};
+use crate::{utf8_stdout, InvocationLocation, ManifestPath, Sysroot};
 use crate::{CfgOverrides, InvocationStrategy};
 
 /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
@@ -236,12 +237,13 @@ impl CargoWorkspace {
         cargo_toml: &ManifestPath,
         current_dir: &AbsPath,
         config: &CargoConfig,
+        sysroot: Option<&Sysroot>,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<cargo_metadata::Metadata> {
-        let targets = find_list_of_build_targets(config, cargo_toml);
+        let targets = find_list_of_build_targets(config, cargo_toml, sysroot);
 
         let mut meta = MetadataCommand::new();
-        meta.cargo_path(toolchain::cargo());
+        meta.cargo_path(Sysroot::discover_tool(sysroot, Tool::Cargo)?);
         meta.manifest_path(cargo_toml.to_path_buf());
         match &config.features {
             CargoFeatures::All => {
@@ -476,24 +478,29 @@ impl CargoWorkspace {
     }
 }
 
-fn find_list_of_build_targets(config: &CargoConfig, cargo_toml: &ManifestPath) -> Vec<String> {
+fn find_list_of_build_targets(
+    config: &CargoConfig,
+    cargo_toml: &ManifestPath,
+    sysroot: Option<&Sysroot>,
+) -> Vec<String> {
     if let Some(target) = &config.target {
         return [target.into()].to_vec();
     }
 
-    let build_targets = cargo_config_build_target(cargo_toml, &config.extra_env);
+    let build_targets = cargo_config_build_target(cargo_toml, &config.extra_env, sysroot);
     if !build_targets.is_empty() {
         return build_targets;
     }
 
-    rustc_discover_host_triple(cargo_toml, &config.extra_env).into_iter().collect()
+    rustc_discover_host_triple(cargo_toml, &config.extra_env, sysroot).into_iter().collect()
 }
 
 fn rustc_discover_host_triple(
     cargo_toml: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
+    sysroot: Option<&Sysroot>,
 ) -> Option<String> {
-    let mut rustc = Command::new(toolchain::rustc());
+    let mut rustc = Command::new(Sysroot::discover_tool(sysroot, Tool::Rustc).ok()?);
     rustc.envs(extra_env);
     rustc.current_dir(cargo_toml.parent()).arg("-vV");
     tracing::debug!("Discovering host platform by {:?}", rustc);
@@ -519,8 +526,10 @@ fn rustc_discover_host_triple(
 fn cargo_config_build_target(
     cargo_toml: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
+    sysroot: Option<&Sysroot>,
 ) -> Vec<String> {
-    let mut cargo_config = Command::new(toolchain::cargo());
+    let Ok(program) = Sysroot::discover_tool(sysroot, Tool::Cargo) else { return vec![] };
+    let mut cargo_config = Command::new(program);
     cargo_config.envs(extra_env);
     cargo_config
         .current_dir(cargo_toml.parent())
diff --git a/crates/project-model/src/rustc_cfg.rs b/crates/project-model/src/rustc_cfg.rs
index 0aee002fbb3..efbdfffd9a4 100644
--- a/crates/project-model/src/rustc_cfg.rs
+++ b/crates/project-model/src/rustc_cfg.rs
@@ -8,17 +8,13 @@ use rustc_hash::FxHashMap;
 use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot};
 
 /// Determines how `rustc --print cfg` is discovered and invoked.
-///
-/// There options are supported:
-/// - [`RustcCfgConfig::Cargo`], which relies on `cargo rustc --print cfg`
-///   and `RUSTC_BOOTSTRAP`.
-/// - [`RustcCfgConfig::Explicit`], which uses an explicit path to the `rustc`
-///   binary in the sysroot.
-/// - [`RustcCfgConfig::Discover`], which uses [`toolchain::rustc`].
 pub(crate) enum RustcCfgConfig<'a> {
-    Cargo(&'a ManifestPath),
-    Explicit(&'a Sysroot),
-    Discover,
+    /// Use `rustc --print cfg`, either from with the binary from the sysroot or by discovering via
+    /// [`toolchain::rustc`].
+    Rustc(Option<&'a Sysroot>),
+    /// Use `cargo --print cfg`, either from with the binary from the sysroot or by discovering via
+    /// [`toolchain::cargo`].
+    Cargo(Option<&'a Sysroot>, &'a ManifestPath),
 }
 
 pub(crate) fn get(
@@ -71,36 +67,31 @@ fn get_rust_cfgs(
     extra_env: &FxHashMap<String, String>,
     config: RustcCfgConfig<'_>,
 ) -> anyhow::Result<String> {
-    let mut cmd = match config {
-        RustcCfgConfig::Cargo(cargo_toml) => {
-            let mut cmd = Command::new(toolchain::cargo());
+    match config {
+        RustcCfgConfig::Cargo(sysroot, cargo_oml) => {
+            let cargo = Sysroot::discover_tool(sysroot, toolchain::Tool::Cargo)?;
+            let mut cmd = Command::new(cargo);
             cmd.envs(extra_env);
-            cmd.current_dir(cargo_toml.parent())
+            cmd.current_dir(cargo_oml.parent())
                 .args(["rustc", "-Z", "unstable-options", "--print", "cfg"])
                 .env("RUSTC_BOOTSTRAP", "1");
             if let Some(target) = target {
                 cmd.args(["--target", target]);
             }
 
-            return utf8_stdout(cmd).context("Unable to run `cargo rustc`");
+            utf8_stdout(cmd).context("Unable to run `cargo rustc`")
         }
-        RustcCfgConfig::Explicit(sysroot) => {
-            let rustc: std::path::PathBuf = sysroot.discover_rustc()?.into();
+        RustcCfgConfig::Rustc(sysroot) => {
+            let rustc = Sysroot::discover_tool(sysroot, toolchain::Tool::Rustc)?;
             tracing::debug!(?rustc, "using explicit rustc from sysroot");
-            Command::new(rustc)
-        }
-        RustcCfgConfig::Discover => {
-            let rustc = toolchain::rustc();
-            tracing::debug!(?rustc, "using rustc from env");
-            Command::new(rustc)
-        }
-    };
+            let mut cmd = Command::new(rustc);
+            cmd.envs(extra_env);
+            cmd.args(["--print", "cfg", "-O"]);
+            if let Some(target) = target {
+                cmd.args(["--target", target]);
+            }
 
-    cmd.envs(extra_env);
-    cmd.args(["--print", "cfg", "-O"]);
-    if let Some(target) = target {
-        cmd.args(["--target", target]);
+            utf8_stdout(cmd).context("Unable to run `rustc`")
+        }
     }
-
-    utf8_stdout(cmd).context("Unable to run `rustc`")
 }
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index 9e19a525838..b0a8f0d4a41 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -4,7 +4,7 @@
 //! but we can't process `.rlib` and need source code instead. The source code
 //! is typically installed with `rustup component add rust-src` command.
 
-use std::{env, fs, iter, ops, path::PathBuf, process::Command};
+use std::{env, fs, iter, ops, path::PathBuf, process::Command, sync::Arc};
 
 use anyhow::{format_err, Context, Result};
 use base_db::CrateName;
@@ -12,16 +12,30 @@ use itertools::Itertools;
 use la_arena::{Arena, Idx};
 use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::FxHashMap;
+use toolchain::{probe_for_binary, Tool};
 
 use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath};
 
-#[derive(Debug, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone)]
 pub struct Sysroot {
     root: AbsPathBuf,
-    src_root: AbsPathBuf,
+    src_root: Option<Result<AbsPathBuf, Arc<anyhow::Error>>>,
     mode: SysrootMode,
 }
 
+impl Eq for Sysroot {}
+impl PartialEq for Sysroot {
+    fn eq(&self, other: &Self) -> bool {
+        self.root == other.root
+            && self.mode == other.mode
+            && match (&self.src_root, &other.src_root) {
+                (Some(Ok(this)), Some(Ok(other))) => this == other,
+                (None, None) | (Some(Err(_)), Some(Err(_))) => true,
+                _ => false,
+            }
+    }
+}
+
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub(crate) enum SysrootMode {
     Workspace(CargoWorkspace),
@@ -86,8 +100,8 @@ impl Sysroot {
 
     /// 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 src_root(&self) -> Option<&AbsPath> {
+        self.src_root.as_ref()?.as_deref().ok()
     }
 
     pub fn is_empty(&self) -> bool {
@@ -98,6 +112,11 @@ impl Sysroot {
     }
 
     pub fn loading_warning(&self) -> Option<String> {
+        let src_root = match &self.src_root {
+            None => return Some(format!("sysroot at `{}` has no library sources", self.root)),
+            Some(Ok(src_root)) => src_root,
+            Some(Err(e)) => return Some(e.to_string()),
+        };
         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(),
@@ -108,10 +127,7 @@ impl Sysroot {
             } else {
                 " try running `rustup component add rust-src` to possible fix this"
             };
-            Some(format!(
-                "could not find libcore in loaded sysroot at `{}`{var_note}",
-                self.src_root.as_path(),
-            ))
+            Some(format!("could not find libcore in loaded sysroot at `{}`{var_note}", src_root,))
         } else {
             None
         }
@@ -140,8 +156,19 @@ impl 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, metadata))
+            discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env);
+        Ok(Sysroot::load(sysroot_dir, Some(sysroot_src_dir), metadata))
+    }
+
+    pub fn discover_no_source(
+        dir: &AbsPath,
+        extra_env: &FxHashMap<String, String>,
+    ) -> 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, Some(sysroot_src_dir), false))
     }
 
     pub fn discover_with_src_override(
@@ -152,33 +179,73 @@ impl Sysroot {
     ) -> 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, metadata))
+        Ok(Sysroot::load(sysroot_dir, Some(Ok(src)), metadata))
     }
 
     pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
         get_rustc_src(&self.root)
     }
 
-    pub fn discover_rustc(&self) -> anyhow::Result<AbsPathBuf> {
-        let rustc = self.root.join("bin/rustc");
-        tracing::debug!(?rustc, "checking for rustc binary at location");
-        match fs::metadata(&rustc) {
-            Ok(_) => Ok(rustc),
-            Err(e) => Err(e).context(format!(
-                "failed to discover rustc in sysroot: {:?}",
-                AsRef::<std::path::Path>::as_ref(&self.root)
-            )),
-        }
-    }
-
     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, metadata))
+        });
+        Ok(Sysroot::load(sysroot_dir, Some(sysroot_src_dir), metadata))
     }
 
-    pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf, metadata: bool) -> Sysroot {
+    pub fn discover_binary(&self, binary: &str) -> anyhow::Result<AbsPathBuf> {
+        toolchain::probe_for_binary(self.root.join("bin").join(binary).into())
+            .ok_or_else(|| anyhow::anyhow!("no rustc binary found in {}", self.root.join("bin")))
+            .and_then(|rustc| {
+                fs::metadata(&rustc).map(|_| AbsPathBuf::assert(rustc)).with_context(|| {
+                    format!(
+                        "failed to discover rustc in sysroot: {:?}",
+                        AsRef::<std::path::Path>::as_ref(&self.root)
+                    )
+                })
+            })
+    }
+
+    pub fn discover_tool(sysroot: Option<&Self>, tool: Tool) -> anyhow::Result<PathBuf> {
+        match sysroot {
+            Some(sysroot) => sysroot.discover_binary(tool.name()).map(Into::into),
+            None => Ok(tool.path()),
+        }
+    }
+
+    pub fn discover_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
+        ["libexec", "lib"]
+            .into_iter()
+            .map(|segment| self.root().join(segment).join("rust-analyzer-proc-macro-srv"))
+            .find_map(|server_path| probe_for_binary(server_path.into()))
+            .map(AbsPathBuf::assert)
+            .ok_or_else(|| {
+                anyhow::format_err!("cannot find proc-macro server in sysroot `{}`", self.root())
+            })
+    }
+
+    pub fn load(
+        sysroot_dir: AbsPathBuf,
+        sysroot_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
+        metadata: bool,
+    ) -> Sysroot {
+        let sysroot_src_dir = match sysroot_src_dir {
+            Some(Ok(sysroot_src_dir)) => sysroot_src_dir,
+            Some(Err(e)) => {
+                return Sysroot {
+                    root: sysroot_dir,
+                    src_root: Some(Err(Arc::new(e))),
+                    mode: SysrootMode::Stitched(Stitched { crates: Arena::default() }),
+                }
+            }
+            None => {
+                return Sysroot {
+                    root: sysroot_dir,
+                    src_root: None,
+                    mode: SysrootMode::Stitched(Stitched { crates: Arena::default() }),
+                }
+            }
+        };
         if metadata {
             let sysroot: Option<_> = (|| {
                 let sysroot_cargo_toml = ManifestPath::try_from(
@@ -191,6 +258,7 @@ impl Sysroot {
                     &sysroot_cargo_toml,
                     &current_dir,
                     &CargoConfig::default(),
+                    None,
                     &|_| (),
                 )
                 .map_err(|e| {
@@ -274,7 +342,7 @@ impl Sysroot {
                 let cargo_workspace = CargoWorkspace::new(res);
                 Some(Sysroot {
                     root: sysroot_dir.clone(),
-                    src_root: sysroot_src_dir.clone(),
+                    src_root: Some(Ok(sysroot_src_dir.clone())),
                     mode: SysrootMode::Workspace(cargo_workspace),
                 })
             })();
@@ -326,7 +394,7 @@ impl Sysroot {
         }
         Sysroot {
             root: sysroot_dir,
-            src_root: sysroot_src_dir,
+            src_root: Some(Ok(sysroot_src_dir)),
             mode: SysrootMode::Stitched(stitched),
         }
     }
diff --git a/crates/project-model/src/target_data_layout.rs b/crates/project-model/src/target_data_layout.rs
index cb995857ec7..847829be180 100644
--- a/crates/project-model/src/target_data_layout.rs
+++ b/crates/project-model/src/target_data_layout.rs
@@ -3,16 +3,27 @@ use std::process::Command;
 
 use rustc_hash::FxHashMap;
 
-use crate::{utf8_stdout, ManifestPath};
+use crate::{utf8_stdout, ManifestPath, Sysroot};
+
+/// Determines how `rustc --print target-spec-json` is discovered and invoked.
+pub enum RustcDataLayoutConfig<'a> {
+    /// Use `rustc --print target-spec-json`, either from with the binary from the sysroot or by discovering via
+    /// [`toolchain::rustc`].
+    Rustc(Option<&'a Sysroot>),
+    /// Use `cargo --print target-spec-json`, either from with the binary from the sysroot or by discovering via
+    /// [`toolchain::cargo`].
+    Cargo(Option<&'a Sysroot>, &'a ManifestPath),
+}
 
 pub fn get(
-    cargo_toml: Option<&ManifestPath>,
+    config: RustcDataLayoutConfig<'_>,
     target: Option<&str>,
     extra_env: &FxHashMap<String, String>,
 ) -> anyhow::Result<String> {
-    let output = (|| {
-        if let Some(cargo_toml) = cargo_toml {
-            let mut cmd = Command::new(toolchain::rustc());
+    let output = match config {
+        RustcDataLayoutConfig::Cargo(sysroot, cargo_toml) => {
+            let cargo = Sysroot::discover_tool(sysroot, toolchain::Tool::Cargo)?;
+            let mut cmd = Command::new(cargo);
             cmd.envs(extra_env);
             cmd.current_dir(cargo_toml.parent())
                 .args(["-Z", "unstable-options", "--print", "target-spec-json"])
@@ -20,21 +31,20 @@ pub fn get(
             if let Some(target) = target {
                 cmd.args(["--target", target]);
             }
-            match utf8_stdout(cmd) {
-                Ok(it) => return Ok(it),
-                Err(e) => tracing::debug!("{e:?}: falling back to querying rustc for cfgs"),
-            }
+            utf8_stdout(cmd)
         }
-        // using unstable cargo features failed, fall back to using plain rustc
-        let mut cmd = Command::new(toolchain::rustc());
-        cmd.envs(extra_env)
-            .args(["-Z", "unstable-options", "--print", "target-spec-json"])
-            .env("RUSTC_BOOTSTRAP", "1");
-        if let Some(target) = target {
-            cmd.args(["--target", target]);
+        RustcDataLayoutConfig::Rustc(sysroot) => {
+            let rustc = Sysroot::discover_tool(sysroot, toolchain::Tool::Rustc)?;
+            let mut cmd = Command::new(rustc);
+            cmd.envs(extra_env)
+                .args(["-Z", "unstable-options", "--print", "target-spec-json"])
+                .env("RUSTC_BOOTSTRAP", "1");
+            if let Some(target) = target {
+                cmd.args(["--target", target]);
+            }
+            utf8_stdout(cmd)
         }
-        utf8_stdout(cmd)
-    })()?;
+    }?;
     (|| Some(output.split_once(r#""data-layout": ""#)?.1.split_once('"')?.0.to_owned()))()
         .ok_or_else(|| anyhow::format_err!("could not fetch target-spec-json from command output"))
 }
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 74042e925ed..75d48004b64 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -69,8 +69,13 @@ fn load_rust_project(file: &str) -> (CrateGraph, ProcMacroPaths) {
     let data = get_test_json_file(file);
     let project = rooted_project_json(data);
     let sysroot = Ok(get_fake_sysroot());
-    let project_workspace =
-        ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new(), toolchain: None };
+    let project_workspace = ProjectWorkspace::Json {
+        project,
+        sysroot,
+        rustc_cfg: Vec::new(),
+        toolchain: None,
+        target_layout: Err("test has no data layout".to_owned()),
+    };
     to_crate_graph(project_workspace)
 }
 
@@ -125,7 +130,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, false)
+    Sysroot::load(sysroot_dir, Some(Ok(sysroot_src_dir)), false)
 }
 
 fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index cda5ad2f110..80093925283 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -2,7 +2,9 @@
 //! metadata` or `rust-project.json`) into representation stored in the salsa
 //! database -- `CrateGraph`.
 
-use std::{collections::VecDeque, fmt, fs, iter, process::Command, str::FromStr, sync};
+use std::{
+    collections::VecDeque, fmt, fs, iter, path::PathBuf, process::Command, str::FromStr, sync,
+};
 
 use anyhow::{format_err, Context};
 use base_db::{
@@ -23,8 +25,9 @@ use crate::{
     project_json::Crate,
     rustc_cfg::{self, RustcCfgConfig},
     sysroot::{SysrootCrate, SysrootMode},
-    target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
-    Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
+    target_data_layout::{self, RustcDataLayoutConfig},
+    utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
+    ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
 };
 
 /// A set of cfg-overrides per crate.
@@ -79,6 +82,7 @@ pub enum ProjectWorkspace {
         /// `rustc --print cfg`.
         rustc_cfg: Vec<CfgFlag>,
         toolchain: Option<Version>,
+        target_layout: Result<String, String>,
     },
     // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
     // That's not the end user experience we should strive for.
@@ -126,14 +130,22 @@ impl fmt::Debug for ProjectWorkspace {
                 .field("toolchain", &toolchain)
                 .field("data_layout", &data_layout)
                 .finish(),
-            ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
+            ProjectWorkspace::Json {
+                project,
+                sysroot,
+                rustc_cfg,
+                toolchain,
+                target_layout: data_layout,
+            } => {
                 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.num_packages());
                 }
-                debug_struct.field("toolchain", &toolchain);
-                debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
+                debug_struct
+                    .field("toolchain", &toolchain)
+                    .field("n_rustc_cfg", &rustc_cfg.len())
+                    .field("data_layout", &data_layout);
                 debug_struct.finish()
             }
             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
@@ -146,6 +158,26 @@ impl fmt::Debug for ProjectWorkspace {
     }
 }
 
+fn get_toolchain_version(
+    current_dir: &AbsPath,
+    cmd_path: Result<PathBuf, anyhow::Error>,
+    extra_env: &FxHashMap<String, String>,
+    prefix: &str,
+) -> Result<Option<Version>, anyhow::Error> {
+    let cargo_version = utf8_stdout({
+        let mut cmd = Command::new(cmd_path?);
+        cmd.envs(extra_env);
+        cmd.arg("--version").current_dir(current_dir);
+        cmd
+    })
+    .with_context(|| format!("Failed to query rust toolchain version at {current_dir}, is your toolchain setup correctly?"))?;
+    anyhow::Ok(
+        cargo_version
+            .get(prefix.len()..)
+            .and_then(|it| Version::parse(it.split_whitespace().next()?).ok()),
+    )
+}
+
 impl ProjectWorkspace {
     pub fn load(
         manifest: ProjectManifest,
@@ -161,20 +193,6 @@ impl ProjectWorkspace {
         config: &CargoConfig,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<ProjectWorkspace> {
-        let version = |current_dir, cmd_path, prefix: &str| {
-            let cargo_version = utf8_stdout({
-                let mut cmd = Command::new(cmd_path);
-                cmd.envs(&config.extra_env);
-                cmd.arg("--version").current_dir(current_dir);
-                cmd
-            })
-            .with_context(|| format!("Failed to query rust toolchain version at {current_dir}, is your toolchain setup correctly?"))?;
-            anyhow::Ok(
-                cargo_version
-                    .get(prefix.len()..)
-                    .and_then(|it| Version::parse(it.split_whitespace().next()?).ok()),
-            )
-        };
         let res = match manifest {
             ProjectManifest::ProjectJson(project_json) => {
                 let file = fs::read_to_string(project_json)
@@ -182,30 +200,14 @@ impl ProjectWorkspace {
                 let data = serde_json::from_str(&file)
                     .with_context(|| format!("Failed to deserialize json file {project_json}"))?;
                 let project_location = project_json.parent().to_path_buf();
-                let toolchain = version(&*project_location, toolchain::rustc(), "rustc ")?;
-                let project_json = ProjectJson::new(&project_location, data);
+                let project_json: ProjectJson = ProjectJson::new(&project_location, data);
                 ProjectWorkspace::load_inline(
                     project_json,
                     config.target.as_deref(),
                     &config.extra_env,
-                    toolchain,
                 )
             }
             ProjectManifest::CargoToml(cargo_toml) => {
-                let toolchain = version(cargo_toml.parent(), toolchain::cargo(), "cargo ")?;
-                let meta = CargoWorkspace::fetch_metadata(
-                    cargo_toml,
-                    cargo_toml.parent(),
-                    config,
-                    progress,
-                )
-                .with_context(|| {
-                    format!(
-                        "Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
-                    )
-                })?;
-                let cargo = CargoWorkspace::new(meta);
-
                 let sysroot = match (&config.sysroot, &config.sysroot_src) {
                     (Some(RustLibSource::Path(path)), None) => {
                         Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata).map_err(|e| {
@@ -218,7 +220,7 @@ impl ProjectWorkspace {
                         })
                     }
                     (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
-                        Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone(), config.sysroot_query_metadata))
+                        Ok(Sysroot::load(sysroot.clone(), Some(Ok(sysroot_src.clone())), config.sysroot_query_metadata))
                     }
                     (Some(RustLibSource::Discover), Some(sysroot_src)) => {
                         Sysroot::discover_with_src_override(
@@ -231,18 +233,19 @@ impl ProjectWorkspace {
                     }
                     (None, _) => Err(None),
                 };
+                let sysroot_ref = sysroot.as_ref().ok();
 
                 if let Ok(sysroot) = &sysroot {
-                    tracing::info!(workspace = %cargo_toml, src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
+                    tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
                 }
 
                 let rustc_dir = match &config.rustc_source {
                     Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
                         .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
                     Some(RustLibSource::Discover) => {
-                        sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else(
-                            || Some("Failed to discover rustc source for sysroot.".to_owned()),
-                        )
+                        sysroot_ref.and_then(Sysroot::discover_rustc_src).ok_or_else(|| {
+                            Some("Failed to discover rustc source for sysroot.".to_owned())
+                        })
                     }
                     None => Err(None),
                 };
@@ -256,6 +259,7 @@ impl ProjectWorkspace {
                             features: crate::CargoFeatures::default(),
                             ..config.clone()
                         },
+                        sysroot_ref,
                         progress,
                     ) {
                         Ok(meta) => {
@@ -264,6 +268,7 @@ impl ProjectWorkspace {
                                 &workspace,
                                 cargo_toml.parent(),
                                 &config.extra_env,
+                                sysroot_ref
                             );
                             Ok(Box::new((workspace, buildscripts)))
                         }
@@ -279,21 +284,42 @@ impl ProjectWorkspace {
                     }
                 });
 
+                let toolchain = get_toolchain_version(
+                    cargo_toml.parent(),
+                    Sysroot::discover_tool(sysroot_ref, toolchain::Tool::Cargo),
+                    &config.extra_env,
+                    "cargo ",
+                )?;
                 let rustc_cfg = rustc_cfg::get(
                     config.target.as_deref(),
                     &config.extra_env,
-                    RustcCfgConfig::Cargo(cargo_toml),
+                    RustcCfgConfig::Cargo(sysroot_ref, cargo_toml),
                 );
 
                 let cfg_overrides = config.cfg_overrides.clone();
                 let data_layout = target_data_layout::get(
-                    Some(cargo_toml),
+                    RustcDataLayoutConfig::Cargo(sysroot_ref, cargo_toml),
                     config.target.as_deref(),
                     &config.extra_env,
                 );
                 if let Err(e) = &data_layout {
                     tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace");
                 }
+
+                let meta = CargoWorkspace::fetch_metadata(
+                    cargo_toml,
+                    cargo_toml.parent(),
+                    config,
+                    sysroot_ref,
+                    progress,
+                )
+                .with_context(|| {
+                    format!(
+                        "Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
+                    )
+                })?;
+                let cargo = CargoWorkspace::new(meta);
+
                 ProjectWorkspace::Cargo {
                     cargo,
                     build_scripts: WorkspaceBuildScripts::default(),
@@ -314,15 +340,16 @@ impl ProjectWorkspace {
         project_json: ProjectJson,
         target: Option<&str>,
         extra_env: &FxHashMap<String, String>,
-        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, false)),
+            (Some(sysroot), Some(sysroot_src)) => {
+                Ok(Sysroot::load(sysroot, Some(Ok(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, false))
+                Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
             }
             (None, Some(sysroot_src)) => {
                 // assume sysroot is structured like rustup's and guess `sysroot`
@@ -330,23 +357,32 @@ impl ProjectWorkspace {
                 for _ in 0..5 {
                     sysroot.pop();
                 }
-                Ok(Sysroot::load(sysroot, sysroot_src, false))
+                Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
             }
             (None, None) => Err(None),
         };
-        let config = match &sysroot {
-            Ok(sysroot) => {
-                tracing::debug!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
-                RustcCfgConfig::Explicit(sysroot)
-            }
-            Err(_) => {
-                tracing::debug!("discovering sysroot");
-                RustcCfgConfig::Discover
+        let sysroot_ref = sysroot.as_ref().ok();
+        let cfg_config = RustcCfgConfig::Rustc(sysroot_ref);
+        let data_layout_config = RustcDataLayoutConfig::Rustc(sysroot_ref);
+        let rustc = Sysroot::discover_tool(sysroot_ref, toolchain::Tool::Rustc).map(Into::into);
+        let toolchain = match get_toolchain_version(project_json.path(), rustc, extra_env, "rustc ")
+        {
+            Ok(it) => it,
+            Err(e) => {
+                tracing::error!("{e}");
+                None
             }
         };
 
-        let rustc_cfg = rustc_cfg::get(target, extra_env, config);
-        ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain }
+        let rustc_cfg = rustc_cfg::get(target, extra_env, cfg_config);
+        let data_layout = target_data_layout::get(data_layout_config, target, extra_env);
+        ProjectWorkspace::Json {
+            project: project_json,
+            sysroot,
+            rustc_cfg,
+            toolchain,
+            target_layout: data_layout.map_err(|it| it.to_string()),
+        }
     }
 
     pub fn load_detached_files(
@@ -373,18 +409,11 @@ impl ProjectWorkspace {
             }
             None => Err(None),
         };
-        let rustc_config = match &sysroot {
-            Ok(sysroot) => {
-                tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
-                RustcCfgConfig::Explicit(sysroot)
-            }
-            Err(_) => {
-                tracing::info!("discovering sysroot");
-                RustcCfgConfig::Discover
-            }
-        };
-
-        let rustc_cfg = rustc_cfg::get(None, &FxHashMap::default(), rustc_config);
+        let rustc_cfg = rustc_cfg::get(
+            None,
+            &FxHashMap::default(),
+            RustcCfgConfig::Rustc(sysroot.as_ref().ok()),
+        );
         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
     }
 
@@ -395,11 +424,17 @@ impl ProjectWorkspace {
         progress: &dyn Fn(String),
     ) -> anyhow::Result<WorkspaceBuildScripts> {
         match self {
-            ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
-                WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
-                    .with_context(|| {
-                        format!("Failed to run build scripts for {}", cargo.workspace_root())
-                    })
+            ProjectWorkspace::Cargo { cargo, toolchain, sysroot, .. } => {
+                WorkspaceBuildScripts::run_for_workspace(
+                    config,
+                    cargo,
+                    progress,
+                    toolchain,
+                    sysroot.as_ref().ok(),
+                )
+                .with_context(|| {
+                    format!("Failed to run build scripts for {}", cargo.workspace_root())
+                })
             }
             ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
                 Ok(WorkspaceBuildScripts::default())
@@ -472,18 +507,7 @@ impl ProjectWorkspace {
             ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
             | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
             | ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => {
-                let standalone_server_name =
-                    format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
-                ["libexec", "lib"]
-                    .into_iter()
-                    .map(|segment| sysroot.root().join(segment).join(&standalone_server_name))
-                    .find(|server_path| std::fs::metadata(server_path).is_ok())
-                    .ok_or_else(|| {
-                        anyhow::format_err!(
-                            "cannot find proc-macro server in sysroot `{}`",
-                            sysroot.root()
-                        )
-                    })
+                sysroot.discover_proc_macro_srv()
             }
             ProjectWorkspace::DetachedFiles { .. } => {
                 Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found"))
@@ -503,8 +527,7 @@ 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<_, _>, project_root: Option<&AbsPath>| {
-            let project_root = project_root.map(ToOwned::to_owned);
+        let mk_sysroot = |sysroot: Result<_, _>| {
             sysroot.into_iter().flat_map(move |sysroot: &Sysroot| {
                 let mut r = match sysroot.mode() {
                     SysrootMode::Workspace(ws) => ws
@@ -532,18 +555,21 @@ impl ProjectWorkspace {
                 };
 
                 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()],
+                    is_local: false,
+                    include: sysroot.src_root().map(|it| it.to_path_buf()).into_iter().collect(),
                     exclude: Vec::new(),
                 });
                 r
             })
         };
         match self {
-            ProjectWorkspace::Json { project, sysroot, rustc_cfg: _, toolchain: _ } => project
+            ProjectWorkspace::Json {
+                project,
+                sysroot,
+                rustc_cfg: _,
+                toolchain: _,
+                target_layout: _,
+            } => project
                 .crates()
                 .map(|(_, krate)| PackageRoot {
                     is_local: krate.is_workspace_member,
@@ -552,7 +578,7 @@ impl ProjectWorkspace {
                 })
                 .collect::<FxHashSet<_>>()
                 .into_iter()
-                .chain(mk_sysroot(sysroot.as_ref(), Some(project.path())))
+                .chain(mk_sysroot(sysroot.as_ref()))
                 .collect::<Vec<_>>(),
             ProjectWorkspace::Cargo {
                 cargo,
@@ -602,7 +628,7 @@ impl ProjectWorkspace {
                         }
                         PackageRoot { is_local, include, exclude }
                     })
-                    .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root())))
+                    .chain(mk_sysroot(sysroot.as_ref()))
                     .chain(rustc.iter().map(|a| a.as_ref()).flat_map(|(rustc, _)| {
                         rustc.packages().map(move |krate| PackageRoot {
                             is_local: false,
@@ -619,7 +645,7 @@ impl ProjectWorkspace {
                     include: vec![detached_file.clone()],
                     exclude: Vec::new(),
                 })
-                .chain(mk_sysroot(sysroot.as_ref(), None))
+                .chain(mk_sysroot(sysroot.as_ref()))
                 .collect(),
         }
     }
@@ -651,14 +677,17 @@ impl ProjectWorkspace {
         let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered();
 
         let (mut crate_graph, proc_macros) = match self {
-            ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain } => {
+            ProjectWorkspace::Json { project, sysroot, rustc_cfg, toolchain, target_layout } => {
                 project_json_to_crate_graph(
                     rustc_cfg.clone(),
                     load,
                     project,
                     sysroot.as_ref().ok(),
                     extra_env,
-                    Err("rust-project.json projects have no target layout set".into()),
+                    match target_layout.as_ref() {
+                        Ok(it) => Ok(Arc::from(it.as_str())),
+                        Err(it) => Err(Arc::from(it.as_str())),
+                    },
                     toolchain.clone(),
                 )
             }
@@ -735,12 +764,13 @@ impl ProjectWorkspace {
                     && sysroot == o_sysroot
             }
             (
-                Self::Json { project, sysroot, rustc_cfg, toolchain },
+                Self::Json { project, sysroot, rustc_cfg, toolchain, target_layout: _ },
                 Self::Json {
                     project: o_project,
                     sysroot: o_sysroot,
                     rustc_cfg: o_rustc_cfg,
                     toolchain: o_toolchain,
+                    target_layout: _,
                 },
             ) => {
                 project == o_project
@@ -813,12 +843,7 @@ fn project_json_to_crate_graph(
 
                 let target_cfgs = match target.as_deref() {
                     Some(target) => cfg_cache.entry(target).or_insert_with(|| {
-                        let rustc_cfg = match sysroot {
-                            Some(sysroot) => RustcCfgConfig::Explicit(sysroot),
-                            None => RustcCfgConfig::Discover,
-                        };
-
-                        rustc_cfg::get(Some(target), extra_env, rustc_cfg)
+                        rustc_cfg::get(Some(target), extra_env, RustcCfgConfig::Rustc(sysroot))
                     }),
                     None => &rustc_cfg,
                 };
@@ -959,21 +984,6 @@ fn cargo_to_crate_graph(
             }
             let &TargetData { ref name, kind, is_proc_macro, ref root, .. } = &cargo[tgt];
 
-            if kind == TargetKind::Lib
-                && sysroot.map_or(false, |sysroot| root.starts_with(sysroot.src_root()))
-            {
-                if let Some(&(_, crate_id, _)) =
-                    public_deps.deps.iter().find(|(dep_name, ..)| dep_name.as_smol_str() == name)
-                {
-                    pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, kind));
-
-                    lib_tgt = Some((crate_id, name.clone()));
-                    pkg_to_lib_crate.insert(pkg, crate_id);
-                    // sysroot is inside the workspace, prevent the sysroot crates from being duplicated here
-                    continue;
-                }
-            }
-
             let Some(file_id) = load(root) else { continue };
 
             let crate_id = add_target_crate_root(
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 0df99534c5b..0489c4213b4 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
@@ -37,7 +37,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -70,7 +70,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -103,7 +103,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -136,7 +136,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -186,7 +186,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -219,7 +219,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -317,7 +317,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -350,7 +350,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -383,7 +383,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -416,7 +416,7 @@
         ),
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
@@ -493,7 +493,7 @@
         },
         is_proc_macro: false,
         target_layout: Err(
-            "rust-project.json projects have no target layout set",
+            "test has no data layout",
         ),
         toolchain: None,
     },
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 2a3633a48e9..b175dbc895c 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -1937,6 +1937,7 @@ fn run_rustfmt(
 
     let mut command = match snap.config.rustfmt() {
         RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
+            // FIXME: This should use the sysroot's rustfmt if its loaded
             let mut cmd = process::Command::new(toolchain::rustfmt());
             cmd.envs(snap.config.extra_env());
             cmd.args(extra_args);
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 7bd2877b00c..5a5d26e0b05 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -24,7 +24,7 @@ use ide_db::{
 use itertools::Itertools;
 use load_cargo::{load_proc_macro, ProjectFolders};
 use proc_macro_api::ProcMacroServer;
-use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
+use project_model::{ProjectWorkspace, Sysroot, WorkspaceBuildScripts};
 use rustc_hash::FxHashSet;
 use stdx::{format_to, thread::ThreadIntent};
 use triomphe::Arc;
@@ -234,7 +234,6 @@ impl GlobalState {
                                 it.clone(),
                                 cargo_config.target.as_deref(),
                                 &cargo_config.extra_env,
-                                None,
                             ))
                         }
                     })
@@ -605,6 +604,7 @@ impl GlobalState {
                 0,
                 Box::new(move |msg| sender.send(msg).unwrap()),
                 config,
+                toolchain::cargo(),
                 self.config.root_path().clone(),
             )],
             flycheck::InvocationStrategy::PerWorkspace => {
@@ -612,23 +612,35 @@ impl GlobalState {
                     .iter()
                     .enumerate()
                     .filter_map(|(id, w)| match w {
-                        ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
-                        ProjectWorkspace::Json { project, .. } => {
+                        ProjectWorkspace::Cargo { cargo, sysroot, .. } => Some((
+                            id,
+                            cargo.workspace_root(),
+                            Sysroot::discover_tool(sysroot.as_ref().ok(), toolchain::Tool::Cargo),
+                        )),
+                        ProjectWorkspace::Json { project, sysroot, .. } => {
                             // Enable flychecks for json projects if a custom flycheck command was supplied
                             // in the workspace configuration.
                             match config {
-                                FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
+                                FlycheckConfig::CustomCommand { .. } => Some((
+                                    id,
+                                    project.path(),
+                                    Sysroot::discover_tool(
+                                        sysroot.as_ref().ok(),
+                                        toolchain::Tool::Cargo,
+                                    ),
+                                )),
                                 _ => None,
                             }
                         }
                         ProjectWorkspace::DetachedFiles { .. } => None,
                     })
-                    .map(|(id, root)| {
+                    .map(|(id, root, cargo)| {
                         let sender = sender.clone();
                         FlycheckHandle::spawn(
                             id,
                             Box::new(move |msg| sender.send(msg).unwrap()),
                             config.clone(),
+                            cargo.unwrap_or_else(|_| toolchain::cargo()),
                             root.to_path_buf(),
                         )
                     })
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 79ae0c30cfc..960f5b531d4 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -911,20 +911,18 @@ fn root_contains_symlink_out_dirs_check() {
 #[cfg(any(feature = "sysroot-abi", rust_analyzer))]
 fn resolve_proc_macro() {
     use expect_test::expect;
+    use vfs::AbsPathBuf;
     if skip_slow_tests() {
         return;
     }
 
-    // skip using the sysroot config as to prevent us from loading the sysroot sources
-    let mut rustc = std::process::Command::new(toolchain::rustc());
-    rustc.args(["--print", "sysroot"]);
-    let output = rustc.output().unwrap();
-    let sysroot =
-        vfs::AbsPathBuf::try_from(std::str::from_utf8(&output.stdout).unwrap().trim()).unwrap();
+    let sysroot = project_model::Sysroot::discover_no_source(
+        &AbsPathBuf::assert(std::env::current_dir().unwrap()),
+        &Default::default(),
+    )
+    .unwrap();
 
-    let standalone_server_name =
-        format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
-    let proc_macro_server_path = sysroot.join("libexec").join(&standalone_server_name);
+    let proc_macro_server_path = sysroot.discover_proc_macro_srv().unwrap();
 
     let server = Project::with_fixture(
         r###"
diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs
index 997f339edc4..ae71b6700c0 100644
--- a/crates/toolchain/src/lib.rs
+++ b/crates/toolchain/src/lib.rs
@@ -2,7 +2,41 @@
 
 #![warn(rust_2018_idioms, unused_lifetimes)]
 
-use std::{env, iter, path::PathBuf};
+use std::{
+    env, iter,
+    path::{Path, PathBuf},
+};
+
+#[derive(Copy, Clone)]
+pub enum Tool {
+    Cargo,
+    Rustc,
+    Rustup,
+    Rustfmt,
+}
+
+impl Tool {
+    pub fn path(self) -> PathBuf {
+        get_path_for_executable(self.name())
+    }
+
+    pub fn path_in(self, path: &Path) -> Option<PathBuf> {
+        probe_for_binary(path.join(self.name()))
+    }
+
+    pub fn path_in_or_discover(self, path: &Path) -> PathBuf {
+        probe_for_binary(path.join(self.name())).unwrap_or_else(|| self.path())
+    }
+
+    pub fn name(self) -> &'static str {
+        match self {
+            Tool::Cargo => "cargo",
+            Tool::Rustc => "rustc",
+            Tool::Rustup => "rustup",
+            Tool::Rustfmt => "rustfmt",
+        }
+    }
+}
 
 pub fn cargo() -> PathBuf {
     get_path_for_executable("cargo")
@@ -47,7 +81,7 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
     if let Some(mut path) = get_cargo_home() {
         path.push("bin");
         path.push(executable_name);
-        if let Some(path) = probe(path) {
+        if let Some(path) = probe_for_binary(path) {
             return path;
         }
     }
@@ -57,7 +91,7 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
 
 fn lookup_in_path(exec: &str) -> bool {
     let paths = env::var_os("PATH").unwrap_or_default();
-    env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe).is_some()
+    env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe_for_binary).is_some()
 }
 
 fn get_cargo_home() -> Option<PathBuf> {
@@ -73,7 +107,7 @@ fn get_cargo_home() -> Option<PathBuf> {
     None
 }
 
-fn probe(path: PathBuf) -> Option<PathBuf> {
+pub fn probe_for_binary(path: PathBuf) -> Option<PathBuf> {
     let with_extension = match env::consts::EXE_EXTENSION {
         "" => None,
         it => Some(path.with_extension(it)),