use cargo_metadata::{Dependency, Package, camino::Utf8PathBuf};
use cargo_toml::{Manifest, Workspace};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fs;
pub mod diff;
pub mod show;
#[derive(Debug, Clone)]
pub struct MemberDependency {
pub name: String,
pub manifest_path: Utf8PathBuf,
pub dependency: Dependency,
}
pub type PartitionedDependencies = (
BTreeMap<String, Vec<MemberDependency>>,
BTreeSet<String>,
BTreeMap<String, MemberDependency>,
);
pub fn partition_dependencies(
workspace: &Workspace,
selected: &[&Package],
aggressive: bool,
) -> anyhow::Result<PartitionedDependencies> {
let current_deps: HashSet<_> = workspace.dependencies.keys().cloned().collect();
let mut needed_deps: BTreeMap<String, Vec<MemberDependency>> = BTreeMap::new();
let mut ws_users: BTreeMap<String, Vec<MemberDependency>> = BTreeMap::new();
for &member in selected {
let content = fs::read_to_string(member.manifest_path.as_std_path())?;
let member_manifest: Manifest = Manifest::from_str(&content)?;
let mut inherited: HashSet<String> = HashSet::new();
for (name, dep) in member_manifest
.dependencies
.iter()
.chain(member_manifest.dev_dependencies.iter())
.chain(member_manifest.build_dependencies.iter())
{
if matches!(dep, cargo_toml::Dependency::Inherited(_)) {
inherited.insert(name.clone());
}
}
for dep in member.dependencies.iter().filter(|&dep| dep.path.is_none()) {
let md = MemberDependency {
name: member.name.to_string(),
manifest_path: member.manifest_path.clone(),
dependency: dep.clone(),
};
if inherited.contains(&dep.name) {
ws_users.entry(dep.name.clone()).or_default().push(md);
} else {
needed_deps.entry(dep.name.clone()).or_default().push(md);
}
}
}
needed_deps.retain(|name, members| {
members.sort_by(|a, b| a.name.cmp(&b.name));
let inherited_count = ws_users.get(name).map(|v| v.len()).unwrap_or(0);
!members.is_empty() && (members.len() + inherited_count) > 1
});
let (common, mut remove) = current_deps
.into_iter()
.partition::<BTreeSet<_>, _>(|name| {
needed_deps.contains_key(name) || ws_users.contains_key(name)
});
let mut inline: BTreeMap<String, MemberDependency> = BTreeMap::new();
if aggressive {
for name in &common {
let inherited_count = ws_users.get(name).map(|v| v.len()).unwrap_or(0);
let inline_count = needed_deps.get(name).map(|v| v.len()).unwrap_or(0);
if inherited_count == 1
&& inline_count == 0
&& let Some(mut users) = ws_users.remove(name)
&& let Some(md) = users.pop()
{
inline.insert(name.clone(), md);
remove.insert(name.clone());
}
}
}
Ok((needed_deps, remove, inline))
}