Add support for loading rustc private crates

This commit is contained in:
Xavier Denis 2020-11-10 21:50:05 +01:00
parent 5c06e820fa
commit 8716087919
6 changed files with 3606 additions and 77 deletions

View file

@ -64,6 +64,9 @@ pub struct CargoConfig {
/// rustc target
pub target: Option<String>,
/// rustc private crate source
pub rustc_source: Option<AbsPathBuf>,
}
pub type Package = Idx<PackageData>;

View file

@ -9,6 +9,7 @@ use std::{
fmt,
fs::{self, read_dir, ReadDir},
io,
path::Component,
process::Command,
};
@ -31,7 +32,7 @@ pub use proc_macro_api::ProcMacroClient;
#[derive(Clone, Eq, PartialEq)]
pub enum ProjectWorkspace {
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
/// Project workspace was manually specified using a `rust-project.json` file.
Json { project: ProjectJson, sysroot: Option<Sysroot> },
}
@ -39,10 +40,14 @@ pub enum ProjectWorkspace {
impl fmt::Debug for ProjectWorkspace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProjectWorkspace::Cargo { cargo, sysroot } => f
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
.debug_struct("Cargo")
.field("n_packages", &cargo.packages().len())
.field("n_sysroot_crates", &sysroot.crates().len())
.field(
"n_rustc_compiler_crates",
&rustc.as_ref().map(|rc| rc.packages().len()).unwrap_or(0),
)
.finish(),
ProjectWorkspace::Json { project, sysroot } => {
let mut debug_struct = f.debug_struct("Json");
@ -200,7 +205,19 @@ impl ProjectWorkspace {
} else {
Sysroot::default()
};
ProjectWorkspace::Cargo { cargo, sysroot }
let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source {
Some(
CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config)
.with_context(|| {
format!("Failed to read Cargo metadata for Rust sources")
})?,
)
} else {
None
};
ProjectWorkspace::Cargo { cargo, sysroot, rustc }
}
};
@ -238,31 +255,43 @@ impl ProjectWorkspace {
})
}))
.collect::<Vec<_>>(),
ProjectWorkspace::Cargo { cargo, sysroot } => cargo
.packages()
.map(|pkg| {
let is_member = cargo[pkg].is_member;
let pkg_root = cargo[pkg].root().to_path_buf();
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
let roots = cargo
.packages()
.map(|pkg| {
let is_member = cargo[pkg].is_member;
let pkg_root = cargo[pkg].root().to_path_buf();
let mut include = vec![pkg_root.clone()];
include.extend(cargo[pkg].out_dir.clone());
let mut include = vec![pkg_root.clone()];
include.extend(cargo[pkg].out_dir.clone());
let mut exclude = vec![pkg_root.join(".git")];
if is_member {
exclude.push(pkg_root.join("target"));
} else {
exclude.push(pkg_root.join("tests"));
exclude.push(pkg_root.join("examples"));
exclude.push(pkg_root.join("benches"));
}
PackageRoot { is_member, include, exclude }
})
.chain(sysroot.crates().map(|krate| PackageRoot {
is_member: false,
include: vec![sysroot[krate].root_dir().to_path_buf()],
exclude: Vec::new(),
}))
.collect(),
let mut exclude = vec![pkg_root.join(".git")];
if is_member {
exclude.push(pkg_root.join("target"));
} else {
exclude.push(pkg_root.join("tests"));
exclude.push(pkg_root.join("examples"));
exclude.push(pkg_root.join("benches"));
}
PackageRoot { is_member, include, exclude }
})
.chain(sysroot.crates().map(|krate| PackageRoot {
is_member: false,
include: vec![sysroot[krate].root_dir().to_path_buf()],
exclude: Vec::new(),
}));
if let Some(rustc_packages) = rustc {
roots
.chain(rustc_packages.packages().map(|krate| PackageRoot {
is_member: false,
include: vec![rustc_packages[krate].root().to_path_buf()],
exclude: Vec::new(),
}))
.collect()
} else {
roots.collect()
}
}
}
}
@ -273,7 +302,7 @@ impl ProjectWorkspace {
.filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
.cloned()
.collect(),
ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo
.packages()
.filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
.cloned()
@ -284,8 +313,10 @@ impl ProjectWorkspace {
pub fn n_packages(&self) -> usize {
match self {
ProjectWorkspace::Json { project, .. } => project.n_crates(),
ProjectWorkspace::Cargo { cargo, sysroot } => {
cargo.packages().len() + sysroot.crates().len()
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
let rustc_package_len = rustc.as_ref().map(|rc| rc.packages().len()).unwrap_or(0);
dbg!(rustc_package_len);
cargo.packages().len() + sysroot.crates().len() + rustc_package_len
}
}
}
@ -365,7 +396,7 @@ impl ProjectWorkspace {
}
}
}
ProjectWorkspace::Cargo { cargo, sysroot } => {
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
let (public_deps, libproc_macro) =
sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
@ -373,50 +404,88 @@ impl ProjectWorkspace {
cfg_options.extend(get_rustc_cfg_options(target));
let mut pkg_to_lib_crate = FxHashMap::default();
let mut pkg_crates = FxHashMap::default();
// Add test cfg for non-sysroot crates
cfg_options.insert_atom("test".into());
cfg_options.insert_atom("debug_assertions".into());
let mut rustc_pkg_crates = FxHashMap::default();
// Add crate roots for rustc_private libs if a path to source is provided
if let Some(rustc_workspace) = rustc {
for pkg in rustc_workspace.packages() {
for &tgt in rustc_workspace[pkg].targets.iter() {
if rustc_workspace[tgt].kind != TargetKind::Lib {
continue;
}
// Exclude alloc / core / std
if rustc_workspace[tgt]
.root
.components()
.any(|c| c == Component::Normal("library".as_ref()))
{
continue;
}
if let Some(crate_id) = add_target_crate_root(
&mut crate_graph,
&rustc_workspace[pkg],
&rustc_workspace[tgt],
&cfg_options,
proc_macro_client,
load,
) {
pkg_to_lib_crate.insert(pkg, crate_id);
// Add dependencies on the core / std / alloc for rustc
for (name, krate) in public_deps.iter() {
if let Err(_) =
crate_graph.add_dep(crate_id, name.clone(), *krate)
{
log::error!(
"cyclic dependency on {} for {}",
name,
&cargo[pkg].name
)
}
}
rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
}
}
}
// Now add a dep edge from all targets of upstream to the lib
// target of downstream.
for pkg in rustc_workspace.packages() {
for dep in rustc_workspace[pkg].dependencies.iter() {
let name = CrateName::new(&dep.name).unwrap();
if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
log::error!(
"cyclic dependency {} -> {}",
&rustc_workspace[pkg].name,
&rustc_workspace[dep.pkg].name
)
}
}
}
}
}
};
let mut pkg_crates = FxHashMap::default();
// Next, create crates for each package, target pair
for pkg in cargo.packages() {
let mut lib_tgt = None;
for &tgt in cargo[pkg].targets.iter() {
let root = cargo[tgt].root.as_path();
if let Some(file_id) = load(root) {
let edition = cargo[pkg].edition;
let cfg_options = {
let mut opts = cfg_options.clone();
for feature in cargo[pkg].features.iter() {
opts.insert_key_value("feature".into(), feature.into());
}
opts.extend(cargo[pkg].cfgs.iter().cloned());
opts
};
let mut env = Env::default();
if let Some(out_dir) = &cargo[pkg].out_dir {
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
env.set("OUT_DIR", out_dir);
}
}
let proc_macro = cargo[pkg]
.proc_macro_dylib_path
.as_ref()
.map(|it| proc_macro_client.by_dylib_path(&it))
.unwrap_or_default();
let display_name =
CrateDisplayName::from_canonical_name(cargo[pkg].name.clone());
let crate_id = crate_graph.add_crate_root(
file_id,
edition,
Some(display_name),
cfg_options,
env,
proc_macro.clone(),
);
if let Some(crate_id) = add_target_crate_root(
&mut crate_graph,
&cargo[pkg],
&cargo[tgt],
&cfg_options,
proc_macro_client,
load,
) {
if cargo[tgt].kind == TargetKind::Lib {
lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
pkg_to_lib_crate.insert(pkg, crate_id);
@ -466,6 +535,30 @@ impl ProjectWorkspace {
}
}
// If we have access to the rust sources, create dependencies onto rustc_private libraries from all targets
// that are members of the current workspace
if let Some(rustc_workspace) = rustc {
for dep in rustc_workspace.packages() {
let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
if let Some(&from) = pkg_to_lib_crate.get(&dep) {
for pkg in cargo.packages() {
if !cargo[pkg].is_member {
continue;
}
for &to in pkg_crates.get(&pkg).into_iter().flatten() {
if let Err(_) = crate_graph.add_dep(to, name.clone(), from) {
log::error!(
"cyclic dependency22 {} -> {}",
&cargo[pkg].name,
&rustc_workspace[dep].name
)
}
}
}
}
}
}
// Now add a dep edge from all targets of upstream to the lib
// target of downstream.
for pkg in cargo.packages() {
@ -537,6 +630,52 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
Ok(stdout.trim().to_string())
}
fn add_target_crate_root(
crate_graph: &mut CrateGraph,
pkg: &cargo_workspace::PackageData,
tgt: &cargo_workspace::TargetData,
cfg_options: &CfgOptions,
proc_macro_client: &ProcMacroClient,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
) -> Option<CrateId> {
let root = tgt.root.as_path();
if let Some(file_id) = load(root) {
let edition = pkg.edition;
let cfg_options = {
let mut opts = cfg_options.clone();
for feature in pkg.features.iter() {
opts.insert_key_value("feature".into(), feature.into());
}
opts.extend(pkg.cfgs.iter().cloned());
opts
};
let mut env = Env::default();
if let Some(out_dir) = &pkg.out_dir {
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
env.set("OUT_DIR", out_dir);
}
}
let proc_macro = pkg
.proc_macro_dylib_path
.as_ref()
.map(|it| proc_macro_client.by_dylib_path(&it))
.unwrap_or_default();
let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
let crate_id = crate_graph.add_crate_root(
file_id,
edition,
Some(display_name),
cfg_options,
env,
proc_macro.clone(),
);
return Some(crate_id);
}
None
}
fn sysroot_to_crate_graph(
crate_graph: &mut CrateGraph,
sysroot: &Sysroot,

View file

@ -7,7 +7,7 @@
//! configure the server itself, feature flags are passed into analysis, and
//! tweak things like automatic insertion of `()` in completions.
use std::{ffi::OsString, path::PathBuf};
use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
use flycheck::FlycheckConfig;
use hir::PrefixKind;
@ -227,12 +227,25 @@ impl Config {
self.notifications =
NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound };
self.cargo_autoreload = data.cargo_autoreload;
let rustc_source = if let Some(rustc_source) = data.rustcSource {
let rustpath: PathBuf = rustc_source.into();
AbsPathBuf::try_from(rustpath)
.map_err(|_| {
log::error!("rustc source directory must be an absolute path");
})
.ok()
} else {
None
};
self.cargo = CargoConfig {
no_default_features: data.cargo_noDefaultFeatures,
all_features: data.cargo_allFeatures,
features: data.cargo_features.clone(),
load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
target: data.cargo_target.clone(),
rustc_source: rustc_source,
};
self.runnables = RunnablesConfig {
override_cargo: data.runnables_overrideCargo,
@ -532,5 +545,6 @@ config_data! {
rustfmt_overrideCommand: Option<Vec<String>> = None,
withSysroot: bool = true,
rustcSource : Option<String> = None,
}
}

View file

@ -246,7 +246,9 @@ impl GlobalState {
.iter()
.enumerate()
.filter_map(|(id, w)| match w {
ProjectWorkspace::Cargo { cargo, sysroot: _ } => Some((id, cargo.workspace_root())),
ProjectWorkspace::Cargo { cargo, sysroot: _, rustc: _ } => {
Some((id, cargo.workspace_root()))
}
ProjectWorkspace::Json { project, .. } => {
// Enable flychecks for json projects if a custom flycheck command was supplied
// in the workspace configuration.

File diff suppressed because it is too large Load diff

View file

@ -687,6 +687,12 @@
},
"default": [],
"description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'"
},
"rust-analyzer.rustcSource" : {
"type": [ "null", "string" ],
"default": null,
"description": "Path to the rust compiler sources, for usage in rustc_private projects."
}
}
},