use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use cargo_metadata::{Metadata, MetadataCommand};
use crate::package_id_info::PackageIdInfo;
use crate::package_source::PackageSource;
pub struct CargoMeta {
metadata: Metadata,
}
impl CargoMeta {
pub fn new(registry_path: &Path) -> Result<Self> {
let manifest_path = if registry_path.ends_with("Cargo.toml") {
PathBuf::from(registry_path)
} else {
registry_path.join("Cargo.toml")
};
let metadata = MetadataCommand::new()
.manifest_path(manifest_path)
.exec()
.context("Cannot get metadata for manifest: {manifest_path}")?;
Ok(Self { metadata })
}
pub fn registry_path(&self) -> Option<PathBuf> {
self.metadata.packages.iter().find_map(|pkg| {
let registry_path = pkg.manifest_path.to_string();
if registry_path.contains(".cargo/") {
PathBuf::from(registry_path)
.parent()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.filter(|p| {
p.to_str()
.map(|s| s.contains(".cargo/registry/src/index.crates.io-"))
== Some(true)
})
} else {
None
}
})
}
pub fn get_dependency_info(&self, crate_name: &str) -> HashMap<String, PackageIdInfo> {
let mut dep_info = HashMap::new();
for (target_name, deps) in self.workspace_member_dependencies() {
if let Some(dep) = deps.into_iter().find(|d| d.name == crate_name) {
dep_info.insert(target_name, dep);
}
}
dep_info
}
pub fn workspace_member_dependencies(&self) -> HashMap<String, Vec<PackageIdInfo>> {
if let Some(resolve) = &self.metadata.resolve {
let workspace_members = self.metadata.workspace_members.clone();
let mut packages = HashMap::new();
for node in &resolve.nodes {
if workspace_members.contains(&node.id) {
let Some(info) = PackageIdInfo::from_package_id(&node.id) else {
continue;
};
let mut deps = vec![];
for dep in &node.dependencies {
if let Some(pkg_dep) = PackageIdInfo::from_package_id(dep)
&& pkg_dep.source != PackageSource::Path
{
deps.push(pkg_dep);
}
}
packages.insert(info.name, deps);
}
}
packages
} else {
eprintln!(
"[ERROR] Metadata is not resolved for: {}",
self.metadata.workspace_root
);
HashMap::default()
}
}
pub fn workspace_nested_packages(&self) -> Vec<PackageIdInfo> {
if let Some(resolve) = &self.metadata.resolve {
let workspace_members = self.metadata.workspace_members.clone();
let mut workspace_dependencies = HashMap::new();
for node in &resolve.nodes {
if workspace_members.contains(&node.id) {
let mut deps = vec![];
for pkg_id in &node.dependencies {
let Some(pkg_dep) = PackageIdInfo::parse_source(pkg_id) else {
continue;
};
if pkg_dep != PackageSource::Path {
deps.push(pkg_id.clone());
}
}
workspace_dependencies.insert(node.id.clone(), deps);
}
}
let mut packages = vec![];
'next_node: for node in &resolve.nodes {
for (target, deps) in &workspace_dependencies {
if *target == node.id {
continue 'next_node;
}
if deps.contains(&node.id) {
continue 'next_node;
}
}
if let Some(pkg_info) = PackageIdInfo::from_package_id(&node.id) {
packages.push(pkg_info)
}
}
packages
} else {
eprintln!(
"[ERROR] Metadata is not resolved for: {}",
self.metadata.workspace_root
);
vec![]
}
}
}