Merge remote-tracking branch 'upstream/master' into sync-from-rust

This commit is contained in:
Laurențiu Nicola 2024-01-16 08:48:23 +02:00
commit a2aee95ce3
12 changed files with 509 additions and 250 deletions

View file

@ -9,7 +9,7 @@
use std::{fmt, mem, ops, str::FromStr};
use cfg::CfgOptions;
use la_arena::{Arena, Idx};
use la_arena::{Arena, Idx, RawIdx};
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
use syntax::SmolStr;
@ -157,6 +157,10 @@ impl CrateOrigin {
pub fn is_lib(&self) -> bool {
matches!(self, CrateOrigin::Library { .. })
}
pub fn is_lang(&self) -> bool {
matches!(self, CrateOrigin::Lang { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -174,7 +178,7 @@ impl From<&str> for LangCrateOrigin {
match s {
"alloc" => LangCrateOrigin::Alloc,
"core" => LangCrateOrigin::Core,
"proc-macro" => LangCrateOrigin::ProcMacro,
"proc-macro" | "proc_macro" => LangCrateOrigin::ProcMacro,
"std" => LangCrateOrigin::Std,
"test" => LangCrateOrigin::Test,
_ => LangCrateOrigin::Other,
@ -522,7 +526,7 @@ impl CrateGraph {
self.arena.iter().map(|(idx, _)| idx)
}
// FIXME: used for `handle_hack_cargo_workspace`, should be removed later
// FIXME: used for fixing up the toolchain sysroot, should be removed and done differently
#[doc(hidden)]
pub fn iter_mut(&mut self) -> impl Iterator<Item = (CrateId, &mut CrateData)> + '_ {
self.arena.iter_mut()
@ -619,7 +623,12 @@ impl CrateGraph {
/// This will deduplicate the crates of the graph where possible.
/// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id.
/// If the crate dependencies were sorted, the resulting graph from this `extend` call will also have the crate dependencies sorted.
pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) {
pub fn extend(
&mut self,
mut other: CrateGraph,
proc_macros: &mut ProcMacroPaths,
on_finished: impl FnOnce(&FxHashMap<CrateId, CrateId>),
) {
let topo = other.crates_in_topological_order();
let mut id_map: FxHashMap<CrateId, CrateId> = FxHashMap::default();
for topo in topo {
@ -670,6 +679,8 @@ impl CrateGraph {
*proc_macros =
mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect();
on_finished(&id_map);
}
fn find_path(
@ -721,6 +732,29 @@ impl CrateGraph {
fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a {
self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
}
/// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
/// Returns a mapping from old crate ids to new crate ids.
pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> {
let mut id_map = vec![None; self.arena.len()];
self.arena = std::mem::take(&mut self.arena)
.into_iter()
.filter_map(|(id, data)| if to_keep.contains(&id) { Some((id, data)) } else { None })
.enumerate()
.map(|(new_id, (id, data))| {
id_map[id.into_raw().into_u32() as usize] =
Some(CrateId::from_raw(RawIdx::from_u32(new_id as u32)));
data
})
.collect();
for (_, data) in self.arena.iter_mut() {
data.dependencies.iter_mut().for_each(|dep| {
dep.crate_id =
id_map[dep.crate_id.into_raw().into_u32() as usize].expect("crate was filtered")
});
}
id_map
}
}
impl ops::Index<CrateId> for CrateGraph {

View file

@ -82,6 +82,8 @@ pub struct CargoConfig {
pub target: Option<String>,
/// Sysroot loading behavior
pub sysroot: Option<RustLibSource>,
/// Whether to invoke `cargo metadata` on the sysroot crate.
pub sysroot_query_metadata: bool,
pub sysroot_src: Option<AbsPathBuf>,
/// rustc private crate source
pub rustc_source: Option<RustLibSource>,

View file

@ -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<SysrootCrateData>,
/// Stores the result of `cargo metadata` of the `RA_UNSTABLE_SYSROOT_HACK` workspace.
pub hack_cargo_workspace: Option<CargoWorkspace>,
}
impl ops::Index<SysrootCrate> for Stitched {
type Output = SysrootCrateData;
fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
&self.crates[index]
}
}
impl Stitched {
pub(crate) fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
// core is added as a dependency before std in order to
// mimic rustcs dependency order
["core", "alloc", "std"]
.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<SysrootCrate> {
self.by_name("proc_macro")
}
pub(crate) fn crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_ {
self.crates.iter().map(|(id, _data)| id)
}
fn by_name(&self, name: &str) -> Option<SysrootCrate> {
let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
Some(id)
}
}
pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SysrootCrateData {
pub name: String,
pub root: ManifestPath,
pub deps: Vec<SysrootCrate>,
}
impl ops::Index<SysrootCrate> 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<SysrootCrate>,
}
impl Sysroot {
@ -53,32 +90,19 @@ impl Sysroot {
&self.src_root
}
pub fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ {
// core is added as a dependency before std in order to
// mimic rustcs dependency order
["core", "alloc", "std"]
.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<SysrootCrate> {
self.by_name("proc_macro")
}
pub fn crates(&self) -> impl Iterator<Item = SysrootCrate> + 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<String> {
if self.by_name("core").is_none() {
let has_core = match &self.mode {
SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
};
if !has_core {
let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
" (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
} else {
@ -92,27 +116,43 @@ impl Sysroot {
None
}
}
pub fn num_packages(&self) -> usize {
match &self.mode {
SysrootMode::Workspace(ws) => ws.packages().count(),
SysrootMode::Stitched(c) => c.crates().count(),
}
}
pub(crate) fn mode(&self) -> &SysrootMode {
&self.mode
}
}
// FIXME: Expose a builder api as loading the sysroot got way too modular and complicated.
impl Sysroot {
/// Attempts to discover the toolchain's sysroot from the given `dir`.
pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
pub fn discover(
dir: &AbsPath,
extra_env: &FxHashMap<String, String>,
metadata: bool,
) -> Result<Sysroot> {
tracing::debug!("discovering sysroot for {dir}");
let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
let sysroot_src_dir =
discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir, metadata))
}
pub fn discover_with_src_override(
current_dir: &AbsPath,
extra_env: &FxHashMap<String, String>,
src: AbsPathBuf,
metadata: bool,
) -> Result<Sysroot> {
tracing::debug!("discovering sysroot for {current_dir}");
let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?;
Ok(Sysroot::load(sysroot_dir, src))
Ok(Sysroot::load(sysroot_dir, src, metadata))
}
pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
@ -131,49 +171,132 @@ impl Sysroot {
}
}
pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf, metadata: bool) -> Result<Sysroot> {
let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
format_err!("can't load standard library from sysroot path {sysroot_dir}")
})?;
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir, metadata))
}
pub fn load(sysroot_dir: AbsPathBuf, mut sysroot_src_dir: AbsPathBuf) -> Sysroot {
// FIXME: Remove this `hack_cargo_workspace` field completely once we support sysroot dependencies
let hack_cargo_workspace = if let Ok(path) = std::env::var("RA_UNSTABLE_SYSROOT_HACK") {
let cargo_toml = ManifestPath::try_from(
AbsPathBuf::try_from(&*format!("{path}/Cargo.toml")).unwrap(),
)
.unwrap();
sysroot_src_dir = AbsPathBuf::try_from(&*path).unwrap().join("library");
CargoWorkspace::fetch_metadata(
&cargo_toml,
&AbsPathBuf::try_from("/").unwrap(),
&CargoConfig::default(),
&|_| (),
)
.map(CargoWorkspace::new)
.ok()
} else {
None
};
let mut sysroot = Sysroot {
root: sysroot_dir,
src_root: sysroot_src_dir,
crates: Arena::default(),
hack_cargo_workspace,
};
pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf, metadata: bool) -> Sysroot {
if metadata {
let sysroot: Option<_> = (|| {
let sysroot_cargo_toml = ManifestPath::try_from(
AbsPathBuf::try_from(&*format!("{sysroot_src_dir}/sysroot/Cargo.toml")).ok()?,
)
.ok()?;
let current_dir =
AbsPathBuf::try_from(&*format!("{sysroot_src_dir}/sysroot")).ok()?;
let res = CargoWorkspace::fetch_metadata(
&sysroot_cargo_toml,
&current_dir,
&CargoConfig::default(),
&|_| (),
)
.map_err(|e| {
tracing::error!(
"failed to load sysroot `{sysroot_src_dir}/sysroot/Cargo.toml`: {}",
e
);
e
});
if let Err(e) =
std::fs::remove_file(&format!("{sysroot_src_dir}/sysroot/Cargo.lock"))
{
tracing::error!(
"failed to remove sysroot `{sysroot_src_dir}/sysroot/Cargo.lock`: {}",
e
)
}
let mut res = res.ok()?;
// Patch out `rustc-std-workspace-*` crates to point to the real crates.
// This is done prior to `CrateGraph` construction to avoid having duplicate `std` targets.
let mut fake_core = None;
let mut fake_alloc = None;
let mut fake_std = None;
let mut real_core = None;
let mut real_alloc = None;
let mut real_std = None;
res.packages.iter().enumerate().for_each(|(idx, package)| {
match package.name.strip_prefix("rustc-std-workspace-") {
Some("core") => fake_core = Some((idx, package.id.clone())),
Some("alloc") => fake_alloc = Some((idx, package.id.clone())),
Some("std") => fake_std = Some((idx, package.id.clone())),
Some(_) => {
tracing::warn!("unknown rustc-std-workspace-* crate: {}", package.name)
}
None => match &*package.name {
"core" => real_core = Some(package.id.clone()),
"alloc" => real_alloc = Some(package.id.clone()),
"std" => real_std = Some(package.id.clone()),
_ => (),
},
}
});
let patches =
[fake_core.zip(real_core), fake_alloc.zip(real_alloc), fake_std.zip(real_std)]
.into_iter()
.flatten();
let resolve = res.resolve.as_mut().expect("metadata executed with deps");
let mut remove_nodes = vec![];
for (idx, node) in resolve.nodes.iter_mut().enumerate() {
// Replace them in the dependency list
node.deps.iter_mut().for_each(|dep| {
if let Some((_, real)) =
patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg)
{
dep.pkg = real;
}
});
if patches.clone().any(|((_, fake), _)| fake == node.id) {
remove_nodes.push(idx);
}
}
// Remove the fake ones from the resolve data
remove_nodes.into_iter().rev().for_each(|r| {
resolve.nodes.remove(r);
});
// Remove the fake ones from the packages
patches.map(|((r, _), _)| r).sorted().rev().for_each(|r| {
res.packages.remove(r);
});
res.workspace_members = res
.packages
.iter()
.filter_map(|package| {
RELEVANT_SYSROOT_CRATES
.contains(&&*package.name)
.then(|| package.id.clone())
})
.collect();
let cargo_workspace = CargoWorkspace::new(res);
Some(Sysroot {
root: sysroot_dir.clone(),
src_root: sysroot_src_dir.clone(),
mode: SysrootMode::Workspace(cargo_workspace),
})
})();
if let Some(sysroot) = sysroot {
return sysroot;
}
}
let mut stitched = Stitched { crates: Arena::default() };
for path in SYSROOT_CRATES.trim().lines() {
let name = path.split('/').last().unwrap();
let root = [format!("{path}/src/lib.rs"), format!("lib{path}/lib.rs")]
.into_iter()
.map(|it| sysroot.src_root.join(it))
.map(|it| sysroot_src_dir.join(it))
.filter_map(|it| ManifestPath::try_from(it).ok())
.find(|it| fs::metadata(it).is_ok());
if let Some(root) = root {
sysroot.crates.alloc(SysrootCrateData {
stitched.crates.alloc(SysrootCrateData {
name: name.into(),
root,
deps: Vec::new(),
@ -181,36 +304,34 @@ impl Sysroot {
}
}
if let Some(std) = sysroot.by_name("std") {
if let Some(std) = stitched.by_name("std") {
for dep in STD_DEPS.trim().lines() {
if let Some(dep) = sysroot.by_name(dep) {
sysroot.crates[std].deps.push(dep)
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[std].deps.push(dep)
}
}
}
if let Some(alloc) = sysroot.by_name("alloc") {
if let Some(alloc) = stitched.by_name("alloc") {
for dep in ALLOC_DEPS.trim().lines() {
if let Some(dep) = sysroot.by_name(dep) {
sysroot.crates[alloc].deps.push(dep)
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[alloc].deps.push(dep)
}
}
}
if let Some(proc_macro) = sysroot.by_name("proc_macro") {
if let Some(proc_macro) = stitched.by_name("proc_macro") {
for dep in PROC_MACRO_DEPS.trim().lines() {
if let Some(dep) = sysroot.by_name(dep) {
sysroot.crates[proc_macro].deps.push(dep)
if let Some(dep) = stitched.by_name(dep) {
stitched.crates[proc_macro].deps.push(dep)
}
}
}
sysroot
}
fn by_name(&self, name: &str) -> Option<SysrootCrate> {
let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
Some(id)
Sysroot {
root: sysroot_dir,
src_root: sysroot_src_dir,
mode: SysrootMode::Stitched(stitched),
}
}
}
@ -318,3 +439,5 @@ test";
const PROC_MACRO_DEPS: &str = "
std
core";
const RELEVANT_SYSROOT_CRATES: &[&str] = &["core", "alloc", "std", "test", "proc_macro"];

View file

@ -38,7 +38,7 @@ fn load_cargo_with_overrides(
to_crate_graph(project_workspace)
}
fn load_cargo_with_sysroot(
fn load_cargo_with_fake_sysroot(
file_map: &mut FxHashMap<AbsPathBuf, FileId>,
file: &str,
) -> (CrateGraph, ProcMacroPaths) {
@ -125,7 +125,7 @@ fn get_fake_sysroot() -> Sysroot {
// fake sysroot, so we give them both the same path:
let sysroot_dir = AbsPathBuf::assert(sysroot_path);
let sysroot_src_dir = sysroot_dir.clone();
Sysroot::load(sysroot_dir, sysroot_src_dir)
Sysroot::load(sysroot_dir, sysroot_src_dir, false)
}
fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
@ -225,12 +225,12 @@ fn rust_project_is_proc_macro_has_proc_macro_dep() {
#[test]
fn crate_graph_dedup_identical() {
let (mut crate_graph, proc_macros) =
load_cargo_with_sysroot(&mut Default::default(), "regex-metadata.json");
load_cargo_with_fake_sysroot(&mut Default::default(), "regex-metadata.json");
crate_graph.sort_deps();
let (d_crate_graph, mut d_proc_macros) = (crate_graph.clone(), proc_macros.clone());
crate_graph.extend(d_crate_graph.clone(), &mut d_proc_macros);
crate_graph.extend(d_crate_graph.clone(), &mut d_proc_macros, |_| ());
assert!(crate_graph.iter().eq(d_crate_graph.iter()));
assert_eq!(proc_macros, d_proc_macros);
}
@ -239,14 +239,14 @@ fn crate_graph_dedup_identical() {
fn crate_graph_dedup() {
let path_map = &mut Default::default();
let (mut crate_graph, _proc_macros) =
load_cargo_with_sysroot(path_map, "ripgrep-metadata.json");
load_cargo_with_fake_sysroot(path_map, "ripgrep-metadata.json");
assert_eq!(crate_graph.iter().count(), 81);
crate_graph.sort_deps();
let (regex_crate_graph, mut regex_proc_macros) =
load_cargo_with_sysroot(path_map, "regex-metadata.json");
load_cargo_with_fake_sysroot(path_map, "regex-metadata.json");
assert_eq!(regex_crate_graph.iter().count(), 60);
crate_graph.extend(regex_crate_graph, &mut regex_proc_macros);
crate_graph.extend(regex_crate_graph, &mut regex_proc_macros, |_| ());
assert_eq!(crate_graph.iter().count(), 118);
}
@ -254,12 +254,12 @@ fn crate_graph_dedup() {
fn test_deduplicate_origin_dev() {
let path_map = &mut Default::default();
let (mut crate_graph, _proc_macros) =
load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_A.json");
crate_graph.sort_deps();
let (crate_graph_1, mut _proc_macros_2) =
load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_B.json");
crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
crate_graph.extend(crate_graph_1, &mut _proc_macros_2, |_| ());
let mut crates_named_p2 = vec![];
for id in crate_graph.iter() {
@ -280,12 +280,12 @@ fn test_deduplicate_origin_dev() {
fn test_deduplicate_origin_dev_rev() {
let path_map = &mut Default::default();
let (mut crate_graph, _proc_macros) =
load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json");
load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_B.json");
crate_graph.sort_deps();
let (crate_graph_1, mut _proc_macros_2) =
load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json");
load_cargo_with_fake_sysroot(path_map, "deduplication_crate_graph_A.json");
crate_graph.extend(crate_graph_1, &mut _proc_macros_2);
crate_graph.extend(crate_graph_1, &mut _proc_macros_2, |_| ());
let mut crates_named_p2 = vec![];
for id in crate_graph.iter() {
@ -301,3 +301,40 @@ fn test_deduplicate_origin_dev_rev() {
let p2 = crates_named_p2[0];
assert!(p2.origin.is_local());
}
#[test]
fn smoke_test_real_sysroot_cargo() {
if std::env::var("SYSROOT_CARGO_METADATA").is_err() {
return;
}
let file_map = &mut FxHashMap::<AbsPathBuf, FileId>::default();
let meta = get_test_json_file("hello-world-metadata.json");
let cargo_workspace = CargoWorkspace::new(meta);
let sysroot = Ok(Sysroot::discover(
AbsPath::assert(Path::new(env!("CARGO_MANIFEST_DIR"))),
&Default::default(),
true,
)
.unwrap());
let project_workspace = ProjectWorkspace::Cargo {
cargo: cargo_workspace,
build_scripts: WorkspaceBuildScripts::default(),
sysroot,
rustc: Err(None),
rustc_cfg: Vec::new(),
cfg_overrides: Default::default(),
toolchain: None,
target_layout: Err("target_data_layout not loaded".into()),
};
project_workspace.to_crate_graph(
&mut {
|path| {
let len = file_map.len();
Some(*file_map.entry(path.to_path_buf()).or_insert(FileId::from_raw(len as u32)))
}
},
&Default::default(),
);
}

View file

@ -9,7 +9,7 @@ use base_db::{
CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, DependencyKind,
Edition, Env, FileId, LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult,
};
use cfg::{CfgDiff, CfgOptions};
use cfg::{CfgAtom, CfgDiff, CfgOptions};
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::{FxHashMap, FxHashSet};
use semver::Version;
@ -22,7 +22,7 @@ use crate::{
cfg_flag::CfgFlag,
project_json::Crate,
rustc_cfg::{self, RustcCfgConfig},
sysroot::SysrootCrate,
sysroot::{SysrootCrate, SysrootMode},
target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath,
Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
};
@ -130,7 +130,7 @@ impl fmt::Debug for ProjectWorkspace {
let mut debug_struct = f.debug_struct("Json");
debug_struct.field("n_crates", &project.n_crates());
if let Ok(sysroot) = sysroot {
debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
debug_struct.field("n_sysroot_crates", &sysroot.num_packages());
}
debug_struct.field("toolchain", &toolchain);
debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
@ -208,23 +208,23 @@ impl ProjectWorkspace {
let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => {
Sysroot::with_sysroot_dir(path.clone()).map_err(|e| {
Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata).map_err(|e| {
Some(format!("Failed to find sysroot at {path}:{e}"))
})
}
(Some(RustLibSource::Discover), None) => {
Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| {
Sysroot::discover(cargo_toml.parent(), &config.extra_env, config.sysroot_query_metadata).map_err(|e| {
Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
}
(Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone()))
Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone(), config.sysroot_query_metadata))
}
(Some(RustLibSource::Discover), Some(sysroot_src)) => {
Sysroot::discover_with_src_override(
cargo_toml.parent(),
&config.extra_env,
sysroot_src.clone(),
sysroot_src.clone(), config.sysroot_query_metadata,
).map_err(|e| {
Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
@ -317,12 +317,12 @@ impl ProjectWorkspace {
toolchain: Option<Version>,
) -> ProjectWorkspace {
let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
(Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src)),
(Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src, false)),
(Some(sysroot), None) => {
// assume sysroot is structured like rustup's and guess `sysroot_src`
let sysroot_src =
sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
Ok(Sysroot::load(sysroot, sysroot_src))
Ok(Sysroot::load(sysroot, sysroot_src, false))
}
(None, Some(sysroot_src)) => {
// assume sysroot is structured like rustup's and guess `sysroot`
@ -330,7 +330,7 @@ impl ProjectWorkspace {
for _ in 0..5 {
sysroot.pop();
}
Ok(Sysroot::load(sysroot, sysroot_src))
Ok(Sysroot::load(sysroot, sysroot_src, false))
}
(None, None) => Err(None),
};
@ -354,16 +354,22 @@ impl ProjectWorkspace {
config: &CargoConfig,
) -> anyhow::Result<ProjectWorkspace> {
let sysroot = match &config.sysroot {
Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone())
.map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))),
Some(RustLibSource::Path(path)) => {
Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata)
.map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}")))
}
Some(RustLibSource::Discover) => {
let dir = &detached_files
.first()
.and_then(|it| it.parent())
.ok_or_else(|| format_err!("No detached files to load"))?;
Sysroot::discover(dir, &config.extra_env).map_err(|e| {
Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
})
Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata).map_err(
|e| {
Some(format!(
"Failed to find sysroot for {dir}. Is rust-src installed? {e}"
))
},
)
}
None => Err(None),
};
@ -494,13 +500,43 @@ impl ProjectWorkspace {
/// The return type contains the path and whether or not
/// the root is a member of the current workspace
pub fn to_roots(&self) -> Vec<PackageRoot> {
let mk_sysroot = |sysroot: Result<&Sysroot, _>, project_root: Option<&AbsPath>| {
sysroot.map(|sysroot| PackageRoot {
// mark the sysroot as mutable if it is located inside of the project
is_local: project_root
.map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
include: vec![sysroot.src_root().to_path_buf()],
exclude: Vec::new(),
let mk_sysroot = |sysroot: Result<_, _>, project_root: Option<&AbsPath>| {
let project_root = project_root.map(ToOwned::to_owned);
sysroot.into_iter().flat_map(move |sysroot: &Sysroot| {
let mut r = match sysroot.mode() {
SysrootMode::Workspace(ws) => ws
.packages()
.filter_map(|pkg| {
if ws[pkg].is_local {
// the local ones are included in the main `PackageRoot`` below
return None;
}
let pkg_root = ws[pkg].manifest.parent().to_path_buf();
let include = vec![pkg_root.clone()];
let exclude = vec![
pkg_root.join(".git"),
pkg_root.join("target"),
pkg_root.join("tests"),
pkg_root.join("examples"),
pkg_root.join("benches"),
];
Some(PackageRoot { is_local: false, include, exclude })
})
.collect(),
SysrootMode::Stitched(_) => vec![],
};
r.push(PackageRoot {
// mark the sysroot as mutable if it is located inside of the project
is_local: project_root
.as_ref()
.map_or(false, |project_root| sysroot.src_root().starts_with(project_root)),
include: vec![sysroot.src_root().to_path_buf()],
exclude: Vec::new(),
});
r
})
};
match self {
@ -588,16 +624,16 @@ impl ProjectWorkspace {
pub fn n_packages(&self) -> usize {
match self {
ProjectWorkspace::Json { project, sysroot, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
sysroot_package_len + project.n_crates()
}
ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len());
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
cargo.packages().len() + sysroot_package_len + rustc_package_len
}
ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
sysroot_package_len + files.len()
}
}
@ -638,7 +674,6 @@ impl ProjectWorkspace {
sysroot.as_ref().ok(),
rustc_cfg.clone(),
cfg_overrides,
None,
build_scripts,
match target_layout.as_ref() {
Ok(it) => Ok(Arc::from(it.as_str())),
@ -849,8 +884,6 @@ fn cargo_to_crate_graph(
sysroot: Option<&Sysroot>,
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
// Don't compute cfg and use this if present, only used for the sysroot experiment hack
forced_cfg: Option<CfgOptions>,
build_scripts: &WorkspaceBuildScripts,
target_layout: TargetLayoutLoadResult,
toolchain: Option<&Version>,
@ -883,7 +916,7 @@ fn cargo_to_crate_graph(
for pkg in cargo.packages() {
has_private |= cargo[pkg].metadata.rustc_private;
let cfg_options = forced_cfg.clone().unwrap_or_else(|| {
let cfg_options = {
let mut cfg_options = cfg_options.clone();
// Add test cfg for local crates
@ -908,7 +941,7 @@ fn cargo_to_crate_graph(
cfg_options.apply_diff(diff.clone());
};
cfg_options
});
};
let mut lib_tgt = None;
for &tgt in cargo[pkg].targets.iter() {
@ -1349,124 +1382,126 @@ fn sysroot_to_crate_graph(
toolchain: Option<&Version>,
) -> (SysrootPublicDeps, Option<CrateId>) {
let _p = profile::span("sysroot_to_crate_graph");
let cfg_options = create_cfg_options(rustc_cfg.clone());
let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = match &sysroot.hack_cargo_workspace {
Some(cargo) => handle_hack_cargo_workspace(
load,
cargo,
rustc_cfg,
cfg_options,
target_layout,
toolchain,
crate_graph,
sysroot,
),
None => sysroot
.crates()
.filter_map(|krate| {
let file_id = load(&sysroot[krate].root)?;
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<SysrootCrate, CrateId> = 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::<Vec<_>>(),
};
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::<Vec<_>>(),
};
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<FileId>,
cargo: &CargoWorkspace,
rustc_cfg: Vec<CfgFlag>,
cfg_options: CfgOptions,
target_layout: Result<Arc<str>, Arc<str>>,
toolchain: Option<&Version>,
crate_graph: &mut CrateGraph,
sysroot: &Sysroot,
) -> FxHashMap<SysrootCrate, CrateId> {
let (cg, mut pm) = cargo_to_crate_graph(
load,
None,
cargo,
None,
rustc_cfg,
&CfgOverrides::default(),
Some(cfg_options),
&WorkspaceBuildScripts::default(),
target_layout,
toolchain,
);
crate_graph.extend(cg, &mut pm);
for crate_name in ["std", "alloc", "core"] {
let original = crate_graph
.iter()
.find(|x| {
crate_graph[*x]
.display_name
.as_ref()
.map(|x| x.canonical_name() == crate_name)
.unwrap_or(false)
})
.unwrap();
let fake_crate_name = format!("rustc-std-workspace-{}", crate_name);
let fake = crate_graph
.iter()
.find(|x| {
crate_graph[*x]
.display_name
.as_ref()
.map(|x| x.canonical_name() == fake_crate_name)
.unwrap_or(false)
})
.unwrap();
crate_graph.remove_and_replace(fake, original).unwrap();
}
for (_, c) in crate_graph.iter_mut() {
if c.origin.is_local() {
// LangCrateOrigin::Other is good enough for a hack.
c.origin = CrateOrigin::Lang(LangCrateOrigin::Other);
}
}
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(

View file

@ -182,7 +182,7 @@
},
],
origin: Lang(
Other,
ProcMacro,
),
is_proc_macro: false,
target_layout: Err(

View file

@ -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 {

View file

@ -135,6 +135,13 @@ config_data! {
///
/// This option does not take effect until rust-analyzer is restarted.
cargo_sysroot: Option<String> = "\"discover\"",
/// Whether to run cargo metadata on the sysroot library allowing rust-analyzer to analyze
/// third-party dependencies of the standard libraries.
///
/// This will cause `cargo` to create a lockfile in your sysroot directory. rust-analyzer
/// will attempt to clean up afterwards, but nevertheless requires the location to be
/// writable to.
cargo_sysrootQueryMetadata: bool = "false",
/// Relative path to the sysroot library sources. If left unset, this will default to
/// `{cargo.sysroot}/lib/rustlib/src/rust/library`.
///
@ -1233,6 +1240,7 @@ impl Config {
});
let sysroot_src =
self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
let sysroot_query_metadata = self.data.cargo_sysrootQueryMetadata;
CargoConfig {
features: match &self.data.cargo_features {
@ -1244,6 +1252,7 @@ impl Config {
},
target: self.data.cargo_target.clone(),
sysroot,
sysroot_query_metadata,
sysroot_src,
rustc_source,
cfg_overrides: project_model::CfgOverrides {

View file

@ -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)

View file

@ -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`)::
+
--

View file

@ -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 <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>.
=== Visual Studio 2022
[#visual-studio]
=== [[visual-studio-2022]]Visual Studio 2022
There are multiple rust-analyzer extensions for Visual Studio 2022 on Windows:

View file

@ -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,