diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-11-12 17:55:32 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-11-12 17:55:32 +0000 |
commit | ddccaecb79246f0c2bffb0ef9614939ee9a25835 (patch) | |
tree | 6dd43dd27d4ba2ae1ea7f8c9c4ca81c160d31848 /crates/project_model/src | |
parent | cf73b6851b4e8f80c170d1ba8912f3c27b816c34 (diff) | |
parent | 89ce6b6664c9451c3c6ab9446fcd40697c5b0267 (diff) |
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]>
Diffstat (limited to 'crates/project_model/src')
-rw-r--r-- | crates/project_model/src/cargo_workspace.rs | 3 | ||||
-rw-r--r-- | crates/project_model/src/lib.rs | 271 |
2 files changed, 207 insertions, 67 deletions
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index d5f6a4025..608a031d4 100644 --- a/crates/project_model/src/cargo_workspace.rs +++ b/crates/project_model/src/cargo_workspace.rs | |||
@@ -64,6 +64,9 @@ pub struct CargoConfig { | |||
64 | 64 | ||
65 | /// rustc target | 65 | /// rustc target |
66 | pub target: Option<String>, | 66 | pub target: Option<String>, |
67 | |||
68 | /// rustc private crate source | ||
69 | pub rustc_source: Option<AbsPathBuf>, | ||
67 | } | 70 | } |
68 | 71 | ||
69 | pub type Package = Idx<PackageData>; | 72 | pub type Package = Idx<PackageData>; |
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index e92cfea59..4531b1928 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs | |||
@@ -9,6 +9,7 @@ use std::{ | |||
9 | fmt, | 9 | fmt, |
10 | fs::{self, read_dir, ReadDir}, | 10 | fs::{self, read_dir, ReadDir}, |
11 | io, | 11 | io, |
12 | path::Component, | ||
12 | process::Command, | 13 | process::Command, |
13 | }; | 14 | }; |
14 | 15 | ||
@@ -31,7 +32,7 @@ pub use proc_macro_api::ProcMacroClient; | |||
31 | #[derive(Clone, Eq, PartialEq)] | 32 | #[derive(Clone, Eq, PartialEq)] |
32 | pub enum ProjectWorkspace { | 33 | pub enum ProjectWorkspace { |
33 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. | 34 | /// 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> }, |
35 | /// Project workspace was manually specified using a `rust-project.json` file. | 36 | /// Project workspace was manually specified using a `rust-project.json` file. |
36 | Json { project: ProjectJson, sysroot: Option<Sysroot> }, | 37 | Json { project: ProjectJson, sysroot: Option<Sysroot> }, |
37 | } | 38 | } |
@@ -39,10 +40,14 @@ pub enum ProjectWorkspace { | |||
39 | impl fmt::Debug for ProjectWorkspace { | 40 | impl fmt::Debug for ProjectWorkspace { |
40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
41 | match self { | 42 | match self { |
42 | ProjectWorkspace::Cargo { cargo, sysroot } => f | 43 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f |
43 | .debug_struct("Cargo") | 44 | .debug_struct("Cargo") |
44 | .field("n_packages", &cargo.packages().len()) | 45 | .field("n_packages", &cargo.packages().len()) |
45 | .field("n_sysroot_crates", &sysroot.crates().len()) | 46 | .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 | ) | ||
46 | .finish(), | 51 | .finish(), |
47 | ProjectWorkspace::Json { project, sysroot } => { | 52 | ProjectWorkspace::Json { project, sysroot } => { |
48 | let mut debug_struct = f.debug_struct("Json"); | 53 | let mut debug_struct = f.debug_struct("Json"); |
@@ -200,7 +205,19 @@ impl ProjectWorkspace { | |||
200 | } else { | 205 | } else { |
201 | Sysroot::default() | 206 | Sysroot::default() |
202 | }; | 207 | }; |
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 } | ||
204 | } | 221 | } |
205 | }; | 222 | }; |
206 | 223 | ||
@@ -238,31 +255,43 @@ impl ProjectWorkspace { | |||
238 | }) | 255 | }) |
239 | })) | 256 | })) |
240 | .collect::<Vec<_>>(), | 257 | .collect::<Vec<_>>(), |
241 | ProjectWorkspace::Cargo { cargo, sysroot } => cargo | 258 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { |
242 | .packages() | 259 | let roots = cargo |
243 | .map(|pkg| { | 260 | .packages() |
244 | let is_member = cargo[pkg].is_member; | 261 | .map(|pkg| { |
245 | let pkg_root = cargo[pkg].root().to_path_buf(); | 262 | let is_member = cargo[pkg].is_member; |
246 | 263 | let pkg_root = cargo[pkg].root().to_path_buf(); | |
247 | let mut include = vec![pkg_root.clone()]; | 264 | |
248 | include.extend(cargo[pkg].out_dir.clone()); | 265 | let mut include = vec![pkg_root.clone()]; |
249 | 266 | include.extend(cargo[pkg].out_dir.clone()); | |
250 | let mut exclude = vec![pkg_root.join(".git")]; | 267 | |
251 | if is_member { | 268 | let mut exclude = vec![pkg_root.join(".git")]; |
252 | exclude.push(pkg_root.join("target")); | 269 | if is_member { |
253 | } else { | 270 | exclude.push(pkg_root.join("target")); |
254 | exclude.push(pkg_root.join("tests")); | 271 | } else { |
255 | exclude.push(pkg_root.join("examples")); | 272 | exclude.push(pkg_root.join("tests")); |
256 | exclude.push(pkg_root.join("benches")); | 273 | exclude.push(pkg_root.join("examples")); |
257 | } | 274 | exclude.push(pkg_root.join("benches")); |
258 | PackageRoot { is_member, include, exclude } | 275 | } |
259 | }) | 276 | PackageRoot { is_member, include, exclude } |
260 | .chain(sysroot.crates().map(|krate| PackageRoot { | 277 | }) |
261 | is_member: false, | 278 | .chain(sysroot.crates().map(|krate| PackageRoot { |
262 | include: vec![sysroot[krate].root_dir().to_path_buf()], | 279 | is_member: false, |
263 | exclude: Vec::new(), | 280 | include: vec![sysroot[krate].root_dir().to_path_buf()], |
264 | })) | 281 | exclude: Vec::new(), |
265 | .collect(), | 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 | } | ||
266 | } | 295 | } |
267 | } | 296 | } |
268 | 297 | ||
@@ -273,7 +302,7 @@ impl ProjectWorkspace { | |||
273 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) | 302 | .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref()) |
274 | .cloned() | 303 | .cloned() |
275 | .collect(), | 304 | .collect(), |
276 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo | 305 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo |
277 | .packages() | 306 | .packages() |
278 | .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) | 307 | .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) |
279 | .cloned() | 308 | .cloned() |
@@ -284,8 +313,9 @@ impl ProjectWorkspace { | |||
284 | pub fn n_packages(&self) -> usize { | 313 | pub fn n_packages(&self) -> usize { |
285 | match self { | 314 | match self { |
286 | ProjectWorkspace::Json { project, .. } => project.n_crates(), | 315 | ProjectWorkspace::Json { project, .. } => project.n_crates(), |
287 | ProjectWorkspace::Cargo { cargo, sysroot } => { | 316 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { |
288 | cargo.packages().len() + sysroot.crates().len() | 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 | ||
289 | } | 319 | } |
290 | } | 320 | } |
291 | } | 321 | } |
@@ -365,7 +395,7 @@ impl ProjectWorkspace { | |||
365 | } | 395 | } |
366 | } | 396 | } |
367 | } | 397 | } |
368 | ProjectWorkspace::Cargo { cargo, sysroot } => { | 398 | ProjectWorkspace::Cargo { cargo, sysroot, rustc } => { |
369 | let (public_deps, libproc_macro) = | 399 | let (public_deps, libproc_macro) = |
370 | sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); | 400 | sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load); |
371 | 401 | ||
@@ -373,50 +403,25 @@ impl ProjectWorkspace { | |||
373 | cfg_options.extend(get_rustc_cfg_options(target)); | 403 | cfg_options.extend(get_rustc_cfg_options(target)); |
374 | 404 | ||
375 | let mut pkg_to_lib_crate = FxHashMap::default(); | 405 | let mut pkg_to_lib_crate = FxHashMap::default(); |
376 | let mut pkg_crates = FxHashMap::default(); | ||
377 | 406 | ||
378 | // Add test cfg for non-sysroot crates | 407 | // Add test cfg for non-sysroot crates |
379 | cfg_options.insert_atom("test".into()); | 408 | cfg_options.insert_atom("test".into()); |
380 | cfg_options.insert_atom("debug_assertions".into()); | 409 | cfg_options.insert_atom("debug_assertions".into()); |
381 | 410 | ||
411 | let mut pkg_crates = FxHashMap::default(); | ||
412 | |||
382 | // Next, create crates for each package, target pair | 413 | // Next, create crates for each package, target pair |
383 | for pkg in cargo.packages() { | 414 | for pkg in cargo.packages() { |
384 | let mut lib_tgt = None; | 415 | let mut lib_tgt = None; |
385 | for &tgt in cargo[pkg].targets.iter() { | 416 | for &tgt in cargo[pkg].targets.iter() { |
386 | let root = cargo[tgt].root.as_path(); | 417 | if let Some(crate_id) = add_target_crate_root( |
387 | if let Some(file_id) = load(root) { | 418 | &mut crate_graph, |
388 | let edition = cargo[pkg].edition; | 419 | &cargo[pkg], |
389 | let cfg_options = { | 420 | &cargo[tgt], |
390 | let mut opts = cfg_options.clone(); | 421 | &cfg_options, |
391 | for feature in cargo[pkg].features.iter() { | 422 | proc_macro_client, |
392 | opts.insert_key_value("feature".into(), feature.into()); | 423 | load, |
393 | } | 424 | ) { |
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 | ); | ||
420 | if cargo[tgt].kind == TargetKind::Lib { | 425 | if cargo[tgt].kind == TargetKind::Lib { |
421 | lib_tgt = Some((crate_id, cargo[tgt].name.clone())); | 426 | lib_tgt = Some((crate_id, cargo[tgt].name.clone())); |
422 | pkg_to_lib_crate.insert(pkg, crate_id); | 427 | pkg_to_lib_crate.insert(pkg, crate_id); |
@@ -484,6 +489,92 @@ impl ProjectWorkspace { | |||
484 | } | 489 | } |
485 | } | 490 | } |
486 | } | 491 | } |
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 | } | ||
487 | } | 578 | } |
488 | } | 579 | } |
489 | if crate_graph.patch_cfg_if() { | 580 | if crate_graph.patch_cfg_if() { |
@@ -537,6 +628,52 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> { | |||
537 | Ok(stdout.trim().to_string()) | 628 | Ok(stdout.trim().to_string()) |
538 | } | 629 | } |
539 | 630 | ||
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 | } | ||
540 | fn sysroot_to_crate_graph( | 677 | fn sysroot_to_crate_graph( |
541 | crate_graph: &mut CrateGraph, | 678 | crate_graph: &mut CrateGraph, |
542 | sysroot: &Sysroot, | 679 | sysroot: &Sysroot, |