aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-11-12 17:55:32 +0000
committerGitHub <[email protected]>2020-11-12 17:55:32 +0000
commitddccaecb79246f0c2bffb0ef9614939ee9a25835 (patch)
tree6dd43dd27d4ba2ae1ea7f8c9c4ca81c160d31848
parentcf73b6851b4e8f80c170d1ba8912f3c27b816c34 (diff)
parent89ce6b6664c9451c3c6ab9446fcd40697c5b0267 (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]>
-rw-r--r--crates/project_model/src/cargo_workspace.rs3
-rw-r--r--crates/project_model/src/lib.rs271
-rw-r--r--crates/rust-analyzer/src/config.rs16
-rw-r--r--crates/rust-analyzer/src/reload.rs4
-rw-r--r--editors/code/package.json8
5 files changed, 233 insertions, 69 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
69pub type Package = Idx<PackageData>; 72pub 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)]
32pub enum ProjectWorkspace { 33pub 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 {
39impl fmt::Debug for ProjectWorkspace { 40impl 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
631fn 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}
540fn sysroot_to_crate_graph( 677fn sysroot_to_crate_graph(
541 crate_graph: &mut CrateGraph, 678 crate_graph: &mut CrateGraph,
542 sysroot: &Sysroot, 679 sysroot: &Sysroot,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b4c738272..74a021dbf 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,7 +7,7 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use hir::PrefixKind; 13use hir::PrefixKind;
@@ -227,12 +227,25 @@ impl Config {
227 self.notifications = 227 self.notifications =
228 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound }; 228 NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound };
229 self.cargo_autoreload = data.cargo_autoreload; 229 self.cargo_autoreload = data.cargo_autoreload;
230
231 let rustc_source = if let Some(rustc_source) = data.rustcSource {
232 let rustpath: PathBuf = rustc_source.into();
233 AbsPathBuf::try_from(rustpath)
234 .map_err(|_| {
235 log::error!("rustc source directory must be an absolute path");
236 })
237 .ok()
238 } else {
239 None
240 };
241
230 self.cargo = CargoConfig { 242 self.cargo = CargoConfig {
231 no_default_features: data.cargo_noDefaultFeatures, 243 no_default_features: data.cargo_noDefaultFeatures,
232 all_features: data.cargo_allFeatures, 244 all_features: data.cargo_allFeatures,
233 features: data.cargo_features.clone(), 245 features: data.cargo_features.clone(),
234 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, 246 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
235 target: data.cargo_target.clone(), 247 target: data.cargo_target.clone(),
248 rustc_source: rustc_source,
236 }; 249 };
237 self.runnables = RunnablesConfig { 250 self.runnables = RunnablesConfig {
238 override_cargo: data.runnables_overrideCargo, 251 override_cargo: data.runnables_overrideCargo,
@@ -535,5 +548,6 @@ config_data! {
535 rustfmt_overrideCommand: Option<Vec<String>> = None, 548 rustfmt_overrideCommand: Option<Vec<String>> = None,
536 549
537 withSysroot: bool = true, 550 withSysroot: bool = true,
551 rustcSource : Option<String> = None,
538 } 552 }
539} 553}
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 0eabd51bd..11c8d0e5f 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -246,7 +246,9 @@ impl GlobalState {
246 .iter() 246 .iter()
247 .enumerate() 247 .enumerate()
248 .filter_map(|(id, w)| match w { 248 .filter_map(|(id, w)| match w {
249 ProjectWorkspace::Cargo { cargo, sysroot: _ } => Some((id, cargo.workspace_root())), 249 ProjectWorkspace::Cargo { cargo, sysroot: _, rustc: _ } => {
250 Some((id, cargo.workspace_root()))
251 }
250 ProjectWorkspace::Json { project, .. } => { 252 ProjectWorkspace::Json { project, .. } => {
251 // Enable flychecks for json projects if a custom flycheck command was supplied 253 // Enable flychecks for json projects if a custom flycheck command was supplied
252 // in the workspace configuration. 254 // in the workspace configuration.
diff --git a/editors/code/package.json b/editors/code/package.json
index eccafccdd..6db78a99a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -687,6 +687,14 @@
687 }, 687 },
688 "default": [], 688 "default": [],
689 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'" 689 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'"
690 },
691 "rust-analyzer.rustcSource": {
692 "type": [
693 "null",
694 "string"
695 ],
696 "default": null,
697 "description": "Path to the rust compiler sources, for usage in rustc_private projects."
690 } 698 }
691 } 699 }
692 }, 700 },