diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index f7241b71136..bc6e203414e 100644 --- a/crates/project_model/src/cargo_workspace.rs +++ b/crates/project_model/src/cargo_workspace.rs @@ -9,6 +9,8 @@ use la_arena::{Arena, Idx}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; +use serde::Deserialize; +use serde_json::from_value; use crate::build_data::BuildDataConfig; use crate::utf8_stdout; @@ -104,6 +106,13 @@ pub struct PackageData { pub active_features: Vec, // String representation of package id pub id: String, + // The contents of [package.metadata.rust-analyzer] + pub metadata: RustAnalyzerPackageMetaData, +} + +#[derive(Deserialize, Default, Debug, Clone, Eq, PartialEq)] +pub struct RustAnalyzerPackageMetaData { + pub rustc_private: bool, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -161,6 +170,13 @@ pub fn root(&self) -> &AbsPath { } } +#[derive(Deserialize, Default)] +// Deserialise helper for the cargo metadata +struct PackageMetadata { + #[serde(rename = "rust-analyzer")] + rust_analyzer: Option, +} + impl CargoWorkspace { pub fn from_cargo_metadata( cargo_toml: &AbsPath, @@ -244,8 +260,10 @@ pub fn from_cargo_metadata( meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); for meta_pkg in &meta.packages { - let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = - meta_pkg; + let cargo_metadata::Package { + id, edition, name, manifest_path, version, metadata, .. + } = meta_pkg; + let meta = from_value::(metadata.clone()).unwrap_or_default(); let is_member = ws_members.contains(&id); let edition = edition .parse::() @@ -262,6 +280,7 @@ pub fn from_cargo_metadata( dependencies: Vec::new(), features: meta_pkg.features.clone().into_iter().collect(), active_features: Vec::new(), + metadata: meta.rust_analyzer.unwrap_or_default(), }); let pkg_data = &mut packages[pkg]; pkg_by_id.insert(id, pkg); diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index 0220efdb4e5..1b53fcc30a6 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -2,11 +2,7 @@ //! metadata` or `rust-project.json`) into representation stored in the salsa //! database -- `CrateGraph`. -use std::{ - fmt, fs, - path::{Component, Path}, - process::Command, -}; +use std::{collections::VecDeque, fmt, fs, path::Path, process::Command}; use anyhow::{Context, Result}; use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; @@ -60,6 +56,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f .debug_struct("Cargo") + .field("root", &cargo.workspace_root().file_name()) .field("n_packages", &cargo.packages().len()) .field("n_sysroot_crates", &sysroot.crates().len()) .field( @@ -279,11 +276,8 @@ pub fn to_crate_graph( pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) { match self { - ProjectWorkspace::Cargo { cargo, rustc, .. } => { + ProjectWorkspace::Cargo { cargo, .. } => { collector.add_config(&cargo.workspace_root(), cargo.build_data_config().clone()); - if let Some(rustc) = rustc { - collector.add_config(rustc.workspace_root(), rustc.build_data_config().clone()); - } } _ => {} } @@ -380,9 +374,11 @@ fn cargo_to_crate_graph( cfg_options.insert_atom("debug_assertions".into()); let mut pkg_crates = FxHashMap::default(); - + // Does any crate signal to rust-analyzer that they need the rustc_private crates? + let mut has_private = false; // Next, create crates for each package, target pair for pkg in cargo.packages() { + has_private |= cargo[pkg].metadata.rustc_private; let mut lib_tgt = None; for &tgt in cargo[pkg].targets.iter() { if let Some(file_id) = load(&cargo[tgt].root) { @@ -443,28 +439,66 @@ fn cargo_to_crate_graph( } } - let mut rustc_pkg_crates = FxHashMap::default(); + if has_private { + // If the user provided a path to rustc sources, we add all the rustc_private crates + // and create dependencies on them for the crates which opt-in to that + if let Some(rustc_workspace) = rustc { + handle_rustc_crates( + rustc_workspace, + load, + &mut crate_graph, + rustc_build_data_map, + &cfg_options, + proc_macro_loader, + &mut pkg_to_lib_crate, + &public_deps, + cargo, + &pkg_crates, + ); + } + } + crate_graph +} - // If the user provided a path to rustc sources, we add all the rustc_private crates - // and create dependencies on them for the crates in the current workspace - if let Some(rustc_workspace) = rustc { - for pkg in rustc_workspace.packages() { +fn handle_rustc_crates( + rustc_workspace: &CargoWorkspace, + load: &mut dyn FnMut(&AbsPath) -> Option, + crate_graph: &mut CrateGraph, + rustc_build_data_map: Option<&FxHashMap>, + cfg_options: &CfgOptions, + proc_macro_loader: &dyn Fn(&Path) -> Vec, + pkg_to_lib_crate: &mut FxHashMap, CrateId>, + public_deps: &[(CrateName, CrateId)], + cargo: &CargoWorkspace, + pkg_crates: &FxHashMap, Vec>, +) { + let mut rustc_pkg_crates = FxHashMap::default(); + // The root package of the rustc-dev component is rustc_driver, so we match that + let root_pkg = + rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver"); + // The rustc workspace might be incomplete (such as if rustc-dev is not + // installed for the current toolchain) and `rustcSource` is set to discover. + if let Some(root_pkg) = root_pkg { + // Iterate through every crate in the dependency subtree of rustc_driver using BFS + let mut queue = VecDeque::new(); + queue.push_back(root_pkg); + while let Some(pkg) = queue.pop_front() { + // Don't duplicate packages if they are dependended on a diamond pattern + // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates + // which is not ideal + if rustc_pkg_crates.contains_key(&pkg) { + continue; + } + for dep in &rustc_workspace[pkg].dependencies { + queue.push_back(dep.pkg); + } 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(file_id) = load(&rustc_workspace[tgt].root) { let crate_id = add_target_crate_root( - &mut crate_graph, + crate_graph, &rustc_workspace[pkg], rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)), &cfg_options, @@ -472,44 +506,50 @@ fn cargo_to_crate_graph( file_id, ); pkg_to_lib_crate.insert(pkg, crate_id); - // Add dependencies on the core / std / alloc for rustc + // Add dependencies on core / std / alloc for this crate for (name, krate) in public_deps.iter() { - add_dep(&mut crate_graph, crate_id, name.clone(), *krate); + add_dep(crate_graph, crate_id, name.clone(), *krate); } 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() { - add_dep(&mut crate_graph, from, name.clone(), to); - } + } + // Now add a dep edge from all targets of upstream to the lib + // target of downstream. + for pkg in rustc_pkg_crates.keys().copied() { + 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() { + add_dep(crate_graph, from, name.clone(), to); } } } + } + // Add a dependency on the rustc_private crates for all targets of each package + // which opts in + for dep in rustc_workspace.packages() { + let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); - // Add dependencies for all the crates of the current workspace to rustc_private libraries - for dep in rustc_workspace.packages() { - let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); - - if let Some(&to) = pkg_to_lib_crate.get(&dep) { - for pkg in cargo.packages() { - if !cargo[pkg].is_member { - continue; - } - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - add_dep(&mut crate_graph, from, name.clone(), to); + if let Some(&to) = pkg_to_lib_crate.get(&dep) { + for pkg in cargo.packages() { + let package = &cargo[pkg]; + if !package.metadata.rustc_private { + continue; + } + for &from in pkg_crates.get(&pkg).into_iter().flatten() { + // Avoid creating duplicate dependencies + // This avoids the situation where `from` depends on e.g. `arrayvec`, but + // `rust_analyzer` thinks that it should use the one from the `rustcSource` + // instead of the one from `crates.io` + if !crate_graph[from].dependencies.iter().any(|d| d.name == name) { + add_dep(crate_graph, from, name.clone(), to); } } } } } - crate_graph } fn add_target_crate_root( diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 807fecc40a3..4dbabdba784 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -181,7 +181,8 @@ struct ConfigData { runnables_cargoExtraArgs: Vec = "[]", /// Path to the rust compiler sources, for usage in rustc_private projects, or "discover" - /// to try to automatically find it. + /// to try to automatically find it. Any project which uses rust-analyzer with the rustcPrivate + /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. rustcSource : Option = "null", /// Additional arguments to `rustfmt`. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 96788bc2ccb..5243bcbf6a6 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -107,7 +107,7 @@ [[rust-analyzer.runnables.cargoExtraArgs]]rust-analyzer.runnables.cargoExtraArgs (default: `[]`):: Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be `--release`. [[rust-analyzer.rustcSource]]rust-analyzer.rustcSource (default: `null`):: - Path to the rust compiler sources, for usage in rustc_private projects, or "discover" to try to automatically find it. + Path to the rust compiler sources, for usage in rustc_private projects, or "discover" to try to automatically find it. Any project which uses rust-analyzer with the rustcPrivate crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. [[rust-analyzer.rustfmt.extraArgs]]rust-analyzer.rustfmt.extraArgs (default: `[]`):: Additional arguments to `rustfmt`. [[rust-analyzer.rustfmt.overrideCommand]]rust-analyzer.rustfmt.overrideCommand (default: `null`):: diff --git a/editors/code/package.json b/editors/code/package.json index cabb2d13616..856f1c94e5a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -713,7 +713,7 @@ } }, "rust-analyzer.rustcSource": { - "markdownDescription": "Path to the rust compiler sources, for usage in rustc_private projects, or \"discover\" to try to automatically find it.", + "markdownDescription": "Path to the rust compiler sources, for usage in rustc_private projects, or \"discover\" to try to automatically find it. Any project which uses rust-analyzer with the rustcPrivate crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.", "default": null, "type": [ "null",