pub mod lint;
pub mod trace;
use cargo_metadata::{Dependency, Metadata, MetadataCommand, Package, Resolve};
#[derive(Debug, clap::Parser)]
#[command(author, version, about, long_about = None)]
pub struct Command {
#[clap(subcommand)]
subcommand: SubCommand,
#[clap(long, global = true)]
quiet: bool,
}
#[derive(Debug, clap::Subcommand)]
enum SubCommand {
Trace(trace::TraceCmd),
Lint(lint::LintCmd),
}
impl Command {
pub fn run(&self) {
if self.quiet {
log::set_max_level(log::LevelFilter::Error);
} else {
log::set_max_level(log::LevelFilter::Info);
}
match &self.subcommand {
SubCommand::Trace(cmd) => cmd.run(),
SubCommand::Lint(cmd) => cmd.run(),
}
}
}
#[derive(Debug, clap::Parser)]
pub struct CargoArgs {
#[arg(long, global = true, default_value = "Cargo.toml")]
pub manifest_path: std::path::PathBuf,
#[clap(long, global = true)]
pub workspace: bool,
#[clap(long, global = true)]
pub offline: bool,
#[clap(long, global = true)]
pub locked: bool,
#[clap(long, global = true)]
pub all_features: bool,
}
impl CargoArgs {
pub fn load_metadata(&self) -> Result<Metadata, String> {
let mut cmd = MetadataCommand::new();
let manifest_path = if self.manifest_path.is_dir() {
self.manifest_path.join("Cargo.toml")
} else {
self.manifest_path.clone()
};
log::debug!("Manifest at: {:?}", manifest_path);
cmd.manifest_path(&manifest_path);
cmd.features(cargo_metadata::CargoOpt::AllFeatures);
if self.workspace {
cmd.no_deps();
}
if self.offline {
cmd.other_options(vec!["--offline".to_string()]);
}
if self.locked {
cmd.other_options(vec!["--locked".to_string()]);
}
cmd.exec().map_err(|e| format!("Failed to load metadata: {e}"))
}
}
pub(crate) fn resolve_dep(
pkg: &Package,
dep: &Dependency,
meta: &Metadata,
) -> Option<RenamedPackage> {
match meta.resolve.as_ref() {
Some(resolve) => resolve_dep_from_graph(pkg, dep, (meta, resolve)),
None => resolve_dep_from_workspace(dep, meta),
}
}
pub(crate) fn resolve_dep_from_workspace(
dep: &Dependency,
meta: &Metadata,
) -> Option<RenamedPackage> {
for work in meta.workspace_packages() {
if work.name == dep.name {
let pkg = meta.packages.iter().find(|pkg| pkg.id == work.id).cloned();
return pkg.map(|pkg| RenamedPackage::new(pkg, dep.rename.clone(), dep.optional))
}
}
None
}
pub(crate) fn resolve_dep_from_graph(
pkg: &Package,
dep: &Dependency,
(meta, resolve): (&Metadata, &Resolve),
) -> Option<RenamedPackage> {
let dep_name = dep.rename.clone().unwrap_or(dep.name.clone()).replace('-', "_");
let resolved_pkg = resolve.nodes.iter().find(|node| node.id == pkg.id)?;
let resolved_dep_id = resolved_pkg.deps.iter().find(|node| node.name == dep_name)?;
let resolve_dep = meta.packages.iter().find(|pkg| pkg.id == resolved_dep_id.pkg)?;
Some(RenamedPackage::new(resolve_dep.clone(), dep.rename.clone(), dep.optional))
}
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq)]
pub struct RenamedPackage {
pub pkg: Package,
pub rename: Option<String>,
pub optional: bool,
}
impl RenamedPackage {
pub fn new(pkg: Package, rename: Option<String>, optional: bool) -> Self {
Self { pkg, rename, optional }
}
pub fn name(&self) -> String {
self.rename.clone().unwrap_or(self.pkg.name.clone())
}
pub fn display_name(&self) -> String {
match &self.rename {
Some(rename) => format!("{} (renamed from {})", rename, self.pkg.name),
None => self.pkg.name.clone(),
}
}
}
impl Ord for RenamedPackage {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.pkg.id.cmp(&other.pkg.id)
}
}
impl PartialOrd for RenamedPackage {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}