aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-11-13 15:44:48 +0000
committerAleksey Kladov <[email protected]>2020-11-13 15:44:48 +0000
commitaeda30e301d74e40fc1eb992fad581afb627126f (patch)
tree0b3a116ab450e5a1eae24bde65ed683e75f9555a /crates/project_model
parentf0f13d8cfa3a9d6d3968be1cc852eb3120c94653 (diff)
Move tricky workspace logic to a separate module
Diffstat (limited to 'crates/project_model')
-rw-r--r--crates/project_model/src/lib.rs606
-rw-r--r--crates/project_model/src/workspace.rs607
2 files changed, 611 insertions, 602 deletions
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index 4531b1928..24aa9b8fa 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -4,74 +4,27 @@ mod cargo_workspace;
4mod project_json; 4mod project_json;
5mod sysroot; 5mod sysroot;
6mod cfg_flag; 6mod cfg_flag;
7mod workspace;
7 8
8use std::{ 9use std::{
9 fmt, 10 fs::{read_dir, ReadDir},
10 fs::{self, read_dir, ReadDir},
11 io, 11 io,
12 path::Component,
13 process::Command, 12 process::Command,
14}; 13};
15 14
16use anyhow::{bail, Context, Result}; 15use anyhow::{bail, Context, Result};
17use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
18use cfg::CfgOptions;
19use paths::{AbsPath, AbsPathBuf}; 16use paths::{AbsPath, AbsPathBuf};
20use rustc_hash::{FxHashMap, FxHashSet}; 17use rustc_hash::FxHashSet;
21
22use crate::cfg_flag::CfgFlag;
23 18
24pub use crate::{ 19pub use crate::{
25 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, 20 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
26 project_json::{ProjectJson, ProjectJsonData}, 21 project_json::{ProjectJson, ProjectJsonData},
27 sysroot::Sysroot, 22 sysroot::Sysroot,
23 workspace::{PackageRoot, ProjectWorkspace},
28}; 24};
29 25
30pub use proc_macro_api::ProcMacroClient; 26pub use proc_macro_api::ProcMacroClient;
31 27
32#[derive(Clone, Eq, PartialEq)]
33pub enum ProjectWorkspace {
34 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
35 Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
36 /// Project workspace was manually specified using a `rust-project.json` file.
37 Json { project: ProjectJson, sysroot: Option<Sysroot> },
38}
39
40impl fmt::Debug for ProjectWorkspace {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
44 .debug_struct("Cargo")
45 .field("n_packages", &cargo.packages().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 )
51 .finish(),
52 ProjectWorkspace::Json { project, sysroot } => {
53 let mut debug_struct = f.debug_struct("Json");
54 debug_struct.field("n_crates", &project.n_crates());
55 if let Some(sysroot) = sysroot {
56 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
57 }
58 debug_struct.finish()
59 }
60 }
61 }
62}
63
64/// `PackageRoot` describes a package root folder.
65/// Which may be an external dependency, or a member of
66/// the current workspace.
67#[derive(Debug, Clone, Eq, PartialEq, Hash)]
68pub struct PackageRoot {
69 /// Is a member of the current workspace
70 pub is_member: bool,
71 pub include: Vec<AbsPathBuf>,
72 pub exclude: Vec<AbsPathBuf>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 28#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
76pub enum ProjectManifest { 29pub enum ProjectManifest {
77 ProjectJson(AbsPathBuf), 30 ProjectJson(AbsPathBuf),
@@ -158,462 +111,6 @@ impl ProjectManifest {
158 } 111 }
159} 112}
160 113
161impl ProjectWorkspace {
162 pub fn load(
163 manifest: ProjectManifest,
164 cargo_config: &CargoConfig,
165 with_sysroot: bool,
166 ) -> Result<ProjectWorkspace> {
167 let res = match manifest {
168 ProjectManifest::ProjectJson(project_json) => {
169 let file = fs::read_to_string(&project_json).with_context(|| {
170 format!("Failed to read json file {}", project_json.display())
171 })?;
172 let data = serde_json::from_str(&file).with_context(|| {
173 format!("Failed to deserialize json file {}", project_json.display())
174 })?;
175 let project_location = project_json.parent().unwrap().to_path_buf();
176 let project = ProjectJson::new(&project_location, data);
177 let sysroot = match &project.sysroot_src {
178 Some(path) => Some(Sysroot::load(path)?),
179 None => None,
180 };
181 ProjectWorkspace::Json { project, sysroot }
182 }
183 ProjectManifest::CargoToml(cargo_toml) => {
184 let cargo_version = utf8_stdout({
185 let mut cmd = Command::new(toolchain::cargo());
186 cmd.arg("--version");
187 cmd
188 })?;
189
190 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config)
191 .with_context(|| {
192 format!(
193 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
194 cargo_toml.display(),
195 cargo_version
196 )
197 })?;
198 let sysroot = if with_sysroot {
199 Sysroot::discover(&cargo_toml).with_context(|| {
200 format!(
201 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
202 cargo_toml.display()
203 )
204 })?
205 } else {
206 Sysroot::default()
207 };
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 }
221 }
222 };
223
224 Ok(res)
225 }
226
227 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
228 let sysroot = match &project_json.sysroot_src {
229 Some(path) => Some(Sysroot::load(path)?),
230 None => None,
231 };
232
233 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
234 }
235
236 /// Returns the roots for the current `ProjectWorkspace`
237 /// The return type contains the path and whether or not
238 /// the root is a member of the current workspace
239 pub fn to_roots(&self) -> Vec<PackageRoot> {
240 match self {
241 ProjectWorkspace::Json { project, sysroot } => project
242 .crates()
243 .map(|(_, krate)| PackageRoot {
244 is_member: krate.is_workspace_member,
245 include: krate.include.clone(),
246 exclude: krate.exclude.clone(),
247 })
248 .collect::<FxHashSet<_>>()
249 .into_iter()
250 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
251 sysroot.crates().map(move |krate| PackageRoot {
252 is_member: false,
253 include: vec![sysroot[krate].root_dir().to_path_buf()],
254 exclude: Vec::new(),
255 })
256 }))
257 .collect::<Vec<_>>(),
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 }
295 }
296 }
297
298 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
299 match self {
300 ProjectWorkspace::Json { project, sysroot: _ } => project
301 .crates()
302 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
303 .cloned()
304 .collect(),
305 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo
306 .packages()
307 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
308 .cloned()
309 .collect(),
310 }
311 }
312
313 pub fn n_packages(&self) -> usize {
314 match self {
315 ProjectWorkspace::Json { project, .. } => project.n_crates(),
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
319 }
320 }
321 }
322
323 pub fn to_crate_graph(
324 &self,
325 target: Option<&str>,
326 proc_macro_client: &ProcMacroClient,
327 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
328 ) -> CrateGraph {
329 let mut crate_graph = CrateGraph::default();
330 match self {
331 ProjectWorkspace::Json { project, sysroot } => {
332 let sysroot_dps = sysroot
333 .as_ref()
334 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
335
336 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
337 let crates: FxHashMap<_, _> = project
338 .crates()
339 .filter_map(|(crate_id, krate)| {
340 let file_path = &krate.root_module;
341 let file_id = match load(&file_path) {
342 Some(id) => id,
343 None => {
344 log::error!("failed to load crate root {}", file_path.display());
345 return None;
346 }
347 };
348
349 let env = krate.env.clone().into_iter().collect();
350 let proc_macro = krate
351 .proc_macro_dylib_path
352 .clone()
353 .map(|it| proc_macro_client.by_dylib_path(&it));
354
355 let target = krate.target.as_deref().or(target);
356 let target_cfgs = cfg_cache
357 .entry(target)
358 .or_insert_with(|| get_rustc_cfg_options(target));
359
360 let mut cfg_options = CfgOptions::default();
361 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
362
363 Some((
364 crate_id,
365 crate_graph.add_crate_root(
366 file_id,
367 krate.edition,
368 krate.display_name.clone(),
369 cfg_options,
370 env,
371 proc_macro.unwrap_or_default(),
372 ),
373 ))
374 })
375 .collect();
376
377 for (from, krate) in project.crates() {
378 if let Some(&from) = crates.get(&from) {
379 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
380 for (name, to) in public_deps.iter() {
381 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
382 log::error!("cyclic dependency on {} for {:?}", name, from)
383 }
384 }
385 }
386
387 for dep in &krate.deps {
388 let to_crate_id = dep.crate_id;
389 if let Some(&to) = crates.get(&to_crate_id) {
390 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
391 log::error!("cyclic dependency {:?} -> {:?}", from, to);
392 }
393 }
394 }
395 }
396 }
397 }
398 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
399 let (public_deps, libproc_macro) =
400 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
401
402 let mut cfg_options = CfgOptions::default();
403 cfg_options.extend(get_rustc_cfg_options(target));
404
405 let mut pkg_to_lib_crate = FxHashMap::default();
406
407 // Add test cfg for non-sysroot crates
408 cfg_options.insert_atom("test".into());
409 cfg_options.insert_atom("debug_assertions".into());
410
411 let mut pkg_crates = FxHashMap::default();
412
413 // Next, create crates for each package, target pair
414 for pkg in cargo.packages() {
415 let mut lib_tgt = None;
416 for &tgt in cargo[pkg].targets.iter() {
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 ) {
425 if cargo[tgt].kind == TargetKind::Lib {
426 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
427 pkg_to_lib_crate.insert(pkg, crate_id);
428 }
429 if cargo[tgt].is_proc_macro {
430 if let Some(proc_macro) = libproc_macro {
431 if let Err(_) = crate_graph.add_dep(
432 crate_id,
433 CrateName::new("proc_macro").unwrap(),
434 proc_macro,
435 ) {
436 log::error!(
437 "cyclic dependency on proc_macro for {}",
438 &cargo[pkg].name
439 )
440 }
441 }
442 }
443
444 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
445 }
446 }
447
448 // Set deps to the core, std and to the lib target of the current package
449 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
450 if let Some((to, name)) = lib_tgt.clone() {
451 // For root projects with dashes in their name,
452 // cargo metadata does not do any normalization,
453 // so we do it ourselves currently
454 let name = CrateName::normalize_dashes(&name);
455 if to != from && crate_graph.add_dep(from, name, to).is_err() {
456 log::error!(
457 "cyclic dependency between targets of {}",
458 &cargo[pkg].name
459 )
460 }
461 }
462 for (name, krate) in public_deps.iter() {
463 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
464 log::error!(
465 "cyclic dependency on {} for {}",
466 name,
467 &cargo[pkg].name
468 )
469 }
470 }
471 }
472 }
473
474 // Now add a dep edge from all targets of upstream to the lib
475 // target of downstream.
476 for pkg in cargo.packages() {
477 for dep in cargo[pkg].dependencies.iter() {
478 let name = CrateName::new(&dep.name).unwrap();
479 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
480 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
481 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
482 log::error!(
483 "cyclic dependency {} -> {}",
484 &cargo[pkg].name,
485 &cargo[dep.pkg].name
486 )
487 }
488 }
489 }
490 }
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 }
578 }
579 }
580 if crate_graph.patch_cfg_if() {
581 log::debug!("Patched std to depend on cfg-if")
582 } else {
583 log::debug!("Did not patch std to depend on cfg-if")
584 }
585 crate_graph
586 }
587}
588
589fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
590 let mut res = Vec::new();
591
592 // Some nightly-only cfgs, which are required for stdlib
593 res.push(CfgFlag::Atom("target_thread_local".into()));
594 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
595 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
596 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
597 }
598 }
599
600 let rustc_cfgs = {
601 let mut cmd = Command::new(toolchain::rustc());
602 cmd.args(&["--print", "cfg", "-O"]);
603 if let Some(target) = target {
604 cmd.args(&["--target", target]);
605 }
606 utf8_stdout(cmd)
607 };
608
609 match rustc_cfgs {
610 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
611 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
612 }
613
614 res
615}
616
617fn utf8_stdout(mut cmd: Command) -> Result<String> { 114fn utf8_stdout(mut cmd: Command) -> Result<String> {
618 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; 115 let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
619 if !output.status.success() { 116 if !output.status.success() {
@@ -627,98 +124,3 @@ fn utf8_stdout(mut cmd: Command) -> Result<String> {
627 let stdout = String::from_utf8(output.stdout)?; 124 let stdout = String::from_utf8(output.stdout)?;
628 Ok(stdout.trim().to_string()) 125 Ok(stdout.trim().to_string())
629} 126}
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}
677fn sysroot_to_crate_graph(
678 crate_graph: &mut CrateGraph,
679 sysroot: &Sysroot,
680 target: Option<&str>,
681 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
682) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
683 let mut cfg_options = CfgOptions::default();
684 cfg_options.extend(get_rustc_cfg_options(target));
685 let sysroot_crates: FxHashMap<_, _> = sysroot
686 .crates()
687 .filter_map(|krate| {
688 let file_id = load(&sysroot[krate].root)?;
689
690 let env = Env::default();
691 let proc_macro = vec![];
692 let name = CrateName::new(&sysroot[krate].name)
693 .expect("Sysroot crates' names do not contain dashes");
694 let crate_id = crate_graph.add_crate_root(
695 file_id,
696 Edition::Edition2018,
697 Some(name.into()),
698 cfg_options.clone(),
699 env,
700 proc_macro,
701 );
702 Some((krate, crate_id))
703 })
704 .collect();
705
706 for from in sysroot.crates() {
707 for &to in sysroot[from].deps.iter() {
708 let name = CrateName::new(&sysroot[to].name).unwrap();
709 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
710 if let Err(_) = crate_graph.add_dep(from, name, to) {
711 log::error!("cyclic dependency between sysroot crates")
712 }
713 }
714 }
715 }
716
717 let public_deps = sysroot
718 .public_deps()
719 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
720 .collect::<Vec<_>>();
721
722 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
723 (public_deps, libproc_macro)
724}
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
new file mode 100644
index 000000000..43ea351d1
--- /dev/null
+++ b/crates/project_model/src/workspace.rs
@@ -0,0 +1,607 @@
1use std::{fmt, fs, path::Component, process::Command};
2
3use anyhow::{Context, Result};
4use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
5use cfg::CfgOptions;
6use paths::{AbsPath, AbsPathBuf};
7use proc_macro_api::ProcMacroClient;
8use rustc_hash::{FxHashMap, FxHashSet};
9
10use crate::{
11 cargo_workspace, cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoWorkspace, ProjectJson,
12 ProjectManifest, Sysroot, TargetKind,
13};
14
15/// `PackageRoot` describes a package root folder.
16/// Which may be an external dependency, or a member of
17/// the current workspace.
18#[derive(Debug, Clone, Eq, PartialEq, Hash)]
19pub struct PackageRoot {
20 /// Is a member of the current workspace
21 pub is_member: bool,
22 pub include: Vec<AbsPathBuf>,
23 pub exclude: Vec<AbsPathBuf>,
24}
25
26#[derive(Clone, Eq, PartialEq)]
27pub enum ProjectWorkspace {
28 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
29 Cargo { cargo: CargoWorkspace, sysroot: Sysroot, rustc: Option<CargoWorkspace> },
30 /// Project workspace was manually specified using a `rust-project.json` file.
31 Json { project: ProjectJson, sysroot: Option<Sysroot> },
32}
33
34impl fmt::Debug for ProjectWorkspace {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => f
38 .debug_struct("Cargo")
39 .field("n_packages", &cargo.packages().len())
40 .field("n_sysroot_crates", &sysroot.crates().len())
41 .field(
42 "n_rustc_compiler_crates",
43 &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
44 )
45 .finish(),
46 ProjectWorkspace::Json { project, sysroot } => {
47 let mut debug_struct = f.debug_struct("Json");
48 debug_struct.field("n_crates", &project.n_crates());
49 if let Some(sysroot) = sysroot {
50 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
51 }
52 debug_struct.finish()
53 }
54 }
55 }
56}
57
58impl ProjectWorkspace {
59 pub fn load(
60 manifest: ProjectManifest,
61 cargo_config: &CargoConfig,
62 with_sysroot: bool,
63 ) -> Result<ProjectWorkspace> {
64 let res = match manifest {
65 ProjectManifest::ProjectJson(project_json) => {
66 let file = fs::read_to_string(&project_json).with_context(|| {
67 format!("Failed to read json file {}", project_json.display())
68 })?;
69 let data = serde_json::from_str(&file).with_context(|| {
70 format!("Failed to deserialize json file {}", project_json.display())
71 })?;
72 let project_location = project_json.parent().unwrap().to_path_buf();
73 let project = ProjectJson::new(&project_location, data);
74 let sysroot = match &project.sysroot_src {
75 Some(path) => Some(Sysroot::load(path)?),
76 None => None,
77 };
78 ProjectWorkspace::Json { project, sysroot }
79 }
80 ProjectManifest::CargoToml(cargo_toml) => {
81 let cargo_version = utf8_stdout({
82 let mut cmd = Command::new(toolchain::cargo());
83 cmd.arg("--version");
84 cmd
85 })?;
86
87 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config)
88 .with_context(|| {
89 format!(
90 "Failed to read Cargo metadata from Cargo.toml file {}, {}",
91 cargo_toml.display(),
92 cargo_version
93 )
94 })?;
95 let sysroot = if with_sysroot {
96 Sysroot::discover(&cargo_toml).with_context(|| {
97 format!(
98 "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
99 cargo_toml.display()
100 )
101 })?
102 } else {
103 Sysroot::default()
104 };
105
106 let rustc = if let Some(rustc_dir) = &cargo_config.rustc_source {
107 Some(
108 CargoWorkspace::from_cargo_metadata(&rustc_dir, cargo_config)
109 .with_context(|| {
110 format!("Failed to read Cargo metadata for Rust sources")
111 })?,
112 )
113 } else {
114 None
115 };
116
117 ProjectWorkspace::Cargo { cargo, sysroot, rustc }
118 }
119 };
120
121 Ok(res)
122 }
123
124 pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
125 let sysroot = match &project_json.sysroot_src {
126 Some(path) => Some(Sysroot::load(path)?),
127 None => None,
128 };
129
130 Ok(ProjectWorkspace::Json { project: project_json, sysroot })
131 }
132
133 /// Returns the roots for the current `ProjectWorkspace`
134 /// The return type contains the path and whether or not
135 /// the root is a member of the current workspace
136 pub fn to_roots(&self) -> Vec<PackageRoot> {
137 match self {
138 ProjectWorkspace::Json { project, sysroot } => project
139 .crates()
140 .map(|(_, krate)| PackageRoot {
141 is_member: krate.is_workspace_member,
142 include: krate.include.clone(),
143 exclude: krate.exclude.clone(),
144 })
145 .collect::<FxHashSet<_>>()
146 .into_iter()
147 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
148 sysroot.crates().map(move |krate| PackageRoot {
149 is_member: false,
150 include: vec![sysroot[krate].root_dir().to_path_buf()],
151 exclude: Vec::new(),
152 })
153 }))
154 .collect::<Vec<_>>(),
155 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
156 let roots = cargo
157 .packages()
158 .map(|pkg| {
159 let is_member = cargo[pkg].is_member;
160 let pkg_root = cargo[pkg].root().to_path_buf();
161
162 let mut include = vec![pkg_root.clone()];
163 include.extend(cargo[pkg].out_dir.clone());
164
165 let mut exclude = vec![pkg_root.join(".git")];
166 if is_member {
167 exclude.push(pkg_root.join("target"));
168 } else {
169 exclude.push(pkg_root.join("tests"));
170 exclude.push(pkg_root.join("examples"));
171 exclude.push(pkg_root.join("benches"));
172 }
173 PackageRoot { is_member, include, exclude }
174 })
175 .chain(sysroot.crates().map(|krate| PackageRoot {
176 is_member: false,
177 include: vec![sysroot[krate].root_dir().to_path_buf()],
178 exclude: Vec::new(),
179 }));
180 if let Some(rustc_packages) = rustc {
181 roots
182 .chain(rustc_packages.packages().map(|krate| PackageRoot {
183 is_member: false,
184 include: vec![rustc_packages[krate].root().to_path_buf()],
185 exclude: Vec::new(),
186 }))
187 .collect()
188 } else {
189 roots.collect()
190 }
191 }
192 }
193 }
194
195 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
196 match self {
197 ProjectWorkspace::Json { project, sysroot: _ } => project
198 .crates()
199 .filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
200 .cloned()
201 .collect(),
202 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot, rustc: _rustc_crates } => cargo
203 .packages()
204 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
205 .cloned()
206 .collect(),
207 }
208 }
209
210 pub fn n_packages(&self) -> usize {
211 match self {
212 ProjectWorkspace::Json { project, .. } => project.n_crates(),
213 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
214 let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
215 cargo.packages().len() + sysroot.crates().len() + rustc_package_len
216 }
217 }
218 }
219
220 pub fn to_crate_graph(
221 &self,
222 target: Option<&str>,
223 proc_macro_client: &ProcMacroClient,
224 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
225 ) -> CrateGraph {
226 let mut crate_graph = CrateGraph::default();
227 match self {
228 ProjectWorkspace::Json { project, sysroot } => {
229 let sysroot_dps = sysroot
230 .as_ref()
231 .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
232
233 let mut cfg_cache: FxHashMap<Option<&str>, Vec<CfgFlag>> = FxHashMap::default();
234 let crates: FxHashMap<_, _> = project
235 .crates()
236 .filter_map(|(crate_id, krate)| {
237 let file_path = &krate.root_module;
238 let file_id = match load(&file_path) {
239 Some(id) => id,
240 None => {
241 log::error!("failed to load crate root {}", file_path.display());
242 return None;
243 }
244 };
245
246 let env = krate.env.clone().into_iter().collect();
247 let proc_macro = krate
248 .proc_macro_dylib_path
249 .clone()
250 .map(|it| proc_macro_client.by_dylib_path(&it));
251
252 let target = krate.target.as_deref().or(target);
253 let target_cfgs = cfg_cache
254 .entry(target)
255 .or_insert_with(|| get_rustc_cfg_options(target));
256
257 let mut cfg_options = CfgOptions::default();
258 cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
259
260 Some((
261 crate_id,
262 crate_graph.add_crate_root(
263 file_id,
264 krate.edition,
265 krate.display_name.clone(),
266 cfg_options,
267 env,
268 proc_macro.unwrap_or_default(),
269 ),
270 ))
271 })
272 .collect();
273
274 for (from, krate) in project.crates() {
275 if let Some(&from) = crates.get(&from) {
276 if let Some((public_deps, _proc_macro)) = &sysroot_dps {
277 for (name, to) in public_deps.iter() {
278 if let Err(_) = crate_graph.add_dep(from, name.clone(), *to) {
279 log::error!("cyclic dependency on {} for {:?}", name, from)
280 }
281 }
282 }
283
284 for dep in &krate.deps {
285 let to_crate_id = dep.crate_id;
286 if let Some(&to) = crates.get(&to_crate_id) {
287 if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
288 log::error!("cyclic dependency {:?} -> {:?}", from, to);
289 }
290 }
291 }
292 }
293 }
294 }
295 ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
296 let (public_deps, libproc_macro) =
297 sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load);
298
299 let mut cfg_options = CfgOptions::default();
300 cfg_options.extend(get_rustc_cfg_options(target));
301
302 let mut pkg_to_lib_crate = FxHashMap::default();
303
304 // Add test cfg for non-sysroot crates
305 cfg_options.insert_atom("test".into());
306 cfg_options.insert_atom("debug_assertions".into());
307
308 let mut pkg_crates = FxHashMap::default();
309
310 // Next, create crates for each package, target pair
311 for pkg in cargo.packages() {
312 let mut lib_tgt = None;
313 for &tgt in cargo[pkg].targets.iter() {
314 if let Some(crate_id) = add_target_crate_root(
315 &mut crate_graph,
316 &cargo[pkg],
317 &cargo[tgt],
318 &cfg_options,
319 proc_macro_client,
320 load,
321 ) {
322 if cargo[tgt].kind == TargetKind::Lib {
323 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
324 pkg_to_lib_crate.insert(pkg, crate_id);
325 }
326 if cargo[tgt].is_proc_macro {
327 if let Some(proc_macro) = libproc_macro {
328 if let Err(_) = crate_graph.add_dep(
329 crate_id,
330 CrateName::new("proc_macro").unwrap(),
331 proc_macro,
332 ) {
333 log::error!(
334 "cyclic dependency on proc_macro for {}",
335 &cargo[pkg].name
336 )
337 }
338 }
339 }
340
341 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
342 }
343 }
344
345 // Set deps to the core, std and to the lib target of the current package
346 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
347 if let Some((to, name)) = lib_tgt.clone() {
348 // For root projects with dashes in their name,
349 // cargo metadata does not do any normalization,
350 // so we do it ourselves currently
351 let name = CrateName::normalize_dashes(&name);
352 if to != from && crate_graph.add_dep(from, name, to).is_err() {
353 log::error!(
354 "cyclic dependency between targets of {}",
355 &cargo[pkg].name
356 )
357 }
358 }
359 for (name, krate) in public_deps.iter() {
360 if let Err(_) = crate_graph.add_dep(from, name.clone(), *krate) {
361 log::error!(
362 "cyclic dependency on {} for {}",
363 name,
364 &cargo[pkg].name
365 )
366 }
367 }
368 }
369 }
370
371 // Now add a dep edge from all targets of upstream to the lib
372 // target of downstream.
373 for pkg in cargo.packages() {
374 for dep in cargo[pkg].dependencies.iter() {
375 let name = CrateName::new(&dep.name).unwrap();
376 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
377 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
378 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
379 log::error!(
380 "cyclic dependency {} -> {}",
381 &cargo[pkg].name,
382 &cargo[dep.pkg].name
383 )
384 }
385 }
386 }
387 }
388 }
389
390 let mut rustc_pkg_crates = FxHashMap::default();
391
392 // If the user provided a path to rustc sources, we add all the rustc_private crates
393 // and create dependencies on them for the crates in the current workspace
394 if let Some(rustc_workspace) = rustc {
395 for pkg in rustc_workspace.packages() {
396 for &tgt in rustc_workspace[pkg].targets.iter() {
397 if rustc_workspace[tgt].kind != TargetKind::Lib {
398 continue;
399 }
400 // Exclude alloc / core / std
401 if rustc_workspace[tgt]
402 .root
403 .components()
404 .any(|c| c == Component::Normal("library".as_ref()))
405 {
406 continue;
407 }
408
409 if let Some(crate_id) = add_target_crate_root(
410 &mut crate_graph,
411 &rustc_workspace[pkg],
412 &rustc_workspace[tgt],
413 &cfg_options,
414 proc_macro_client,
415 load,
416 ) {
417 pkg_to_lib_crate.insert(pkg, crate_id);
418 // Add dependencies on the core / std / alloc for rustc
419 for (name, krate) in public_deps.iter() {
420 if let Err(_) =
421 crate_graph.add_dep(crate_id, name.clone(), *krate)
422 {
423 log::error!(
424 "cyclic dependency on {} for {}",
425 name,
426 &cargo[pkg].name
427 )
428 }
429 }
430 rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
431 }
432 }
433 }
434 // Now add a dep edge from all targets of upstream to the lib
435 // target of downstream.
436 for pkg in rustc_workspace.packages() {
437 for dep in rustc_workspace[pkg].dependencies.iter() {
438 let name = CrateName::new(&dep.name).unwrap();
439 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
440 for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
441 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
442 log::error!(
443 "cyclic dependency {} -> {}",
444 &rustc_workspace[pkg].name,
445 &rustc_workspace[dep.pkg].name
446 )
447 }
448 }
449 }
450 }
451 }
452
453 // Add dependencies for all the crates of the current workspace to rustc_private libraries
454 for dep in rustc_workspace.packages() {
455 let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
456
457 if let Some(&to) = pkg_to_lib_crate.get(&dep) {
458 for pkg in cargo.packages() {
459 if !cargo[pkg].is_member {
460 continue;
461 }
462 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
463 if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
464 log::error!(
465 "cyclic dependency {} -> {}",
466 &cargo[pkg].name,
467 &rustc_workspace[dep].name
468 )
469 }
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477 if crate_graph.patch_cfg_if() {
478 log::debug!("Patched std to depend on cfg-if")
479 } else {
480 log::debug!("Did not patch std to depend on cfg-if")
481 }
482 crate_graph
483 }
484}
485
486fn add_target_crate_root(
487 crate_graph: &mut CrateGraph,
488 pkg: &cargo_workspace::PackageData,
489 tgt: &cargo_workspace::TargetData,
490 cfg_options: &CfgOptions,
491 proc_macro_client: &ProcMacroClient,
492 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
493) -> Option<CrateId> {
494 let root = tgt.root.as_path();
495 if let Some(file_id) = load(root) {
496 let edition = pkg.edition;
497 let cfg_options = {
498 let mut opts = cfg_options.clone();
499 for feature in pkg.features.iter() {
500 opts.insert_key_value("feature".into(), feature.into());
501 }
502 opts.extend(pkg.cfgs.iter().cloned());
503 opts
504 };
505 let mut env = Env::default();
506 if let Some(out_dir) = &pkg.out_dir {
507 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
508 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
509 env.set("OUT_DIR", out_dir);
510 }
511 }
512 let proc_macro = pkg
513 .proc_macro_dylib_path
514 .as_ref()
515 .map(|it| proc_macro_client.by_dylib_path(&it))
516 .unwrap_or_default();
517
518 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
519 let crate_id = crate_graph.add_crate_root(
520 file_id,
521 edition,
522 Some(display_name),
523 cfg_options,
524 env,
525 proc_macro.clone(),
526 );
527
528 return Some(crate_id);
529 }
530 None
531}
532fn sysroot_to_crate_graph(
533 crate_graph: &mut CrateGraph,
534 sysroot: &Sysroot,
535 target: Option<&str>,
536 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
537) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
538 let mut cfg_options = CfgOptions::default();
539 cfg_options.extend(get_rustc_cfg_options(target));
540 let sysroot_crates: FxHashMap<_, _> = sysroot
541 .crates()
542 .filter_map(|krate| {
543 let file_id = load(&sysroot[krate].root)?;
544
545 let env = Env::default();
546 let proc_macro = vec![];
547 let name = CrateName::new(&sysroot[krate].name)
548 .expect("Sysroot crates' names do not contain dashes");
549 let crate_id = crate_graph.add_crate_root(
550 file_id,
551 Edition::Edition2018,
552 Some(name.into()),
553 cfg_options.clone(),
554 env,
555 proc_macro,
556 );
557 Some((krate, crate_id))
558 })
559 .collect();
560
561 for from in sysroot.crates() {
562 for &to in sysroot[from].deps.iter() {
563 let name = CrateName::new(&sysroot[to].name).unwrap();
564 if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
565 if let Err(_) = crate_graph.add_dep(from, name, to) {
566 log::error!("cyclic dependency between sysroot crates")
567 }
568 }
569 }
570 }
571
572 let public_deps = sysroot
573 .public_deps()
574 .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
575 .collect::<Vec<_>>();
576
577 let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
578 (public_deps, libproc_macro)
579}
580
581fn get_rustc_cfg_options(target: Option<&str>) -> Vec<CfgFlag> {
582 let mut res = Vec::new();
583
584 // Some nightly-only cfgs, which are required for stdlib
585 res.push(CfgFlag::Atom("target_thread_local".into()));
586 for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() {
587 for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() {
588 res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
589 }
590 }
591
592 let rustc_cfgs = {
593 let mut cmd = Command::new(toolchain::rustc());
594 cmd.args(&["--print", "cfg", "-O"]);
595 if let Some(target) = target {
596 cmd.args(&["--target", target]);
597 }
598 utf8_stdout(cmd)
599 };
600
601 match rustc_cfgs {
602 Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())),
603 Err(e) => log::error!("failed to get rustc cfgs: {:#}", e),
604 }
605
606 res
607}