diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 68b592ffaa4d..0c8294846d53 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -40,7 +40,7 @@ pub fn load_workspace_at( ) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option)> { let root = AbsPathBuf::assert(std::env::current_dir()?.join(root)); let root = ProjectManifest::discover_single(&root)?; - let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?; + let mut workspace = ProjectWorkspace::load(root, cargo_config, progress, false)?; if load_config.load_out_dirs_from_check { let build_scripts = workspace.run_build_scripts(cargo_config, progress)?; @@ -81,6 +81,7 @@ pub fn load_workspace( vfs.file_id(&path) }, extra_env, + None, ); let proc_macros = { let proc_macro_server = match &proc_macro_server { diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 901dcfd2b110..a94e72ce9d74 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -50,7 +50,7 @@ pub use crate::{ manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, sysroot::Sysroot, - workspace::{CfgOverrides, PackageRoot, ProjectWorkspace}, + workspace::{CfgOverrides, PackageRoot, ProjectWorkspace, RustcWorkspace}, }; #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 98f3063bb982..a0f4d5747650 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -11,8 +11,8 @@ use rustc_hash::FxHashMap; use serde::de::DeserializeOwned; use crate::{ - CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot, - WorkspaceBuildScripts, + CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, RustcWorkspace, + Sysroot, WorkspaceBuildScripts, }; fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) { @@ -29,7 +29,7 @@ fn load_cargo_with_overrides( cargo: cargo_workspace, build_scripts: WorkspaceBuildScripts::default(), sysroot: Err(None), - rustc: Err(None), + rustc: RustcWorkspace::Loaded(Err(None)), rustc_cfg: Vec::new(), cfg_overrides, toolchain: None, @@ -48,7 +48,7 @@ fn load_cargo_with_sysroot( cargo: cargo_workspace, build_scripts: WorkspaceBuildScripts::default(), sysroot: Ok(get_fake_sysroot()), - rustc: Err(None), + rustc: RustcWorkspace::Loaded(Err(None)), rustc_cfg: Vec::new(), cfg_overrides: Default::default(), toolchain: None, @@ -62,6 +62,7 @@ fn load_cargo_with_sysroot( } }, &Default::default(), + None, ) } @@ -146,6 +147,7 @@ fn to_crate_graph(project_workspace: ProjectWorkspace) -> (CrateGraph, ProcMacro } }, &Default::default(), + None, ) } diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 9333570354a0..311fbb4e2fd2 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -23,8 +23,8 @@ use crate::{ project_json::Crate, rustc_cfg::{self, RustcCfgConfig}, sysroot::SysrootCrate, - target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, - Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, + target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, Package, + ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, }; /// A set of cfg-overrides per crate. @@ -53,6 +53,26 @@ pub struct PackageRoot { pub exclude: Vec, } +#[derive(Clone, PartialEq)] +pub enum RustcWorkspace { + /// A globally-configured rustc source location is being opened as a rust-analyzer workspace + Opening, + /// The rustc source is loaded, e.g. from sysroot, but is not a rust-analyzer workspace + Loaded(Result<(CargoWorkspace, WorkspaceBuildScripts), Option>), +} + +impl RustcWorkspace { + /// Returns the loaded `CargoWorkspace` of the rustc source. + /// Will be `None` if either the rustc source is opened as a rust-analyzer workspace + /// or its loading failed. + fn loaded(&self) -> Option<&(CargoWorkspace, WorkspaceBuildScripts)> { + match self { + Self::Opening => None, + Self::Loaded(res) => res.as_ref().ok(), + } + } +} + #[derive(Clone)] pub enum ProjectWorkspace { /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. @@ -60,7 +80,7 @@ pub enum ProjectWorkspace { cargo: CargoWorkspace, build_scripts: WorkspaceBuildScripts, sysroot: Result>, - rustc: Result<(CargoWorkspace, WorkspaceBuildScripts), Option>, + rustc: RustcWorkspace, /// Holds cfg flags for the current target. We get those by running /// `rustc --print cfg`. /// @@ -119,7 +139,7 @@ impl fmt::Debug for ProjectWorkspace { .field("sysroot", &sysroot.is_ok()) .field( "n_rustc_compiler_crates", - &rustc.as_ref().map_or(0, |(rc, _)| rc.packages().len()), + &rustc.loaded().map_or(0, |(rc, _)| rc.packages().len()), ) .field("n_rustc_cfg", &rustc_cfg.len()) .field("n_cfg_overrides", &cfg_overrides.len()) @@ -151,8 +171,9 @@ impl ProjectWorkspace { manifest: ProjectManifest, config: &CargoConfig, progress: &dyn Fn(String), + opening_rustc_workspace: bool, ) -> anyhow::Result { - ProjectWorkspace::load_inner(&manifest, config, progress) + ProjectWorkspace::load_inner(&manifest, config, progress, opening_rustc_workspace) .with_context(|| format!("Failed to load the project at {manifest}")) } @@ -160,6 +181,7 @@ impl ProjectWorkspace { manifest: &ProjectManifest, config: &CargoConfig, progress: &dyn Fn(String), + opening_rustc_workspace: bool, ) -> anyhow::Result { let version = |current_dir, cmd_path, prefix: &str| { let cargo_version = utf8_stdout({ @@ -236,48 +258,56 @@ impl ProjectWorkspace { 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(format!("Failed to discover rustc source for sysroot.")), - ) - } - None => Err(None), - }; - - let rustc = rustc_dir.and_then(|rustc_dir| { - tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source"); - match CargoWorkspace::fetch_metadata( - &rustc_dir, - cargo_toml.parent(), - &CargoConfig { - features: crate::CargoFeatures::default(), - ..config.clone() - }, - progress, - ) { - Ok(meta) => { - let workspace = CargoWorkspace::new(meta); - let buildscripts = WorkspaceBuildScripts::rustc_crates( - &workspace, - cargo_toml.parent(), - &config.extra_env, - ); - Ok((workspace, buildscripts)) + let rustc = if opening_rustc_workspace { + RustcWorkspace::Opening + } else { + let rustc_dir = match &config.rustc_source { + // `config.rustc_source == Some(Path(...))` while `!opening_rustc_workspace` should only occur if + // `ManifestPath::try_from(rustc_dir)` failed in `fetch_workspaces`, so no need to attempt it here + // again. + Some(RustLibSource::Path(path)) => { + Err(Some(format!("rustc source path is not absolute: {path}"))) } - Err(e) => { - tracing::error!( - %e, - "Failed to read Cargo metadata from rustc source at {rustc_dir}", - ); - Err(Some(format!( - "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}" - ))) + Some(RustLibSource::Discover) => { + sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else( + || Some(format!("Failed to discover rustc source for sysroot.")), + ) } - } - }); + None => Err(None), + }; + + RustcWorkspace::Loaded(rustc_dir.and_then(|rustc_dir| { + tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source"); + match CargoWorkspace::fetch_metadata( + &rustc_dir, + cargo_toml.parent(), + &CargoConfig { + features: crate::CargoFeatures::default(), + ..config.clone() + }, + progress, + ) { + Ok(meta) => { + let workspace = CargoWorkspace::new(meta); + let buildscripts = WorkspaceBuildScripts::rustc_crates( + &workspace, + cargo_toml.parent(), + &config.extra_env, + ); + Ok((workspace, buildscripts)) + } + Err(e) => { + tracing::error!( + %e, + "Failed to read Cargo metadata from rustc source at {rustc_dir}", + ); + Err(Some(format!( + "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}" + ))) + } + } + })) + }; let rustc_cfg = rustc_cfg::get( config.target.as_deref(), @@ -564,7 +594,7 @@ impl ProjectWorkspace { PackageRoot { is_local, include, exclude } }) .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root()))) - .chain(rustc.iter().flat_map(|(rustc, _)| { + .chain(rustc.loaded().iter().flat_map(|(rustc, _)| { rustc.packages().map(move |krate| PackageRoot { is_local: false, include: vec![rustc[krate].manifest.parent().to_path_buf()], @@ -592,7 +622,7 @@ impl ProjectWorkspace { sysroot_package_len + project.n_crates() } ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => { - let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len()); + let rustc_package_len = rustc.loaded().map_or(0, |(it, _)| it.packages().len()); let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len()); cargo.packages().len() + sysroot_package_len + rustc_package_len } @@ -607,6 +637,7 @@ impl ProjectWorkspace { &self, load: &mut dyn FnMut(&AbsPath) -> Option, extra_env: &FxHashMap, + opened_rustc_workspace: Option<(&CargoWorkspace, &WorkspaceBuildScripts)>, ) -> (CrateGraph, ProcMacroPaths) { let _p = profile::span("ProjectWorkspace::to_crate_graph"); @@ -633,7 +664,10 @@ impl ProjectWorkspace { target_layout, } => cargo_to_crate_graph( load, - rustc.as_ref().ok(), + match rustc { + RustcWorkspace::Opening => opened_rustc_workspace, + RustcWorkspace::Loaded(res) => res.as_ref().ok().map(|(a, b)| (a, b)), + }, cargo, sysroot.as_ref().ok(), rustc_cfg.clone(), @@ -844,7 +878,7 @@ fn project_json_to_crate_graph( fn cargo_to_crate_graph( load: &mut dyn FnMut(&AbsPath) -> Option, - rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, + rustc: Option<(&CargoWorkspace, &WorkspaceBuildScripts)>, cargo: &CargoWorkspace, sysroot: Option<&Sysroot>, rustc_cfg: Vec, @@ -1030,13 +1064,7 @@ fn cargo_to_crate_graph( &pkg_crates, &cfg_options, override_cfg, - if rustc_workspace.workspace_root() == cargo.workspace_root() { - // the rustc workspace does not use the installed toolchain's proc-macro server - // so we need to make sure we don't use the pre compiled proc-macros there either - build_scripts - } else { - rustc_build_scripts - }, + rustc_build_scripts, target_layout, channel, ); diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 0f6539f224d7..00026520f1b5 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -70,7 +70,7 @@ impl flags::AnalysisStats { let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); let manifest = ProjectManifest::discover_single(&path)?; - let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; + let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress, false)?; let metadata_time = db_load_sw.elapsed(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: !self.disable_build_scripts, diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index d6a45ce06f21..294268b30f9c 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -299,7 +299,7 @@ impl flags::Lsif { let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); let manifest = ProjectManifest::discover_single(&path)?; - let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; + let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress, false)?; let (host, vfs, _proc_macro) = load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?; diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index abe2191f4002..6a4997b21f8d 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -25,7 +25,10 @@ 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::{ + ManifestPath, ProjectManifest, ProjectWorkspace, RustLibSource, RustcWorkspace, + WorkspaceBuildScripts, +}; use rustc_hash::FxHashSet; use stdx::{format_to, thread::ThreadIntent}; use triomphe::Arc; @@ -166,7 +169,8 @@ impl GlobalState { } } } - if let ProjectWorkspace::Cargo { rustc: Err(Some(e)), .. } = ws { + if let ProjectWorkspace::Cargo { rustc: RustcWorkspace::Loaded(Err(Some(e))), .. } = ws + { status.health = lsp_ext::Health::Warning; message.push_str(e); message.push_str("\n\n"); @@ -188,7 +192,7 @@ impl GlobalState { tracing::info!(%cause, "will fetch workspaces"); self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, { - let linked_projects = self.config.linked_projects(); + let mut linked_projects = self.config.linked_projects(); let detached_files = self.config.detached_files().to_vec(); let cargo_config = self.config.cargo(); @@ -204,6 +208,25 @@ impl GlobalState { sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap(); + let mut opening_rustc_workspace = false; + if let Some(RustLibSource::Path(rustc_dir)) = &cargo_config.rustc_source { + if let Ok(rustc_manifest) = ManifestPath::try_from(rustc_dir.clone()) { + if let Some(rustc_index) = + linked_projects.iter().position(|project| match project { + LinkedProject::ProjectManifest(ProjectManifest::CargoToml( + project_manifest, + )) => *project_manifest == rustc_manifest, + _ => false, + }) + { + // move to the front so that the ensuing workspace's build scripts will be built and available + // to other workspaces (in particular, any that have packages using rustc_private crates) + linked_projects.swap(0, rustc_index); + opening_rustc_workspace = true; + } + } + } + let mut workspaces = linked_projects .iter() .map(|project| match project { @@ -212,6 +235,7 @@ impl GlobalState { manifest.clone(), &cargo_config, &progress, + opening_rustc_workspace, ) } LinkedProject::InlineJsonProject(it) => { @@ -506,9 +530,21 @@ impl GlobalState { let mut crate_graph = CrateGraph::default(); let mut proc_macros = Vec::default(); + + // if the first workspace is `RustcWorkspace::Opening` then it is guaranteed to be the rustc source + // by virtue of the sorting in `fetch_workspaces`. + let opened_rustc_workspace = match self.workspaces.first() { + Some(ProjectWorkspace::Cargo { + rustc: RustcWorkspace::Opening, + cargo, + build_scripts, + .. + }) => Some((cargo, build_scripts)), + _ => None, + }; for ws in &**self.workspaces { let (other, mut crate_proc_macros) = - ws.to_crate_graph(&mut load, &self.config.extra_env()); + ws.to_crate_graph(&mut load, &self.config.extra_env(), opened_rustc_workspace); crate_graph.extend(other, &mut crate_proc_macros); proc_macros.push(crate_proc_macros); }