1#![allow(dead_code)]
2
3mod package_name;
4pub use package_name::PackageName;
5
6mod package;
7pub use package::Package;
8
9mod error;
10pub use error::PackageError;
11
12use std::io::Write;
13use std::{
14 collections::{HashMap, HashSet},
15 path::PathBuf,
16};
17
18use cargo_metadata::Metadata;
19use semver::Version;
20use tracing::{debug, instrument};
21
22use crate::{ReadToml, Result, VersionLocation, display_path};
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct Packages {
26 root_manifest_path: PathBuf,
28 root_package: Option<PackageName>,
30
31 root_version: Option<Version>,
33
34 default_members: HashSet<PackageName>,
36
37 packages: HashMap<PackageName, Package<ReadToml>>,
39
40 workspace_package: Option<Package<ReadToml>>,
42}
43
44impl AsRef<HashMap<PackageName, Package<ReadToml>>> for Packages {
45 fn as_ref(&self) -> &HashMap<PackageName, Package<ReadToml>> {
46 &self.packages
47 }
48}
49
50impl Packages {
51 pub fn new<P, N>(
52 path: PathBuf,
53 packages: &[P],
54 root_package: Option<&P>,
55 default_members: Vec<N>,
56 ) -> Result<Self>
57 where
58 P: Into<Package<ReadToml>> + Clone,
59 N: ToString + Clone,
60 {
61 let workspace_package = Package::workspace_package(&path).ok();
62 let mut ret = Self {
63 root_manifest_path: path,
64 root_package: None,
65 root_version: None,
66 packages: HashMap::from_iter(packages.iter().map(|package| {
67 let package: Package<ReadToml> = package.clone().into();
68 (package.name().clone(), package)
69 })),
70 default_members: HashSet::from_iter(
71 default_members.iter().map(|n| PackageName(n.to_string())),
72 ),
73 workspace_package,
74 };
75 if let Some(root_package) = root_package {
76 let root_package: Package<ReadToml> = root_package.clone().into();
77 ret.root_package = ret.set_root_package_name(root_package.name()).cloned();
78 ret.root_version = Some(root_package.version().clone());
79 }
80 Ok(ret)
81 }
82
83 #[instrument()]
84 pub fn cargo_file_path(&self) -> &PathBuf {
85 debug!("Fetching cargo path");
86 &self.root_manifest_path
87 }
88 pub fn drop_root_package_name(&mut self) {
89 self.root_package = None
90 }
91
92 pub fn workspace_default_members(&self) -> HashSet<&PackageName> {
93 self.packages()
94 .keys()
95 .filter(|&p| self.default_members.contains(p))
96 .collect()
97 }
98
99 pub fn workspace_members(&self) -> HashSet<&PackageName> {
100 self.packages.keys().collect()
101 }
102
103 pub fn set_root_package_name<'name>(
107 &mut self,
108 package_name: &'name PackageName,
109 ) -> Option<&'name PackageName> {
110 self.try_set_root_package_name(package_name).ok()
111 }
112
113 pub fn try_set_root_package_name<'name>(
114 &mut self,
115 package_name: &'name PackageName,
116 ) -> miette::Result<&'name PackageName> {
117 if package_name.is_empty() {
118 Err(PackageError::PackageNameNotProvided)?
119 }
120 match self.packages.contains_key(package_name) {
121 true => {
122 self.root_package = Some(package_name.clone());
123 Ok(package_name)
124 }
125 false => Err(PackageError::PackageNameNotFound(package_name.clone()))?,
126 }
127 }
128
129 pub fn root_package_name_unchecked(&self) -> Option<&PackageName> {
131 self.root_package.as_ref()
132 }
133
134 pub fn root_package_name(&mut self) -> Option<&PackageName> {
136 let root_name = self.root_package.clone()?;
137 if self.packages.contains_key(&root_name) {
138 self.root_package.as_ref()
139 } else {
140 self.drop_root_package_name();
141 None
142 }
143 }
144
145 pub fn get_root_package(&self) -> Option<&Package<ReadToml>> {
147 let root_name = self.root_package_name_unchecked()?;
148 self.packages.get(root_name)
149 }
150
151 pub fn get_root_package_mut(&mut self) -> Option<&mut Package<ReadToml>> {
153 let root_name = self.root_package_name_unchecked()?.clone();
154 self.packages.get_mut(&root_name)
155 }
156
157 pub fn get_root_package_owned(&self) -> Option<Package<ReadToml>> {
159 let root_name = self.root_package_name_unchecked()?;
160 self.packages.get(root_name).cloned()
161 }
162
163 pub fn get_package(&self, package_name: &PackageName) -> Option<&Package<ReadToml>> {
165 self.packages.get(package_name)
166 }
167
168 pub fn get_package_mut(
170 &mut self,
171 package_name: &PackageName,
172 ) -> Option<&mut Package<ReadToml>> {
173 self.packages.get_mut(package_name)
174 }
175
176 pub fn get_package_owned(&self, package_name: &PackageName) -> Option<Package<ReadToml>> {
178 self.packages.get(package_name).cloned()
179 }
180
181 pub fn packages(&self) -> &HashMap<PackageName, Package<ReadToml>> {
182 &self.packages
183 }
184
185 pub fn package_set(&self) -> HashSet<&Package<ReadToml>> {
186 self.packages.values().collect::<HashSet<_>>()
187 }
188
189 pub fn set_cargo_file_path(&mut self, cargo_file: PathBuf) {
190 self.root_manifest_path = cargo_file;
191 }
192
193 pub(crate) fn get_root_package_version(&self) -> Option<Version> {
194 self.get_root_package().map(|rp| rp.version().clone())
195 }
196
197 pub fn root_version(&self) -> Result<Version, PackageError> {
206 let error_no_root_package = PackageError::NoRootVersion;
207 if self.root_version.is_some() {
208 return Ok(self
209 .root_version
210 .as_ref()
211 .expect("There is a root version")
212 .clone());
213 };
214
215 if let Some(root_package_name) = &self.root_package {
217 let root_package = self.packages.get(root_package_name);
218 if let Some(root_package) = root_package {
219 return Ok(root_package.version().clone());
220 };
221 };
222
223 if let Some(workspace_package) = &self.workspace_package {
224 match VersionLocation::WorkspacePackage.get_version(workspace_package.cargo_file()) {
226 Ok(v) => return Ok(v),
227 Err(e) => match e.kind() {
228 crate::VersionLocationErrorKind::SetByWorkspace => (),
229 crate::VersionLocationErrorKind::NotFound(_) => (),
230 crate::VersionLocationErrorKind::PackageNotFound => unreachable!(),
231 crate::VersionLocationErrorKind::WorkspaceNotFound => {
232 todo!("Workspace Not Found")
233 }
234 crate::VersionLocationErrorKind::ItemInvalid(_) => (),
235 crate::VersionLocationErrorKind::SemverError(_) => (),
236 },
237 };
238 }
239
240 Err(error_no_root_package)
241 }
242
243 pub fn workspace_package(&mut self) -> Option<&mut Package<ReadToml>> {
244 self.workspace_package.as_mut()
245 }
246}
247
248impl Packages {
249 pub fn display_tree(&self) -> String {
250 let mut ret_string = Vec::new();
251 let root_package = self.root_package.as_ref();
252 let path_base = self.cargo_file_path().parent().unwrap();
253 let make_relative = |package: &Package<ReadToml>| {
254 PathBuf::new()
255 .join(".")
256 .join(
257 package
258 .manifest_path()
259 .parent()
260 .unwrap()
261 .strip_prefix(path_base)
262 .unwrap(),
263 )
264 .as_os_str()
265 .to_string_lossy()
266 .into_owned()
267 };
268 let _ = writeln!(
269 ret_string,
270 "Workspace root: {}",
271 path_base.as_os_str().to_str().unwrap()
272 );
273 if let Some(root) = root_package {
274 let package = self.get_package(root).unwrap();
275 let _ = writeln!(ret_string, "Root package: {root} {}", package.version(),);
276 }
277
278 if !self.default_members.is_empty() {
279 let _ = writeln!(
280 ret_string,
281 "Default members: {:?}",
282 self.default_members
283 .iter()
284 .map(|n| n.to_string())
285 .collect::<Vec<_>>()
286 );
287 }
288
289 let _ = writeln!(ret_string);
290
291 if let Some(root) = root_package {
292 let _ = writeln!(ret_string, "{root}");
293 } else {
294 let _ = writeln!(ret_string, "Members:");
295 }
296
297 let mut items = self.packages.iter().collect::<Vec<_>>();
298 items.sort_by_key(|(n, _)| n.0.as_str());
299 let last = items.last().cloned();
300
301 for (name, package) in items {
302 if Some(name) == root_package {
303 continue;
304 }
305 if Some((name, package)) == last {
306 let _ = writeln!(
307 ret_string,
308 "└─ {name} {}: {}",
309 package.version(),
310 make_relative(package)
311 );
312 } else {
313 let _ = writeln!(
314 ret_string,
315 "├─ {name} {}: {}",
316 package.version(),
317 make_relative(package)
318 );
319 }
320 }
321 String::from_utf8(ret_string).expect("Chars is valid utf-8")
322 }
323}
324impl From<&Metadata> for Packages {
325 #[track_caller]
326 #[instrument(skip_all)]
327 fn from(metadata: &Metadata) -> Self {
328 let root_path = metadata.workspace_root.join("Cargo.toml");
329 tracing::debug!(
330 "Constructing Packages from Metadata: {}",
331 display_path!(root_path)
332 );
333
334 let default_members = metadata
335 .workspace_default_packages()
336 .iter()
337 .map(|p| p.name.clone())
338 .collect::<Vec<_>>();
339 tracing::trace!("Default members {:?}", default_members);
340 Self::new(
341 root_path.into(),
342 &metadata.packages,
343 metadata.root_package(),
344 default_members,
345 )
346 .expect("From cargo metadata")
347 }
348}