internal: tool discovery prefers sysroot tools

This commit is contained in:
Lukas Wirth 2024-02-12 12:08:18 +01:00
parent ddf105b646
commit 8f3209ba27
14 changed files with 406 additions and 250 deletions

View File

@ -89,9 +89,10 @@ impl FlycheckHandle {
id: usize, id: usize,
sender: Box<dyn Fn(Message) + Send>, sender: Box<dyn Fn(Message) + Send>,
config: FlycheckConfig, config: FlycheckConfig,
cargo: PathBuf,
workspace_root: AbsPathBuf, workspace_root: AbsPathBuf,
) -> FlycheckHandle { ) -> 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 (sender, receiver) = unbounded::<StateChange>();
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
.name("Flycheck".to_owned()) .name("Flycheck".to_owned())
@ -171,6 +172,7 @@ struct FlycheckActor {
/// Either the workspace root of the workspace we are flychecking, /// Either the workspace root of the workspace we are flychecking,
/// or the project root of the project. /// or the project root of the project.
root: AbsPathBuf, root: AbsPathBuf,
cargo: PathBuf,
/// CargoHandle exists to wrap around the communication needed to be able to /// CargoHandle exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library /// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we /// doesn't provide a way to read sub-process output without blocking, so we
@ -189,10 +191,11 @@ impl FlycheckActor {
id: usize, id: usize,
sender: Box<dyn Fn(Message) + Send>, sender: Box<dyn Fn(Message) + Send>,
config: FlycheckConfig, config: FlycheckConfig,
cargo: PathBuf,
workspace_root: AbsPathBuf, workspace_root: AbsPathBuf,
) -> FlycheckActor { ) -> FlycheckActor {
tracing::info!(%id, ?workspace_root, "Spawning flycheck"); 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) { fn report_progress(&self, progress: Progress) {
@ -316,7 +319,7 @@ impl FlycheckActor {
ansi_color_output, ansi_color_output,
target_dir, target_dir,
} => { } => {
let mut cmd = Command::new(toolchain::cargo()); let mut cmd = Command::new(&self.cargo);
cmd.arg(command); cmd.arg(command);
cmd.current_dir(&self.root); cmd.current_dir(&self.root);

View File

@ -1,6 +1,7 @@
use chalk_ir::{AdtId, TyKind}; use chalk_ir::{AdtId, TyKind};
use either::Either; use either::Either;
use hir_def::db::DefDatabase; use hir_def::db::DefDatabase;
use project_model::target_data_layout::RustcDataLayoutConfig;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use test_fixture::WithFixture; use test_fixture::WithFixture;
use triomphe::Arc; use triomphe::Arc;
@ -15,7 +16,12 @@ use crate::{
mod closure; mod closure;
fn current_machine_data_layout() -> String { 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> { fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutError> {

View File

@ -20,10 +20,11 @@ use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version; use semver::Version;
use serde::Deserialize; use serde::Deserialize;
use toolchain::Tool;
use crate::{ use crate::{
cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
InvocationStrategy, Package, InvocationStrategy, Package, Sysroot,
}; };
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
@ -61,6 +62,7 @@ impl WorkspaceBuildScripts {
config: &CargoConfig, config: &CargoConfig,
allowed_features: &FxHashSet<String>, allowed_features: &FxHashSet<String>,
workspace_root: &AbsPathBuf, workspace_root: &AbsPathBuf,
sysroot: Option<&Sysroot>,
) -> io::Result<Command> { ) -> io::Result<Command> {
let mut cmd = match config.run_build_script_command.as_deref() { let mut cmd = match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => { Some([program, args @ ..]) => {
@ -69,7 +71,10 @@ impl WorkspaceBuildScripts {
cmd 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(["check", "--quiet", "--workspace", "--message-format=json"]);
cmd.args(&config.extra_args); cmd.args(&config.extra_args);
@ -133,6 +138,7 @@ impl WorkspaceBuildScripts {
workspace: &CargoWorkspace, workspace: &CargoWorkspace,
progress: &dyn Fn(String), progress: &dyn Fn(String),
toolchain: &Option<Version>, toolchain: &Option<Version>,
sysroot: Option<&Sysroot>,
) -> io::Result<WorkspaceBuildScripts> { ) -> io::Result<WorkspaceBuildScripts> {
const RUST_1_62: Version = Version::new(1, 62, 0); const RUST_1_62: Version = Version::new(1, 62, 0);
@ -151,6 +157,7 @@ impl WorkspaceBuildScripts {
config, config,
&allowed_features, &allowed_features,
&workspace.workspace_root().to_path_buf(), &workspace.workspace_root().to_path_buf(),
sysroot,
)?, )?,
workspace, workspace,
current_dir, current_dir,
@ -165,6 +172,7 @@ impl WorkspaceBuildScripts {
config, config,
&allowed_features, &allowed_features,
&workspace.workspace_root().to_path_buf(), &workspace.workspace_root().to_path_buf(),
sysroot,
)?; )?;
cmd.args(["-Z", "unstable-options", "--keep-going"]).env("RUSTC_BOOTSTRAP", "1"); cmd.args(["-Z", "unstable-options", "--keep-going"]).env("RUSTC_BOOTSTRAP", "1");
let mut res = Self::run_per_ws(cmd, workspace, current_dir, progress)?; 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 // NB: Cargo.toml could have been modified between `cargo metadata` and
// `cargo check`. We shouldn't assume that package ids we see here are // `cargo check`. We shouldn't assume that package ids we see here are
// exactly those from `config`. // exactly those from `config`.
@ -415,6 +423,7 @@ impl WorkspaceBuildScripts {
rustc: &CargoWorkspace, rustc: &CargoWorkspace,
current_dir: &AbsPath, current_dir: &AbsPath,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
sysroot: Option<&Sysroot>,
) -> Self { ) -> Self {
let mut bs = WorkspaceBuildScripts::default(); let mut bs = WorkspaceBuildScripts::default();
for p in rustc.packages() { for p in rustc.packages() {
@ -422,7 +431,7 @@ impl WorkspaceBuildScripts {
} }
let res = (|| { let res = (|| {
let target_libdir = (|| { 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.envs(extra_env);
cargo_config cargo_config
.current_dir(current_dir) .current_dir(current_dir)
@ -431,7 +440,7 @@ impl WorkspaceBuildScripts {
if let Ok(it) = utf8_stdout(cargo_config) { if let Ok(it) = utf8_stdout(cargo_config) {
return Ok(it); 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.envs(extra_env);
cmd.args(["--print", "target-libdir"]); cmd.args(["--print", "target-libdir"]);
utf8_stdout(cmd) utf8_stdout(cmd)

View File

@ -12,8 +12,9 @@ use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize; use serde::Deserialize;
use serde_json::from_value; 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}; use crate::{CfgOverrides, InvocationStrategy};
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
@ -236,12 +237,13 @@ impl CargoWorkspace {
cargo_toml: &ManifestPath, cargo_toml: &ManifestPath,
current_dir: &AbsPath, current_dir: &AbsPath,
config: &CargoConfig, config: &CargoConfig,
sysroot: Option<&Sysroot>,
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> anyhow::Result<cargo_metadata::Metadata> { ) -> 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(); 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()); meta.manifest_path(cargo_toml.to_path_buf());
match &config.features { match &config.features {
CargoFeatures::All => { 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 { if let Some(target) = &config.target {
return [target.into()].to_vec(); 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() { if !build_targets.is_empty() {
return build_targets; 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( fn rustc_discover_host_triple(
cargo_toml: &ManifestPath, cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
sysroot: Option<&Sysroot>,
) -> Option<String> { ) -> 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.envs(extra_env);
rustc.current_dir(cargo_toml.parent()).arg("-vV"); rustc.current_dir(cargo_toml.parent()).arg("-vV");
tracing::debug!("Discovering host platform by {:?}", rustc); tracing::debug!("Discovering host platform by {:?}", rustc);
@ -519,8 +526,10 @@ fn rustc_discover_host_triple(
fn cargo_config_build_target( fn cargo_config_build_target(
cargo_toml: &ManifestPath, cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
sysroot: Option<&Sysroot>,
) -> Vec<String> { ) -> 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.envs(extra_env);
cargo_config cargo_config
.current_dir(cargo_toml.parent()) .current_dir(cargo_toml.parent())

View File

@ -8,17 +8,13 @@ use rustc_hash::FxHashMap;
use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot}; use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot};
/// Determines how `rustc --print cfg` is discovered and invoked. /// 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> { pub(crate) enum RustcCfgConfig<'a> {
Cargo(&'a ManifestPath), /// Use `rustc --print cfg`, either from with the binary from the sysroot or by discovering via
Explicit(&'a Sysroot), /// [`toolchain::rustc`].
Discover, 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( pub(crate) fn get(
@ -71,36 +67,31 @@ fn get_rust_cfgs(
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
config: RustcCfgConfig<'_>, config: RustcCfgConfig<'_>,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let mut cmd = match config { match config {
RustcCfgConfig::Cargo(cargo_toml) => { RustcCfgConfig::Cargo(sysroot, cargo_oml) => {
let mut cmd = Command::new(toolchain::cargo()); let cargo = Sysroot::discover_tool(sysroot, toolchain::Tool::Cargo)?;
let mut cmd = Command::new(cargo);
cmd.envs(extra_env); cmd.envs(extra_env);
cmd.current_dir(cargo_toml.parent()) cmd.current_dir(cargo_oml.parent())
.args(["rustc", "-Z", "unstable-options", "--print", "cfg"]) .args(["rustc", "-Z", "unstable-options", "--print", "cfg"])
.env("RUSTC_BOOTSTRAP", "1"); .env("RUSTC_BOOTSTRAP", "1");
if let Some(target) = target { if let Some(target) = target {
cmd.args(["--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) => { RustcCfgConfig::Rustc(sysroot) => {
let rustc: std::path::PathBuf = sysroot.discover_rustc()?.into(); let rustc = Sysroot::discover_tool(sysroot, toolchain::Tool::Rustc)?;
tracing::debug!(?rustc, "using explicit rustc from sysroot"); tracing::debug!(?rustc, "using explicit rustc from sysroot");
Command::new(rustc) let mut cmd = Command::new(rustc);
} cmd.envs(extra_env);
RustcCfgConfig::Discover => { cmd.args(["--print", "cfg", "-O"]);
let rustc = toolchain::rustc(); if let Some(target) = target {
tracing::debug!(?rustc, "using rustc from env"); cmd.args(["--target", target]);
Command::new(rustc) }
}
};
cmd.envs(extra_env); utf8_stdout(cmd).context("Unable to run `rustc`")
cmd.args(["--print", "cfg", "-O"]); }
if let Some(target) = target {
cmd.args(["--target", target]);
} }
utf8_stdout(cmd).context("Unable to run `rustc`")
} }

View File

@ -4,7 +4,7 @@
//! but we can't process `.rlib` and need source code instead. The source code //! but we can't process `.rlib` and need source code instead. The source code
//! is typically installed with `rustup component add rust-src` command. //! 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 anyhow::{format_err, Context, Result};
use base_db::CrateName; use base_db::CrateName;
@ -12,16 +12,30 @@ use itertools::Itertools;
use la_arena::{Arena, Idx}; use la_arena::{Arena, Idx};
use paths::{AbsPath, AbsPathBuf}; use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use toolchain::{probe_for_binary, Tool};
use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath}; use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath};
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone)]
pub struct Sysroot { pub struct Sysroot {
root: AbsPathBuf, root: AbsPathBuf,
src_root: AbsPathBuf, src_root: Option<Result<AbsPathBuf, Arc<anyhow::Error>>>,
mode: SysrootMode, 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)] #[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum SysrootMode { pub(crate) enum SysrootMode {
Workspace(CargoWorkspace), Workspace(CargoWorkspace),
@ -86,8 +100,8 @@ impl Sysroot {
/// Returns the sysroot "source" directory, where stdlib sources are located, like: /// 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` /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
pub fn src_root(&self) -> &AbsPath { pub fn src_root(&self) -> Option<&AbsPath> {
&self.src_root self.src_root.as_ref()?.as_deref().ok()
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -98,6 +112,11 @@ impl Sysroot {
} }
pub fn loading_warning(&self) -> Option<String> { 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 { let has_core = match &self.mode {
SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"), SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(), SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
@ -108,10 +127,7 @@ impl Sysroot {
} else { } else {
" try running `rustup component add rust-src` to possible fix this" " try running `rustup component add rust-src` to possible fix this"
}; };
Some(format!( Some(format!("could not find libcore in loaded sysroot at `{}`{var_note}", src_root,))
"could not find libcore in loaded sysroot at `{}`{var_note}",
self.src_root.as_path(),
))
} else { } else {
None None
} }
@ -140,8 +156,19 @@ impl Sysroot {
tracing::debug!("discovering sysroot for {dir}"); tracing::debug!("discovering sysroot for {dir}");
let sysroot_dir = discover_sysroot_dir(dir, extra_env)?; let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
let sysroot_src_dir = let sysroot_src_dir =
discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?; discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env);
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir, metadata)) 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( pub fn discover_with_src_override(
@ -152,33 +179,73 @@ impl Sysroot {
) -> Result<Sysroot> { ) -> Result<Sysroot> {
tracing::debug!("discovering sysroot for {current_dir}"); tracing::debug!("discovering sysroot for {current_dir}");
let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?; 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> { pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
get_rustc_src(&self.root) 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> { 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(|| { 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}") 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 { if metadata {
let sysroot: Option<_> = (|| { let sysroot: Option<_> = (|| {
let sysroot_cargo_toml = ManifestPath::try_from( let sysroot_cargo_toml = ManifestPath::try_from(
@ -191,6 +258,7 @@ impl Sysroot {
&sysroot_cargo_toml, &sysroot_cargo_toml,
&current_dir, &current_dir,
&CargoConfig::default(), &CargoConfig::default(),
None,
&|_| (), &|_| (),
) )
.map_err(|e| { .map_err(|e| {
@ -274,7 +342,7 @@ impl Sysroot {
let cargo_workspace = CargoWorkspace::new(res); let cargo_workspace = CargoWorkspace::new(res);
Some(Sysroot { Some(Sysroot {
root: sysroot_dir.clone(), root: sysroot_dir.clone(),
src_root: sysroot_src_dir.clone(), src_root: Some(Ok(sysroot_src_dir.clone())),
mode: SysrootMode::Workspace(cargo_workspace), mode: SysrootMode::Workspace(cargo_workspace),
}) })
})(); })();
@ -326,7 +394,7 @@ impl Sysroot {
} }
Sysroot { Sysroot {
root: sysroot_dir, root: sysroot_dir,
src_root: sysroot_src_dir, src_root: Some(Ok(sysroot_src_dir)),
mode: SysrootMode::Stitched(stitched), mode: SysrootMode::Stitched(stitched),
} }
} }

View File

@ -3,16 +3,27 @@ use std::process::Command;
use rustc_hash::FxHashMap; 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( pub fn get(
cargo_toml: Option<&ManifestPath>, config: RustcDataLayoutConfig<'_>,
target: Option<&str>, target: Option<&str>,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let output = (|| { let output = match config {
if let Some(cargo_toml) = cargo_toml { RustcDataLayoutConfig::Cargo(sysroot, cargo_toml) => {
let mut cmd = Command::new(toolchain::rustc()); let cargo = Sysroot::discover_tool(sysroot, toolchain::Tool::Cargo)?;
let mut cmd = Command::new(cargo);
cmd.envs(extra_env); cmd.envs(extra_env);
cmd.current_dir(cargo_toml.parent()) cmd.current_dir(cargo_toml.parent())
.args(["-Z", "unstable-options", "--print", "target-spec-json"]) .args(["-Z", "unstable-options", "--print", "target-spec-json"])
@ -20,21 +31,20 @@ pub fn get(
if let Some(target) = target { if let Some(target) = target {
cmd.args(["--target", target]); cmd.args(["--target", target]);
} }
match utf8_stdout(cmd) { utf8_stdout(cmd)
Ok(it) => return Ok(it), }
Err(e) => tracing::debug!("{e:?}: falling back to querying rustc for cfgs"), 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)
} }
// 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]);
}
utf8_stdout(cmd)
})()?;
(|| Some(output.split_once(r#""data-layout": ""#)?.1.split_once('"')?.0.to_owned()))() (|| 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")) .ok_or_else(|| anyhow::format_err!("could not fetch target-spec-json from command output"))
} }

View File

@ -69,8 +69,13 @@ fn load_rust_project(file: &str) -> (CrateGraph, ProcMacroPaths) {
let data = get_test_json_file(file); let data = get_test_json_file(file);
let project = rooted_project_json(data); let project = rooted_project_json(data);
let sysroot = Ok(get_fake_sysroot()); let sysroot = Ok(get_fake_sysroot());
let project_workspace = let project_workspace = ProjectWorkspace::Json {
ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new(), toolchain: None }; project,
sysroot,
rustc_cfg: Vec::new(),
toolchain: None,
target_layout: Err("test has no data layout".to_owned()),
};
to_crate_graph(project_workspace) to_crate_graph(project_workspace)
} }
@ -125,7 +130,7 @@ fn get_fake_sysroot() -> Sysroot {
// fake sysroot, so we give them both the same path: // fake sysroot, so we give them both the same path:
let sysroot_dir = AbsPathBuf::assert(sysroot_path); let sysroot_dir = AbsPathBuf::assert(sysroot_path);
let sysroot_src_dir = sysroot_dir.clone(); 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 { fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {

View File

@ -2,7 +2,9 @@
//! metadata` or `rust-project.json`) into representation stored in the salsa //! metadata` or `rust-project.json`) into representation stored in the salsa
//! database -- `CrateGraph`. //! 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 anyhow::{format_err, Context};
use base_db::{ use base_db::{
@ -23,8 +25,9 @@ use crate::{
project_json::Crate, project_json::Crate,
rustc_cfg::{self, RustcCfgConfig}, rustc_cfg::{self, RustcCfgConfig},
sysroot::{SysrootCrate, SysrootMode}, sysroot::{SysrootCrate, SysrootMode},
target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, target_data_layout::{self, RustcDataLayoutConfig},
Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
}; };
/// A set of cfg-overrides per crate. /// A set of cfg-overrides per crate.
@ -79,6 +82,7 @@ pub enum ProjectWorkspace {
/// `rustc --print cfg`. /// `rustc --print cfg`.
rustc_cfg: Vec<CfgFlag>, rustc_cfg: Vec<CfgFlag>,
toolchain: Option<Version>, 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. // 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. // That's not the end user experience we should strive for.
@ -126,14 +130,22 @@ impl fmt::Debug for ProjectWorkspace {
.field("toolchain", &toolchain) .field("toolchain", &toolchain)
.field("data_layout", &data_layout) .field("data_layout", &data_layout)
.finish(), .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"); let mut debug_struct = f.debug_struct("Json");
debug_struct.field("n_crates", &project.n_crates()); debug_struct.field("n_crates", &project.n_crates());
if let Ok(sysroot) = sysroot { if let Ok(sysroot) = sysroot {
debug_struct.field("n_sysroot_crates", &sysroot.num_packages()); debug_struct.field("n_sysroot_crates", &sysroot.num_packages());
} }
debug_struct.field("toolchain", &toolchain); debug_struct
debug_struct.field("n_rustc_cfg", &rustc_cfg.len()); .field("toolchain", &toolchain)
.field("n_rustc_cfg", &rustc_cfg.len())
.field("data_layout", &data_layout);
debug_struct.finish() debug_struct.finish()
} }
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f 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 { impl ProjectWorkspace {
pub fn load( pub fn load(
manifest: ProjectManifest, manifest: ProjectManifest,
@ -161,20 +193,6 @@ impl ProjectWorkspace {
config: &CargoConfig, config: &CargoConfig,
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> anyhow::Result<ProjectWorkspace> { ) -> 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 { let res = match manifest {
ProjectManifest::ProjectJson(project_json) => { ProjectManifest::ProjectJson(project_json) => {
let file = fs::read_to_string(project_json) let file = fs::read_to_string(project_json)
@ -182,30 +200,14 @@ impl ProjectWorkspace {
let data = serde_json::from_str(&file) let data = serde_json::from_str(&file)
.with_context(|| format!("Failed to deserialize json file {project_json}"))?; .with_context(|| format!("Failed to deserialize json file {project_json}"))?;
let project_location = project_json.parent().to_path_buf(); let project_location = project_json.parent().to_path_buf();
let toolchain = version(&*project_location, toolchain::rustc(), "rustc ")?; let project_json: ProjectJson = ProjectJson::new(&project_location, data);
let project_json = ProjectJson::new(&project_location, data);
ProjectWorkspace::load_inline( ProjectWorkspace::load_inline(
project_json, project_json,
config.target.as_deref(), config.target.as_deref(),
&config.extra_env, &config.extra_env,
toolchain,
) )
} }
ProjectManifest::CargoToml(cargo_toml) => { 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) { let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => { (Some(RustLibSource::Path(path)), None) => {
Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata).map_err(|e| { 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)) => { (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)) => { (Some(RustLibSource::Discover), Some(sysroot_src)) => {
Sysroot::discover_with_src_override( Sysroot::discover_with_src_override(
@ -231,18 +233,19 @@ impl ProjectWorkspace {
} }
(None, _) => Err(None), (None, _) => Err(None),
}; };
let sysroot_ref = sysroot.as_ref().ok();
if let Ok(sysroot) = &sysroot { 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 { let rustc_dir = match &config.rustc_source {
Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone()) Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
.map_err(|p| Some(format!("rustc source path is not absolute: {p}"))), .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
Some(RustLibSource::Discover) => { Some(RustLibSource::Discover) => {
sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else( sysroot_ref.and_then(Sysroot::discover_rustc_src).ok_or_else(|| {
|| Some("Failed to discover rustc source for sysroot.".to_owned()), Some("Failed to discover rustc source for sysroot.".to_owned())
) })
} }
None => Err(None), None => Err(None),
}; };
@ -256,6 +259,7 @@ impl ProjectWorkspace {
features: crate::CargoFeatures::default(), features: crate::CargoFeatures::default(),
..config.clone() ..config.clone()
}, },
sysroot_ref,
progress, progress,
) { ) {
Ok(meta) => { Ok(meta) => {
@ -264,6 +268,7 @@ impl ProjectWorkspace {
&workspace, &workspace,
cargo_toml.parent(), cargo_toml.parent(),
&config.extra_env, &config.extra_env,
sysroot_ref
); );
Ok(Box::new((workspace, buildscripts))) 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( let rustc_cfg = rustc_cfg::get(
config.target.as_deref(), config.target.as_deref(),
&config.extra_env, &config.extra_env,
RustcCfgConfig::Cargo(cargo_toml), RustcCfgConfig::Cargo(sysroot_ref, cargo_toml),
); );
let cfg_overrides = config.cfg_overrides.clone(); let cfg_overrides = config.cfg_overrides.clone();
let data_layout = target_data_layout::get( let data_layout = target_data_layout::get(
Some(cargo_toml), RustcDataLayoutConfig::Cargo(sysroot_ref, cargo_toml),
config.target.as_deref(), config.target.as_deref(),
&config.extra_env, &config.extra_env,
); );
if let Err(e) = &data_layout { if let Err(e) = &data_layout {
tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace"); 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 { ProjectWorkspace::Cargo {
cargo, cargo,
build_scripts: WorkspaceBuildScripts::default(), build_scripts: WorkspaceBuildScripts::default(),
@ -314,15 +340,16 @@ impl ProjectWorkspace {
project_json: ProjectJson, project_json: ProjectJson,
target: Option<&str>, target: Option<&str>,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
toolchain: Option<Version>,
) -> ProjectWorkspace { ) -> ProjectWorkspace {
let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) { 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) => { (Some(sysroot), None) => {
// assume sysroot is structured like rustup's and guess `sysroot_src` // assume sysroot is structured like rustup's and guess `sysroot_src`
let sysroot_src = let sysroot_src =
sysroot.join("lib").join("rustlib").join("src").join("rust").join("library"); 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)) => { (None, Some(sysroot_src)) => {
// assume sysroot is structured like rustup's and guess `sysroot` // assume sysroot is structured like rustup's and guess `sysroot`
@ -330,23 +357,32 @@ impl ProjectWorkspace {
for _ in 0..5 { for _ in 0..5 {
sysroot.pop(); sysroot.pop();
} }
Ok(Sysroot::load(sysroot, sysroot_src, false)) Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
} }
(None, None) => Err(None), (None, None) => Err(None),
}; };
let config = match &sysroot { let sysroot_ref = sysroot.as_ref().ok();
Ok(sysroot) => { let cfg_config = RustcCfgConfig::Rustc(sysroot_ref);
tracing::debug!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); let data_layout_config = RustcDataLayoutConfig::Rustc(sysroot_ref);
RustcCfgConfig::Explicit(sysroot) 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 ")
Err(_) => { {
tracing::debug!("discovering sysroot"); Ok(it) => it,
RustcCfgConfig::Discover Err(e) => {
tracing::error!("{e}");
None
} }
}; };
let rustc_cfg = rustc_cfg::get(target, extra_env, config); let rustc_cfg = rustc_cfg::get(target, extra_env, cfg_config);
ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain } 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( pub fn load_detached_files(
@ -373,18 +409,11 @@ impl ProjectWorkspace {
} }
None => Err(None), None => Err(None),
}; };
let rustc_config = match &sysroot { let rustc_cfg = rustc_cfg::get(
Ok(sysroot) => { None,
tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); &FxHashMap::default(),
RustcCfgConfig::Explicit(sysroot) RustcCfgConfig::Rustc(sysroot.as_ref().ok()),
} );
Err(_) => {
tracing::info!("discovering sysroot");
RustcCfgConfig::Discover
}
};
let rustc_cfg = rustc_cfg::get(None, &FxHashMap::default(), rustc_config);
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg }) Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
} }
@ -395,11 +424,17 @@ impl ProjectWorkspace {
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> anyhow::Result<WorkspaceBuildScripts> { ) -> anyhow::Result<WorkspaceBuildScripts> {
match self { match self {
ProjectWorkspace::Cargo { cargo, toolchain, .. } => { ProjectWorkspace::Cargo { cargo, toolchain, sysroot, .. } => {
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain) WorkspaceBuildScripts::run_for_workspace(
.with_context(|| { config,
format!("Failed to run build scripts for {}", cargo.workspace_root()) cargo,
}) progress,
toolchain,
sysroot.as_ref().ok(),
)
.with_context(|| {
format!("Failed to run build scripts for {}", cargo.workspace_root())
})
} }
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => { ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
Ok(WorkspaceBuildScripts::default()) Ok(WorkspaceBuildScripts::default())
@ -472,18 +507,7 @@ impl ProjectWorkspace {
ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. } ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
| ProjectWorkspace::Json { sysroot: Ok(sysroot), .. } | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
| ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => { | ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => {
let standalone_server_name = sysroot.discover_proc_macro_srv()
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()
)
})
} }
ProjectWorkspace::DetachedFiles { .. } => { ProjectWorkspace::DetachedFiles { .. } => {
Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found")) 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 return type contains the path and whether or not
/// the root is a member of the current workspace /// the root is a member of the current workspace
pub fn to_roots(&self) -> Vec<PackageRoot> { pub fn to_roots(&self) -> Vec<PackageRoot> {
let mk_sysroot = |sysroot: Result<_, _>, project_root: Option<&AbsPath>| { let mk_sysroot = |sysroot: Result<_, _>| {
let project_root = project_root.map(ToOwned::to_owned);
sysroot.into_iter().flat_map(move |sysroot: &Sysroot| { sysroot.into_iter().flat_map(move |sysroot: &Sysroot| {
let mut r = match sysroot.mode() { let mut r = match sysroot.mode() {
SysrootMode::Workspace(ws) => ws SysrootMode::Workspace(ws) => ws
@ -532,18 +555,21 @@ impl ProjectWorkspace {
}; };
r.push(PackageRoot { r.push(PackageRoot {
// mark the sysroot as mutable if it is located inside of the project is_local: false,
is_local: project_root include: sysroot.src_root().map(|it| it.to_path_buf()).into_iter().collect(),
.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(), exclude: Vec::new(),
}); });
r r
}) })
}; };
match self { match self {
ProjectWorkspace::Json { project, sysroot, rustc_cfg: _, toolchain: _ } => project ProjectWorkspace::Json {
project,
sysroot,
rustc_cfg: _,
toolchain: _,
target_layout: _,
} => project
.crates() .crates()
.map(|(_, krate)| PackageRoot { .map(|(_, krate)| PackageRoot {
is_local: krate.is_workspace_member, is_local: krate.is_workspace_member,
@ -552,7 +578,7 @@ impl ProjectWorkspace {
}) })
.collect::<FxHashSet<_>>() .collect::<FxHashSet<_>>()
.into_iter() .into_iter()
.chain(mk_sysroot(sysroot.as_ref(), Some(project.path()))) .chain(mk_sysroot(sysroot.as_ref()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
ProjectWorkspace::Cargo { ProjectWorkspace::Cargo {
cargo, cargo,
@ -602,7 +628,7 @@ impl ProjectWorkspace {
} }
PackageRoot { is_local, include, exclude } 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, _)| { .chain(rustc.iter().map(|a| a.as_ref()).flat_map(|(rustc, _)| {
rustc.packages().map(move |krate| PackageRoot { rustc.packages().map(move |krate| PackageRoot {
is_local: false, is_local: false,
@ -619,7 +645,7 @@ impl ProjectWorkspace {
include: vec![detached_file.clone()], include: vec![detached_file.clone()],
exclude: Vec::new(), exclude: Vec::new(),
}) })
.chain(mk_sysroot(sysroot.as_ref(), None)) .chain(mk_sysroot(sysroot.as_ref()))
.collect(), .collect(),
} }
} }
@ -651,14 +677,17 @@ impl ProjectWorkspace {
let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered(); let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered();
let (mut crate_graph, proc_macros) = match self { 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( project_json_to_crate_graph(
rustc_cfg.clone(), rustc_cfg.clone(),
load, load,
project, project,
sysroot.as_ref().ok(), sysroot.as_ref().ok(),
extra_env, 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(), toolchain.clone(),
) )
} }
@ -735,12 +764,13 @@ impl ProjectWorkspace {
&& sysroot == o_sysroot && sysroot == o_sysroot
} }
( (
Self::Json { project, sysroot, rustc_cfg, toolchain }, Self::Json { project, sysroot, rustc_cfg, toolchain, target_layout: _ },
Self::Json { Self::Json {
project: o_project, project: o_project,
sysroot: o_sysroot, sysroot: o_sysroot,
rustc_cfg: o_rustc_cfg, rustc_cfg: o_rustc_cfg,
toolchain: o_toolchain, toolchain: o_toolchain,
target_layout: _,
}, },
) => { ) => {
project == o_project project == o_project
@ -813,12 +843,7 @@ fn project_json_to_crate_graph(
let target_cfgs = match target.as_deref() { let target_cfgs = match target.as_deref() {
Some(target) => cfg_cache.entry(target).or_insert_with(|| { Some(target) => cfg_cache.entry(target).or_insert_with(|| {
let rustc_cfg = match sysroot { rustc_cfg::get(Some(target), extra_env, RustcCfgConfig::Rustc(sysroot))
Some(sysroot) => RustcCfgConfig::Explicit(sysroot),
None => RustcCfgConfig::Discover,
};
rustc_cfg::get(Some(target), extra_env, rustc_cfg)
}), }),
None => &rustc_cfg, None => &rustc_cfg,
}; };
@ -959,21 +984,6 @@ fn cargo_to_crate_graph(
} }
let &TargetData { ref name, kind, is_proc_macro, ref root, .. } = &cargo[tgt]; 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 Some(file_id) = load(root) else { continue };
let crate_id = add_target_crate_root( let crate_id = add_target_crate_root(

View File

@ -37,7 +37,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -70,7 +70,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -103,7 +103,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -136,7 +136,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -186,7 +186,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -219,7 +219,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -317,7 +317,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -350,7 +350,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -383,7 +383,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -416,7 +416,7 @@
), ),
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },
@ -493,7 +493,7 @@
}, },
is_proc_macro: false, is_proc_macro: false,
target_layout: Err( target_layout: Err(
"rust-project.json projects have no target layout set", "test has no data layout",
), ),
toolchain: None, toolchain: None,
}, },

View File

@ -1937,6 +1937,7 @@ fn run_rustfmt(
let mut command = match snap.config.rustfmt() { let mut command = match snap.config.rustfmt() {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { 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()); let mut cmd = process::Command::new(toolchain::rustfmt());
cmd.envs(snap.config.extra_env()); cmd.envs(snap.config.extra_env());
cmd.args(extra_args); cmd.args(extra_args);

View File

@ -24,7 +24,7 @@ use ide_db::{
use itertools::Itertools; use itertools::Itertools;
use load_cargo::{load_proc_macro, ProjectFolders}; use load_cargo::{load_proc_macro, ProjectFolders};
use proc_macro_api::ProcMacroServer; use proc_macro_api::ProcMacroServer;
use project_model::{ProjectWorkspace, WorkspaceBuildScripts}; use project_model::{ProjectWorkspace, Sysroot, WorkspaceBuildScripts};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use stdx::{format_to, thread::ThreadIntent}; use stdx::{format_to, thread::ThreadIntent};
use triomphe::Arc; use triomphe::Arc;
@ -234,7 +234,6 @@ impl GlobalState {
it.clone(), it.clone(),
cargo_config.target.as_deref(), cargo_config.target.as_deref(),
&cargo_config.extra_env, &cargo_config.extra_env,
None,
)) ))
} }
}) })
@ -605,6 +604,7 @@ impl GlobalState {
0, 0,
Box::new(move |msg| sender.send(msg).unwrap()), Box::new(move |msg| sender.send(msg).unwrap()),
config, config,
toolchain::cargo(),
self.config.root_path().clone(), self.config.root_path().clone(),
)], )],
flycheck::InvocationStrategy::PerWorkspace => { flycheck::InvocationStrategy::PerWorkspace => {
@ -612,23 +612,35 @@ impl GlobalState {
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(id, w)| match w { .filter_map(|(id, w)| match w {
ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())), ProjectWorkspace::Cargo { cargo, sysroot, .. } => Some((
ProjectWorkspace::Json { project, .. } => { 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 // Enable flychecks for json projects if a custom flycheck command was supplied
// in the workspace configuration. // in the workspace configuration.
match config { 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, _ => None,
} }
} }
ProjectWorkspace::DetachedFiles { .. } => None, ProjectWorkspace::DetachedFiles { .. } => None,
}) })
.map(|(id, root)| { .map(|(id, root, cargo)| {
let sender = sender.clone(); let sender = sender.clone();
FlycheckHandle::spawn( FlycheckHandle::spawn(
id, id,
Box::new(move |msg| sender.send(msg).unwrap()), Box::new(move |msg| sender.send(msg).unwrap()),
config.clone(), config.clone(),
cargo.unwrap_or_else(|_| toolchain::cargo()),
root.to_path_buf(), root.to_path_buf(),
) )
}) })

View File

@ -911,20 +911,18 @@ fn root_contains_symlink_out_dirs_check() {
#[cfg(any(feature = "sysroot-abi", rust_analyzer))] #[cfg(any(feature = "sysroot-abi", rust_analyzer))]
fn resolve_proc_macro() { fn resolve_proc_macro() {
use expect_test::expect; use expect_test::expect;
use vfs::AbsPathBuf;
if skip_slow_tests() { if skip_slow_tests() {
return; return;
} }
// skip using the sysroot config as to prevent us from loading the sysroot sources let sysroot = project_model::Sysroot::discover_no_source(
let mut rustc = std::process::Command::new(toolchain::rustc()); &AbsPathBuf::assert(std::env::current_dir().unwrap()),
rustc.args(["--print", "sysroot"]); &Default::default(),
let output = rustc.output().unwrap(); )
let sysroot = .unwrap();
vfs::AbsPathBuf::try_from(std::str::from_utf8(&output.stdout).unwrap().trim()).unwrap();
let standalone_server_name = let proc_macro_server_path = sysroot.discover_proc_macro_srv().unwrap();
format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
let proc_macro_server_path = sysroot.join("libexec").join(&standalone_server_name);
let server = Project::with_fixture( let server = Project::with_fixture(
r###" r###"

View File

@ -2,7 +2,41 @@
#![warn(rust_2018_idioms, unused_lifetimes)] #![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 { pub fn cargo() -> PathBuf {
get_path_for_executable("cargo") 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() { if let Some(mut path) = get_cargo_home() {
path.push("bin"); path.push("bin");
path.push(executable_name); path.push(executable_name);
if let Some(path) = probe(path) { if let Some(path) = probe_for_binary(path) {
return path; return path;
} }
} }
@ -57,7 +91,7 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
fn lookup_in_path(exec: &str) -> bool { fn lookup_in_path(exec: &str) -> bool {
let paths = env::var_os("PATH").unwrap_or_default(); 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> { fn get_cargo_home() -> Option<PathBuf> {
@ -73,7 +107,7 @@ fn get_cargo_home() -> Option<PathBuf> {
None None
} }
fn probe(path: PathBuf) -> Option<PathBuf> { pub fn probe_for_binary(path: PathBuf) -> Option<PathBuf> {
let with_extension = match env::consts::EXE_EXTENSION { let with_extension = match env::consts::EXE_EXTENSION {
"" => None, "" => None,
it => Some(path.with_extension(it)), it => Some(path.with_extension(it)),