1use std::{
4 cmp::Ordering,
5 collections::{hash_map::Entry, HashMap, HashSet},
6};
7
8use cargo_metadata::{
9 camino::{Utf8Path, Utf8PathBuf},
10 Metadata, MetadataCommand,
11};
12use once_cell::sync::Lazy;
13use walkdir::WalkDir;
14
15use crate::{fs::ToRelative, Result};
16
17mod metadata;
18mod package;
19
20pub use self::{metadata::*, package::*};
21
22static WORKSPACES: Lazy<Vec<Metadata>> = Lazy::new(|| {
23 let current_dir = std::env::current_dir().unwrap();
24 let current_dir = Utf8PathBuf::try_from(current_dir).unwrap();
25 collect_workspaces(¤t_dir).unwrap()
26});
27
28pub fn current() -> &'static Metadata {
30 &WORKSPACES[0]
31}
32
33pub fn all() -> &'static [Metadata] {
35 &WORKSPACES
36}
37
38fn collect_workspaces(base_dir: &Utf8Path) -> Result<Vec<Metadata>> {
39 let mut workspaces = HashMap::new();
40 let mut target_dirs = HashSet::new();
41
42 let current_workspace = MetadataCommand::new().current_dir(base_dir).exec()?;
43 let current_workspace_root = ¤t_workspace.workspace_root;
44
45 let mut it = WalkDir::new(current_workspace_root)
46 .sort_by(
47 |a, b| match (a.file_type().is_file(), b.file_type().is_file()) {
50 (true, true) => a.file_name().cmp(b.file_name()),
51 (true, false) => Ordering::Less,
52 (false, true) => Ordering::Greater,
53 (false, false) => a.file_name().cmp(b.file_name()),
54 },
55 )
56 .into_iter();
57
58 while let Some(entry) = it.next() {
59 let entry = entry?;
60 let path = <&Utf8Path>::try_from(entry.path())?;
61
62 if entry.file_type().is_file() && path.file_name() == Some("Cargo.toml") {
64 tracing::debug!("Found manifest {}", path.to_relative());
65 let workspace = MetadataCommand::new().manifest_path(path).exec()?;
66 match workspaces.entry(workspace.workspace_root.clone()) {
67 Entry::Occupied(_e) => {}
68 Entry::Vacant(e) => {
69 if workspace.target_directory.is_dir() {
70 let target_dir = workspace.target_directory.canonicalize_utf8()?;
71 tracing::debug!(
72 "Found workspace {}",
73 workspace.workspace_root.to_relative()
74 );
75 target_dirs.insert(target_dir);
76 }
77 e.insert(workspace);
78 }
79 }
80 }
81
82 if entry.file_type().is_dir() && path.file_name() == Some(".git") {
84 tracing::debug!("Skipping git directory {}", path.to_relative());
85 it.skip_current_dir();
86 continue;
87 }
88
89 if entry.file_type().is_dir() && target_dirs.contains(&path.canonicalize_utf8()?) {
93 tracing::debug!("Skipping target directory {}", path.to_relative());
94 it.skip_current_dir();
95 continue;
96 }
97 }
98
99 let mut workspaces = workspaces.into_values().collect::<Vec<_>>();
102 workspaces.sort_by(|a, b| a.workspace_root.cmp(&b.workspace_root));
103
104 Ok(workspaces)
105}