internal: Add unstable config for loading the sysroot sources via cargo metadata

This commit is contained in:
Lukas Wirth 2024-01-13 17:22:39 +01:00
parent 9d8889cdfc
commit c7eb52dd7b
11 changed files with 507 additions and 249 deletions

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(