Skip to content

Commit ddccaec

Browse files
bors[bot]xldenis
andauthored
Merge #6524
6524: Add support for loading rustc private crates r=matklad a=xldenis This PR presents a solution to the problem of making`rustc_private` crates visible to `rust-analyzer`. Currently developers add dependencies to those crates behind a `cfg(NOT_A_TARGET)` target to prevent `cargo` from building them. This solution is unsatisfactory since it requires modifying `Cargo.toml` and causes problems for collaboration or CI. The proposed solution suggested by @matklad is to allow users to give RA a path where the `rustc` sources could be found and then load that like a normal workspace. This PR implements this solution by adding a `rustcSource` configuration item and adding the cargo metadata to the crate graph if it is provided. ------ I have no idea how this should be tested, or if this code is generally tested at all. I've locally run the extension with these changes and it correctly loads the relevant crates on a `rustc_private` project of mine. Co-authored-by: Xavier Denis <[email protected]>
2 parents cf73b68 + 89ce6b6 commit ddccaec

File tree

5 files changed

+233
-69
lines changed

5 files changed

+233
-69
lines changed

crates/project_model/src/cargo_workspace.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ pub struct CargoConfig {
6464

6565
/// rustc target
6666
pub target: Option<String>,
67+
68+
/// rustc private crate source
69+
pub rustc_source: Option<AbsPathBuf>,
6770
}
6871

6972
pub type Package = Idx<PackageData>;

crates/project_model/src/lib.rs

Lines changed: 204 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
fmt,
1010
fs::{self, read_dir, ReadDir},
1111
io,
12+
path::Component,
1213
process::Command,
1314
};
1415

@@ -31,18 +32,22 @@ pub use proc_macro_api::ProcMacroClient;
3132
#[derive(Clone, Eq, PartialEq)]
3233
pub enum ProjectWorkspace {
3334
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
34-
Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
35+
Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
3536
/// Project workspace was manually specified using a `rust-project.json` file.
3637
Json { project: ProjectJson, sysroot: Option<Sysroot> },
3738
}
3839

3940
impl fmt::Debug for ProjectWorkspace {
4041
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4142
match self {
42-
ProjectWorkspace::Cargo { cargo, sysroot } => f
43+
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
4344
.debug_struct("Cargo")
4445
.field("n_packages", &cargo.packages().len())
4546
.field("n_sysroot_crates", &sysroot.crates().len())
47+
.field(
48+
"n_rustc_compiler_crates",
49+
&rustc.as_ref().map_or(0, |rc| rc.packages().len()),
50+
)
4651
.finish(),
4752
ProjectWorkspace::Json { project, sysroot } => {
4853
let mut debug_struct = f.debug_struct("Json");
@@ -200,7 +205,19 @@ impl ProjectWorkspace {
200205
} else {
201206
Sysroot::default()
202207
};
203-
ProjectWorkspace::Cargo { cargo, sysroot }
208+
209+
let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source {
210+
Some(
211+
CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config)
212+
.with_context(|| {
213+
format!("Failed to read Cargo metadata for Rust sources")
214+
})?,
215+
)
216+
} else {
217+
None
218+
};
219+
220+
ProjectWorkspace::Cargo { cargo, sysroot, rustc }
204221
}
205222
};
206223

@@ -238,31 +255,43 @@ impl ProjectWorkspace {
238255
})
239256
}))
240257
.collect::<Vec<_>>(),
241-
ProjectWorkspace::Cargo { cargo, sysroot } => cargo
242-
.packages()
243-
.map(|pkg| {
244-
let is_member = cargo[pkg].is_member;
245-
let pkg_root = cargo[pkg].root().to_path_buf();
246-
247-
let mut include = vec![pkg_root.clone()];
248-
include.extend(cargo[pkg].out_dir.clone());
249-
250-
let mut exclude = vec![pkg_root.join(".git")];
251-
if is_member {
252-
exclude.push(pkg_root.join("target"));
253-
} else {
254-
exclude.push(pkg_root.join("tests"));
255-
exclude.push(pkg_root.join("examples"));
256-
exclude.push(pkg_root.join("benches"));
257-
}
258-
PackageRoot { is_member, include, exclude }
259-
})
260-
.chain(sysroot.crates().map(|krate| PackageRoot {
261-
is_member: false,
262-
include: vec![sysroot[krate].root_dir().to_path_buf()],
263-
exclude: Vec::new(),
264-
}))
265-
.collect(),
258+
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
259+
let roots = cargo
260+
.packages()
261+
.map(|pkg| {
262+
let is_member = cargo[pkg].is_member;
263+
let pkg_root = cargo[pkg].root().to_path_buf();
264+
265+
let mut include = vec![pkg_root.clone()];
266+
include.extend(cargo[pkg].out_dir.clone());
267+
268+
let mut exclude = vec![pkg_root.join(".git")];
269+
if is_member {
270+
exclude.push(pkg_root.join("target"));
271+
} else {
272+
exclude.push(pkg_root.join("tests"));
273+
exclude.push(pkg_root.join("examples"));
274+
exclude.push(pkg_root.join("benches"));
275+
}
276+
PackageRoot { is_member, include, exclude }
277+
})
278+
.chain(sysroot.crates().map(|krate| PackageRoot {
279+
is_member: false,
280+
include: vec![sysroot[krate].root_dir().to_path_buf()],
281+
exclude: Vec::new(),
282+
}));
283+
if let Some(rustc_packages) = rustc {
284+
roots
285+
.chain(rustc_packages.packages().map(|krate| PackageRoot {
286+
is_member: false,
287+
include: vec![rustc_packages[krate].root().to_path_buf()],
288+
exclude: Vec::new(),
289+
}))
290+
.collect()
291+
} else {
292+
roots.collect()
293+
}
294+
}
266295
}
267296
}
268297

@@ -273,7 +302,7 @@ impl ProjectWorkspace {
273302
.filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
274303
.cloned()
275304
.collect(),
276-
ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
305+
ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo
277306
.packages()
278307
.filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
279308
.cloned()
@@ -284,8 +313,9 @@ impl ProjectWorkspace {
284313
pub fn n_packages(&self) -> usize {
285314
match self {
286315
ProjectWorkspace::Json { project, .. } => project.n_crates(),
287-
ProjectWorkspace::Cargo { cargo, sysroot } => {
288-
cargo.packages().len() + sysroot.crates().len()
316+
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
317+
let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
318+
cargo.packages().len() + sysroot.crates().len() + rustc_package_len
289319
}
290320
}
291321
}
@@ -365,58 +395,33 @@ impl ProjectWorkspace {
365395
}
366396
}
367397
}
368-
ProjectWorkspace::Cargo { cargo, sysroot } => {
398+
ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
369399
let (public_deps, libproc_macro) =
370400
sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
371401

372402
let mut cfg_options = CfgOptions::default();
373403
cfg_options.extend(get_rustc_cfg_options(target));
374404

375405
let mut pkg_to_lib_crate = FxHashMap::default();
376-
let mut pkg_crates = FxHashMap::default();
377406

378407
// Add test cfg for non-sysroot crates
379408
cfg_options.insert_atom("test".into());
380409
cfg_options.insert_atom("debug_assertions".into());
381410

411+
let mut pkg_crates = FxHashMap::default();
412+
382413
// Next, create crates for each package, target pair
383414
for pkg in cargo.packages() {
384415
let mut lib_tgt = None;
385416
for &tgt in cargo[pkg].targets.iter() {
386-
let root = cargo[tgt].root.as_path();
387-
if let Some(file_id) = load(root) {
388-
let edition = cargo[pkg].edition;
389-
let cfg_options = {
390-
let mut opts = cfg_options.clone();
391-
for feature in cargo[pkg].features.iter() {
392-
opts.insert_key_value("feature".into(), feature.into());
393-
}
394-
opts.extend(cargo[pkg].cfgs.iter().cloned());
395-
opts
396-
};
397-
let mut env = Env::default();
398-
if let Some(out_dir) = &cargo[pkg].out_dir {
399-
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
400-
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
401-
env.set("OUT_DIR", out_dir);
402-
}
403-
}
404-
let proc_macro = cargo[pkg]
405-
.proc_macro_dylib_path
406-
.as_ref()
407-
.map(|it| proc_macro_client.by_dylib_path(&it))
408-
.unwrap_or_default();
409-
410-
let display_name =
411-
CrateDisplayName::from_canonical_name(cargo[pkg].name.clone());
412-
let crate_id = crate_graph.add_crate_root(
413-
file_id,
414-
edition,
415-
Some(display_name),
416-
cfg_options,
417-
env,
418-
proc_macro.clone(),
419-
);
417+
if let Some(crate_id) = add_target_crate_root(
418+
&mut crate_graph,
419+
&cargo[pkg],
420+
&cargo[tgt],
421+
&cfg_options,
422+
proc_macro_client,
423+
load,
424+
) {
420425
if cargo[tgt].kind == TargetKind::Lib {
421426
lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
422427
pkg_to_lib_crate.insert(pkg, crate_id);
@@ -484,6 +489,92 @@ impl ProjectWorkspace {
484489
}
485490
}
486491
}
492+
493+
let mut rustc_pkg_crates = FxHashMap::default();
494+
495+
// If the user provided a path to rustc sources, we add all the rustc_private crates
496+
// and create dependencies on them for the crates in the current workspace
497+
if let Some(rustc_workspace) = rustc {
498+
for pkg in rustc_workspace.packages() {
499+
for &tgt in rustc_workspace[pkg].targets.iter() {
500+
if rustc_workspace[tgt].kind != TargetKind::Lib {
501+
continue;
502+
}
503+
// Exclude alloc / core / std
504+
if rustc_workspace[tgt]
505+
.root
506+
.components()
507+
.any(|c| c == Component::Normal("library".as_ref()))
508+
{
509+
continue;
510+
}
511+
512+
if let Some(crate_id) = add_target_crate_root(
513+
&mut crate_graph,
514+
&rustc_workspace[pkg],
515+
&rustc_workspace[tgt],
516+
&cfg_options,
517+
proc_macro_client,
518+
load,
519+
) {
520+
pkg_to_lib_crate.insert(pkg, crate_id);
521+
// Add dependencies on the core / std / alloc for rustc
522+
for (name, krate) in public_deps.iter() {
523+
if let Err(_) =
524+
crate_graph.add_dep(crate_id, name.clone(), *krate)
525+
{
526+
log::error!(
527+
"cyclic dependency on {} for {}",
528+
name,
529+
&cargo[pkg].name
530+
)
531+
}
532+
}
533+
rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
534+
}
535+
}
536+
}
537+
// Now add a dep edge from all targets of upstream to the lib
538+
// target of downstream.
539+
for pkg in rustc_workspace.packages() {
540+
for dep in rustc_workspace[pkg].dependencies.iter() {
541+
let name = CrateName::new(&dep.name).unwrap();
542+
if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
543+
for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
544+
if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
545+
log::error!(
546+
"cyclic dependency {} -> {}",
547+
&rustc_workspace[pkg].name,
548+
&rustc_workspace[dep.pkg].name
549+
)
550+
}
551+
}
552+
}
553+
}
554+
}
555+
556+
// Add dependencies for all the crates of the current workspace to rustc_private libraries
557+
for dep in rustc_workspace.packages() {
558+
let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
559+
560+
if let Some(&to) = pkg_to_lib_crate.get(&dep) {
561+
for pkg in cargo.packages() {
562+
if !cargo[pkg].is_member {
563+
continue;
564+
}
565+
for &from in pkg_crates.get(&pkg).into_iter().flatten() {
566+
if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
567+
log::error!(
568+
"cyclic dependency {} -> {}",
569+
&cargo[pkg].name,
570+
&rustc_workspace[dep].name
571+
)
572+
}
573+
}
574+
}
575+
}
576+
}
577+
}
487578
}
488579
}
489580
if crate_graph.patch_cfg_if() {
@@ -537,6 +628,52 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
537628
Ok(stdout.trim().to_string())
538629
}
539630

631+
fn add_target_crate_root(
632+
crate_graph: &mut CrateGraph,
633+
pkg: &cargo_workspace::PackageData,
634+
tgt: &cargo_workspace::TargetData,
635+
cfg_options: &CfgOptions,
636+
proc_macro_client: &ProcMacroClient,
637+
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
638+
) -> Option<CrateId> {
639+
let root = tgt.root.as_path();
640+
if let Some(file_id) = load(root) {
641+
let edition = pkg.edition;
642+
let cfg_options = {
643+
let mut opts = cfg_options.clone();
644+
for feature in pkg.features.iter() {
645+
opts.insert_key_value("feature".into(), feature.into());
646+
}
647+
opts.extend(pkg.cfgs.iter().cloned());
648+
opts
649+
};
650+
let mut env = Env::default();
651+
if let Some(out_dir) = &pkg.out_dir {
652+
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
653+
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
654+
env.set("OUT_DIR", out_dir);
655+
}
656+
}
657+
let proc_macro = pkg
658+
.proc_macro_dylib_path
659+
.as_ref()
660+
.map(|it| proc_macro_client.by_dylib_path(&it))
661+
.unwrap_or_default();
662+
663+
let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
664+
let crate_id = crate_graph.add_crate_root(
665+
file_id,
666+
edition,
667+
Some(display_name),
668+
cfg_options,
669+
env,
670+
proc_macro.clone(),
671+
);
672+
673+
return Some(crate_id);
674+
}
675+
None
676+
}
540677
fn sysroot_to_crate_graph(
541678
crate_graph: &mut CrateGraph,
542679
sysroot: &Sysroot,

0 commit comments

Comments
 (0)