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