use super::*;
use crate::{dag::Dag, log, CrateId};
use cargo_metadata::{Metadata, Package};
use clap::Parser;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Parser)]
pub struct TraceCmd {
#[allow(missing_docs)]
#[clap(flatten)]
cargo_args: super::CargoArgs,
#[clap(long)]
show_source: bool,
#[clap(long)]
show_version: bool,
#[clap(long, default_value = " -> ")]
path_delimiter: String,
#[clap(long)]
unique_versions: bool,
#[clap(index(1))]
from: String,
#[clap(index(2))]
to: String,
}
impl TraceCmd {
pub fn run(&self, _global: &GlobalArgs) {
let meta = self.cargo_args.load_metadata().expect("Loads metadata");
let (dag, index) = Self::build_dag(meta).expect("Builds dependency graph");
let lookup = |id: &str| {
index
.get(id)
.unwrap_or_else(|| panic!("Could not find crate {id} in the metadata"))
};
let froms = index
.iter()
.filter(|(_id, krate)| krate.name == self.from)
.map(|(id, _)| id)
.collect::<Vec<_>>();
if froms.is_empty() {
panic!("Could not find crate {} in the left dependency graph", self.from);
}
let tos = index
.iter()
.filter(|(_id, krate)| krate.name == self.to)
.map(|(id, _)| id)
.collect::<Vec<_>>();
if tos.is_empty() {
panic!("Could not find crate {} in the right dependency graph", self.to);
}
log::info!(
"No version or features specified: Checking all {} possibly distinct paths",
froms.len() * tos.len()
);
let mut paths = BTreeSet::new();
for from in froms.iter() {
for to in tos.iter() {
if let Some(path) = dag.any_path(from, to) {
paths.insert(path);
}
}
}
if paths.is_empty() {
panic!("No path found");
}
log::info!("Found {} distinct paths", paths.len());
let delimiter = self.path_delimiter.replace("\\n", "\n").replace("\\t", "\t");
for path in paths {
let mut out = String::new();
let mut is_first = true;
path.for_each(|id| {
let krate = lookup(id);
if !is_first {
out.push_str(&delimiter);
}
is_first = false;
out.push_str(&krate.name);
if self.show_version {
out.push_str(&format!(" v{}", krate.version));
}
if self.show_source {
if let Some(source) = krate.source.as_ref() {
out.push_str(&format!(" ({})", source.repr));
} else {
out.push_str(" (local)");
}
}
});
println!("{out}");
}
}
fn build_dag(meta: Metadata) -> Result<(Dag<CrateId>, BTreeMap<CrateId, Package>), String> {
let mut dag = Dag::new();
let mut index = BTreeMap::new();
for pkg in meta.packages.clone() {
let id = pkg.id.to_string();
dag.add_node(id.clone());
index.insert(pkg.id.to_string(), pkg.clone());
for dep in pkg.dependencies.iter() {
if let Some(dep) = resolve_dep(&pkg, dep, &meta) {
let dep = dep.pkg; let did = dep.id.to_string();
dag.add_edge(id.clone(), did);
}
}
}
Ok((dag, index))
}
}