1use cargo_metadata::{Dependency, Package, camino::Utf8PathBuf};
2use cargo_toml::{Manifest, Workspace};
3use std::collections::{BTreeMap, BTreeSet, HashSet};
4use std::fs;
5
6pub mod diff;
7pub mod show;
8
9#[derive(Debug, Clone)]
11pub struct MemberDependency {
12 pub name: String,
14 pub manifest_path: Utf8PathBuf,
16 pub dependency: Dependency,
18}
19
20pub type PartitionedDependencies = (
22 BTreeMap<String, Vec<MemberDependency>>,
23 BTreeSet<String>,
24 BTreeMap<String, MemberDependency>,
25);
26
27pub fn partition_dependencies(
28 workspace: &Workspace,
29 selected: &[&Package],
30 aggressive: bool,
31) -> anyhow::Result<PartitionedDependencies> {
32 let current_deps: HashSet<_> = workspace.dependencies.keys().cloned().collect();
33
34 let mut needed_deps: BTreeMap<String, Vec<MemberDependency>> = BTreeMap::new();
40 let mut ws_users: BTreeMap<String, Vec<MemberDependency>> = BTreeMap::new();
41
42 for &member in selected {
43 let content = fs::read_to_string(member.manifest_path.as_std_path())?;
44 let member_manifest: Manifest = Manifest::from_str(&content)?;
45 let mut inherited: HashSet<String> = HashSet::new();
46 for (name, dep) in member_manifest
47 .dependencies
48 .iter()
49 .chain(member_manifest.dev_dependencies.iter())
50 .chain(member_manifest.build_dependencies.iter())
51 {
52 if matches!(dep, cargo_toml::Dependency::Inherited(_)) {
53 inherited.insert(name.clone());
54 }
55 }
56
57 for dep in member.dependencies.iter() {
58 let is_inherited = inherited.contains(&dep.name);
59 if !is_inherited && dep.path.is_some() {
66 continue;
67 }
68 let md = MemberDependency {
69 name: member.name.to_string(),
70 manifest_path: member.manifest_path.clone(),
71 dependency: dep.clone(),
72 };
73 if is_inherited {
74 ws_users.entry(dep.name.clone()).or_default().push(md);
75 } else {
76 needed_deps.entry(dep.name.clone()).or_default().push(md);
77 }
78 }
79 }
80
81 needed_deps.retain(|name, members| {
86 members.sort_by(|a, b| a.name.cmp(&b.name));
87 let inherited_count = ws_users.get(name).map(|v| v.len()).unwrap_or(0);
88 !members.is_empty() && (members.len() + inherited_count) > 1
89 });
90
91 let (common, mut remove) = current_deps
94 .into_iter()
95 .partition::<BTreeSet<_>, _>(|name| {
96 needed_deps.contains_key(name) || ws_users.contains_key(name)
97 });
98
99 let mut inline: BTreeMap<String, MemberDependency> = BTreeMap::new();
103 if aggressive {
104 for name in &common {
105 let inherited_count = ws_users.get(name).map(|v| v.len()).unwrap_or(0);
106 let inline_count = needed_deps.get(name).map(|v| v.len()).unwrap_or(0);
107 if inherited_count == 1
108 && inline_count == 0
109 && let Some(mut users) = ws_users.remove(name)
110 && let Some(md) = users.pop()
111 {
112 inline.insert(name.clone(), md);
113 remove.insert(name.clone());
114 }
115 }
116 }
117
118 Ok((needed_deps, remove, inline))
119}