aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model
diff options
context:
space:
mode:
Diffstat (limited to 'crates/project_model')
-rw-r--r--crates/project_model/Cargo.toml10
-rw-r--r--crates/project_model/src/build_data.rs23
-rw-r--r--crates/project_model/src/cargo_workspace.rs32
-rw-r--r--crates/project_model/src/workspace.rs138
4 files changed, 131 insertions, 72 deletions
diff --git a/crates/project_model/Cargo.toml b/crates/project_model/Cargo.toml
index 293cb5bfe..fe3258332 100644
--- a/crates/project_model/Cargo.toml
+++ b/crates/project_model/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
12[dependencies] 12[dependencies]
13log = "0.4.8" 13log = "0.4.8"
14rustc-hash = "1.1.0" 14rustc-hash = "1.1.0"
15cargo_metadata = "0.12.2" 15cargo_metadata = "0.13"
16serde = { version = "1.0.106", features = ["derive"] } 16serde = { version = "1.0.106", features = ["derive"] }
17serde_json = "1.0.48" 17serde_json = "1.0.48"
18anyhow = "1.0.26" 18anyhow = "1.0.26"
@@ -22,7 +22,7 @@ la-arena = { version = "0.2.0", path = "../../lib/arena" }
22cfg = { path = "../cfg", version = "0.0.0" } 22cfg = { path = "../cfg", version = "0.0.0" }
23base_db = { path = "../base_db", version = "0.0.0" } 23base_db = { path = "../base_db", version = "0.0.0" }
24toolchain = { path = "../toolchain", version = "0.0.0" } 24toolchain = { path = "../toolchain", version = "0.0.0" }
25proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" } 25proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" }
26paths = { path = "../paths", version = "0.0.0" } 26paths = { path = "../paths", version = "0.0.0" }
27stdx = { path = "../stdx", version = "0.0.0" } 27stdx = { path = "../stdx", version = "0.0.0" }
28profile = { path = "../profile", version = "0.0.0" } 28profile = { path = "../profile", version = "0.0.0" }
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index 295b5f8ef..728a258ea 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -1,14 +1,14 @@
1//! Handles build script specific information 1//! Handles build script specific information
2 2
3use std::{ 3use std::{
4 ffi::OsStr,
5 io::BufReader, 4 io::BufReader,
6 path::{Path, PathBuf}, 5 path::PathBuf,
7 process::{Command, Stdio}, 6 process::{Command, Stdio},
8 sync::Arc, 7 sync::Arc,
9}; 8};
10 9
11use anyhow::Result; 10use anyhow::Result;
11use cargo_metadata::camino::Utf8Path;
12use cargo_metadata::{BuildScript, Message}; 12use cargo_metadata::{BuildScript, Message};
13use itertools::Itertools; 13use itertools::Itertools;
14use paths::{AbsPath, AbsPathBuf}; 14use paths::{AbsPath, AbsPathBuf};
@@ -162,8 +162,8 @@ fn collect_from_workspace(
162 let res = res.entry(package_id.repr.clone()).or_default(); 162 let res = res.entry(package_id.repr.clone()).or_default();
163 // cargo_metadata crate returns default (empty) path for 163 // cargo_metadata crate returns default (empty) path for
164 // older cargos, which is not absolute, so work around that. 164 // older cargos, which is not absolute, so work around that.
165 if out_dir != PathBuf::default() { 165 if !out_dir.as_str().is_empty() {
166 let out_dir = AbsPathBuf::assert(out_dir); 166 let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
167 res.out_dir = Some(out_dir); 167 res.out_dir = Some(out_dir);
168 res.cfgs = cfgs; 168 res.cfgs = cfgs;
169 } 169 }
@@ -178,7 +178,7 @@ fn collect_from_workspace(
178 // Skip rmeta file 178 // Skip rmeta file
179 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) 179 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
180 { 180 {
181 let filename = AbsPathBuf::assert(filename.clone()); 181 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
182 let res = res.entry(package_id.repr.clone()).or_default(); 182 let res = res.entry(package_id.repr.clone()).or_default();
183 res.proc_macro_dylib_path = Some(filename); 183 res.proc_macro_dylib_path = Some(filename);
184 } 184 }
@@ -187,9 +187,9 @@ fn collect_from_workspace(
187 Message::CompilerMessage(message) => { 187 Message::CompilerMessage(message) => {
188 progress(message.target.name.clone()); 188 progress(message.target.name.clone());
189 } 189 }
190 Message::Unknown => (),
191 Message::BuildFinished(_) => {} 190 Message::BuildFinished(_) => {}
192 Message::TextLine(_) => {} 191 Message::TextLine(_) => {}
192 _ => {}
193 } 193 }
194 } 194 }
195 } 195 }
@@ -209,8 +209,8 @@ fn collect_from_workspace(
209} 209}
210 210
211// FIXME: File a better way to know if it is a dylib 211// FIXME: File a better way to know if it is a dylib
212fn is_dylib(path: &Path) -> bool { 212fn is_dylib(path: &Utf8Path) -> bool {
213 match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { 213 match path.extension().map(|e| e.to_string().to_lowercase()) {
214 None => false, 214 None => false,
215 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), 215 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
216 } 216 }
@@ -227,9 +227,7 @@ fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildDat
227 227
228 let mut manifest_dir = package.manifest_path.clone(); 228 let mut manifest_dir = package.manifest_path.clone();
229 manifest_dir.pop(); 229 manifest_dir.pop();
230 if let Some(cargo_manifest_dir) = manifest_dir.to_str() { 230 env.push(("CARGO_MANIFEST_DIR".into(), manifest_dir.into_string()));
231 env.push(("CARGO_MANIFEST_DIR".into(), cargo_manifest_dir.into()));
232 }
233 231
234 // Not always right, but works for common cases. 232 // Not always right, but works for common cases.
235 env.push(("CARGO".into(), "cargo".into())); 233 env.push(("CARGO".into(), "cargo".into()));
@@ -251,7 +249,6 @@ fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildDat
251 env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default())); 249 env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
252 env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default())); 250 env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));
253 251
254 let license_file = 252 let license_file = package.license_file.as_ref().map(|buf| buf.to_string()).unwrap_or_default();
255 package.license_file.as_ref().map(|buf| buf.display().to_string()).unwrap_or_default();
256 env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file)); 253 env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
257} 254}
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index 1d8d34a0b..bc6e20341 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -1,5 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::path::PathBuf;
3use std::{convert::TryInto, ops, process::Command, sync::Arc}; 4use std::{convert::TryInto, ops, process::Command, sync::Arc};
4 5
5use anyhow::{Context, Result}; 6use anyhow::{Context, Result};
@@ -8,6 +9,8 @@ use cargo_metadata::{CargoOpt, MetadataCommand};
8use la_arena::{Arena, Idx}; 9use la_arena::{Arena, Idx};
9use paths::{AbsPath, AbsPathBuf}; 10use paths::{AbsPath, AbsPathBuf};
10use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
12use serde::Deserialize;
13use serde_json::from_value;
11 14
12use crate::build_data::BuildDataConfig; 15use crate::build_data::BuildDataConfig;
13use crate::utf8_stdout; 16use crate::utf8_stdout;
@@ -103,6 +106,13 @@ pub struct PackageData {
103 pub active_features: Vec<String>, 106 pub active_features: Vec<String>,
104 // String representation of package id 107 // String representation of package id
105 pub id: String, 108 pub id: String,
109 // The contents of [package.metadata.rust-analyzer]
110 pub metadata: RustAnalyzerPackageMetaData,
111}
112
113#[derive(Deserialize, Default, Debug, Clone, Eq, PartialEq)]
114pub struct RustAnalyzerPackageMetaData {
115 pub rustc_private: bool,
106} 116}
107 117
108#[derive(Debug, Clone, Eq, PartialEq)] 118#[derive(Debug, Clone, Eq, PartialEq)]
@@ -160,6 +170,13 @@ impl PackageData {
160 } 170 }
161} 171}
162 172
173#[derive(Deserialize, Default)]
174// Deserialise helper for the cargo metadata
175struct PackageMetadata {
176 #[serde(rename = "rust-analyzer")]
177 rust_analyzer: Option<RustAnalyzerPackageMetaData>,
178}
179
163impl CargoWorkspace { 180impl CargoWorkspace {
164 pub fn from_cargo_metadata( 181 pub fn from_cargo_metadata(
165 cargo_toml: &AbsPath, 182 cargo_toml: &AbsPath,
@@ -243,23 +260,27 @@ impl CargoWorkspace {
243 260
244 meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); 261 meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
245 for meta_pkg in &meta.packages { 262 for meta_pkg in &meta.packages {
246 let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = 263 let cargo_metadata::Package {
247 meta_pkg; 264 id, edition, name, manifest_path, version, metadata, ..
265 } = meta_pkg;
266 let meta = from_value::<PackageMetadata>(metadata.clone()).unwrap_or_default();
248 let is_member = ws_members.contains(&id); 267 let is_member = ws_members.contains(&id);
249 let edition = edition 268 let edition = edition
250 .parse::<Edition>() 269 .parse::<Edition>()
251 .with_context(|| format!("Failed to parse edition {}", edition))?; 270 .with_context(|| format!("Failed to parse edition {}", edition))?;
271
252 let pkg = packages.alloc(PackageData { 272 let pkg = packages.alloc(PackageData {
253 id: id.repr.clone(), 273 id: id.repr.clone(),
254 name: name.clone(), 274 name: name.clone(),
255 version: version.to_string(), 275 version: version.to_string(),
256 manifest: AbsPathBuf::assert(manifest_path.clone()), 276 manifest: AbsPathBuf::assert(PathBuf::from(&manifest_path)),
257 targets: Vec::new(), 277 targets: Vec::new(),
258 is_member, 278 is_member,
259 edition, 279 edition,
260 dependencies: Vec::new(), 280 dependencies: Vec::new(),
261 features: meta_pkg.features.clone().into_iter().collect(), 281 features: meta_pkg.features.clone().into_iter().collect(),
262 active_features: Vec::new(), 282 active_features: Vec::new(),
283 metadata: meta.rust_analyzer.unwrap_or_default(),
263 }); 284 });
264 let pkg_data = &mut packages[pkg]; 285 let pkg_data = &mut packages[pkg];
265 pkg_by_id.insert(id, pkg); 286 pkg_by_id.insert(id, pkg);
@@ -268,7 +289,7 @@ impl CargoWorkspace {
268 let tgt = targets.alloc(TargetData { 289 let tgt = targets.alloc(TargetData {
269 package: pkg, 290 package: pkg,
270 name: meta_tgt.name.clone(), 291 name: meta_tgt.name.clone(),
271 root: AbsPathBuf::assert(meta_tgt.src_path.clone()), 292 root: AbsPathBuf::assert(PathBuf::from(&meta_tgt.src_path)),
272 kind: TargetKind::new(meta_tgt.kind.as_slice()), 293 kind: TargetKind::new(meta_tgt.kind.as_slice()),
273 is_proc_macro, 294 is_proc_macro,
274 }); 295 });
@@ -305,7 +326,8 @@ impl CargoWorkspace {
305 packages[source].active_features.extend(node.features); 326 packages[source].active_features.extend(node.features);
306 } 327 }
307 328
308 let workspace_root = AbsPathBuf::assert(meta.workspace_root); 329 let workspace_root =
330 AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string()));
309 let build_data_config = BuildDataConfig::new( 331 let build_data_config = BuildDataConfig::new(
310 cargo_toml.to_path_buf(), 332 cargo_toml.to_path_buf(),
311 config.clone(), 333 config.clone(),
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 0220efdb4..1b53fcc30 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -2,11 +2,7 @@
2//! metadata` or `rust-project.json`) into representation stored in the salsa 2//! metadata` or `rust-project.json`) into representation stored in the salsa
3//! database -- `CrateGraph`. 3//! database -- `CrateGraph`.
4 4
5use std::{ 5use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
6 fmt, fs,
7 path::{Component, Path},
8 process::Command,
9};
10 6
11use anyhow::{Context, Result}; 7use anyhow::{Context, Result};
12use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro}; 8use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
@@ -60,6 +56,7 @@ impl fmt::Debug for ProjectWorkspace {
60 match self { 56 match self {
61 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f 57 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
62 .debug_struct("Cargo") 58 .debug_struct("Cargo")
59 .field("root", &cargo.workspace_root().file_name())
63 .field("n_packages", &cargo.packages().len()) 60 .field("n_packages", &cargo.packages().len())
64 .field("n_sysroot_crates", &sysroot.crates().len()) 61 .field("n_sysroot_crates", &sysroot.crates().len())
65 .field( 62 .field(
@@ -279,11 +276,8 @@ impl ProjectWorkspace {
279 276
280 pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) { 277 pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) {
281 match self { 278 match self {
282 ProjectWorkspace::Cargo { cargo, rustc, .. } => { 279 ProjectWorkspace::Cargo { cargo, .. } => {
283 collector.add_config(&cargo.workspace_root(), cargo.build_data_config().clone()); 280 collector.add_config(&cargo.workspace_root(), cargo.build_data_config().clone());
284 if let Some(rustc) = rustc {
285 collector.add_config(rustc.workspace_root(), rustc.build_data_config().clone());
286 }
287 } 281 }
288 _ => {} 282 _ => {}
289 } 283 }
@@ -380,9 +374,11 @@ fn cargo_to_crate_graph(
380 cfg_options.insert_atom("debug_assertions".into()); 374 cfg_options.insert_atom("debug_assertions".into());
381 375
382 let mut pkg_crates = FxHashMap::default(); 376 let mut pkg_crates = FxHashMap::default();
383 377 // Does any crate signal to rust-analyzer that they need the rustc_private crates?
378 let mut has_private = false;
384 // Next, create crates for each package, target pair 379 // Next, create crates for each package, target pair
385 for pkg in cargo.packages() { 380 for pkg in cargo.packages() {
381 has_private |= cargo[pkg].metadata.rustc_private;
386 let mut lib_tgt = None; 382 let mut lib_tgt = None;
387 for &tgt in cargo[pkg].targets.iter() { 383 for &tgt in cargo[pkg].targets.iter() {
388 if let Some(file_id) = load(&cargo[tgt].root) { 384 if let Some(file_id) = load(&cargo[tgt].root) {
@@ -443,28 +439,66 @@ fn cargo_to_crate_graph(
443 } 439 }
444 } 440 }
445 441
446 let mut rustc_pkg_crates = FxHashMap::default(); 442 if has_private {
443 // If the user provided a path to rustc sources, we add all the rustc_private crates
444 // and create dependencies on them for the crates which opt-in to that
445 if let Some(rustc_workspace) = rustc {
446 handle_rustc_crates(
447 rustc_workspace,
448 load,
449 &mut crate_graph,
450 rustc_build_data_map,
451 &cfg_options,
452 proc_macro_loader,
453 &mut pkg_to_lib_crate,
454 &public_deps,
455 cargo,
456 &pkg_crates,
457 );
458 }
459 }
460 crate_graph
461}
447 462
448 // If the user provided a path to rustc sources, we add all the rustc_private crates 463fn handle_rustc_crates(
449 // and create dependencies on them for the crates in the current workspace 464 rustc_workspace: &CargoWorkspace,
450 if let Some(rustc_workspace) = rustc { 465 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
451 for pkg in rustc_workspace.packages() { 466 crate_graph: &mut CrateGraph,
467 rustc_build_data_map: Option<&FxHashMap<String, BuildData>>,
468 cfg_options: &CfgOptions,
469 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
470 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
471 public_deps: &[(CrateName, CrateId)],
472 cargo: &CargoWorkspace,
473 pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<CrateId>>,
474) {
475 let mut rustc_pkg_crates = FxHashMap::default();
476 // The root package of the rustc-dev component is rustc_driver, so we match that
477 let root_pkg =
478 rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
479 // The rustc workspace might be incomplete (such as if rustc-dev is not
480 // installed for the current toolchain) and `rustcSource` is set to discover.
481 if let Some(root_pkg) = root_pkg {
482 // Iterate through every crate in the dependency subtree of rustc_driver using BFS
483 let mut queue = VecDeque::new();
484 queue.push_back(root_pkg);
485 while let Some(pkg) = queue.pop_front() {
486 // Don't duplicate packages if they are dependended on a diamond pattern
487 // N.B. if this line is ommitted, we try to analyse over 4_800_000 crates
488 // which is not ideal
489 if rustc_pkg_crates.contains_key(&pkg) {
490 continue;
491 }
492 for dep in &rustc_workspace[pkg].dependencies {
493 queue.push_back(dep.pkg);
494 }
452 for &tgt in rustc_workspace[pkg].targets.iter() { 495 for &tgt in rustc_workspace[pkg].targets.iter() {
453 if rustc_workspace[tgt].kind != TargetKind::Lib { 496 if rustc_workspace[tgt].kind != TargetKind::Lib {
454 continue; 497 continue;
455 } 498 }
456 // Exclude alloc / core / std
457 if rustc_workspace[tgt]
458 .root
459 .components()
460 .any(|c| c == Component::Normal("library".as_ref()))
461 {
462 continue;
463 }
464
465 if let Some(file_id) = load(&rustc_workspace[tgt].root) { 499 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
466 let crate_id = add_target_crate_root( 500 let crate_id = add_target_crate_root(
467 &mut crate_graph, 501 crate_graph,
468 &rustc_workspace[pkg], 502 &rustc_workspace[pkg],
469 rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)), 503 rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)),
470 &cfg_options, 504 &cfg_options,
@@ -472,44 +506,50 @@ fn cargo_to_crate_graph(
472 file_id, 506 file_id,
473 ); 507 );
474 pkg_to_lib_crate.insert(pkg, crate_id); 508 pkg_to_lib_crate.insert(pkg, crate_id);
475 // Add dependencies on the core / std / alloc for rustc 509 // Add dependencies on core / std / alloc for this crate
476 for (name, krate) in public_deps.iter() { 510 for (name, krate) in public_deps.iter() {
477 add_dep(&mut crate_graph, crate_id, name.clone(), *krate); 511 add_dep(crate_graph, crate_id, name.clone(), *krate);
478 } 512 }
479 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); 513 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
480 } 514 }
481 } 515 }
482 } 516 }
483 // Now add a dep edge from all targets of upstream to the lib 517 }
484 // target of downstream. 518 // Now add a dep edge from all targets of upstream to the lib
485 for pkg in rustc_workspace.packages() { 519 // target of downstream.
486 for dep in rustc_workspace[pkg].dependencies.iter() { 520 for pkg in rustc_pkg_crates.keys().copied() {
487 let name = CrateName::new(&dep.name).unwrap(); 521 for dep in rustc_workspace[pkg].dependencies.iter() {
488 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { 522 let name = CrateName::new(&dep.name).unwrap();
489 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() { 523 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
490 add_dep(&mut crate_graph, from, name.clone(), to); 524 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
491 } 525 add_dep(crate_graph, from, name.clone(), to);
492 } 526 }
493 } 527 }
494 } 528 }
495 529 }
496 // Add dependencies for all the crates of the current workspace to rustc_private libraries 530 // Add a dependency on the rustc_private crates for all targets of each package
497 for dep in rustc_workspace.packages() { 531 // which opts in
498 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name); 532 for dep in rustc_workspace.packages() {
499 533 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
500 if let Some(&to) = pkg_to_lib_crate.get(&dep) { 534
501 for pkg in cargo.packages() { 535 if let Some(&to) = pkg_to_lib_crate.get(&dep) {
502 if !cargo[pkg].is_member { 536 for pkg in cargo.packages() {
503 continue; 537 let package = &cargo[pkg];
504 } 538 if !package.metadata.rustc_private {
505 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 539 continue;
506 add_dep(&mut crate_graph, from, name.clone(), to); 540 }
541 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
542 // Avoid creating duplicate dependencies
543 // This avoids the situation where `from` depends on e.g. `arrayvec`, but
544 // `rust_analyzer` thinks that it should use the one from the `rustcSource`
545 // instead of the one from `crates.io`
546 if !crate_graph[from].dependencies.iter().any(|d| d.name == name) {
547 add_dep(crate_graph, from, name.clone(), to);
507 } 548 }
508 } 549 }
509 } 550 }
510 } 551 }
511 } 552 }
512 crate_graph
513} 553}
514 554
515fn add_target_crate_root( 555fn add_target_crate_root(