use anyhow::{bail, Context, Result};
use cargo_metadata::{DependencyKind, Metadata, Package, Target};
use clap_cargo::{Features, Manifest, Workspace};
use heck::ToShoutyKebabCase;
use impls::Merge;
use std::{
collections::HashMap,
env,
ffi::OsString,
path::{Path, PathBuf},
process::Command,
};
mod cargo_bin;
mod cargo_build;
#[cfg(feature = "std")]
mod cmd;
pub mod impls;
pub use cargo_bin::*;
pub use cargo_build::*;
pub use impls::*;
#[cfg(feature = "std")]
pub use cmd::ToCmd;
#[derive(Default, Clone, Debug, PartialEq, Eq, clap::Args)]
#[non_exhaustive]
pub struct ClapCargo {
#[clap(flatten)]
pub features: Features,
#[clap(flatten)]
pub manifest: Manifest,
#[clap(flatten)]
pub workspace: Workspace,
#[clap(flatten)]
pub cargo_bin: CargoBin,
#[clap(flatten)]
pub cargo_build: CargoBuild,
#[clap(last = true, name = "CARGO_ARGS")]
pub slop: Vec<OsString>,
}
impl ClapCargo {
pub fn metadata(&self) -> Result<&Metadata> {
unsafe {
static mut METADATA: Option<Metadata> = None;
if METADATA.is_none() {
let mut metadata_cmd = self.manifest.metadata();
self.features.forward_metadata(&mut metadata_cmd);
METADATA = Some(metadata_cmd.exec()?);
}
METADATA
.as_ref()
.ok_or_else(|| anyhow::anyhow!("failed to read metadata"))
}
}
pub fn manifest_path(&self) -> Result<PathBuf> {
let manifest_path = self
.manifest
.manifest_path
.clone()
.unwrap_or_else(|| Path::new("./Cargo.toml").to_path_buf());
Ok(if manifest_path.is_relative() {
env::current_dir()?.join(manifest_path)
} else {
manifest_path
})
}
pub fn target_dir(&self) -> Result<PathBuf> {
Ok(self.metadata()?.target_directory.clone().into())
}
pub fn current_packages(&self) -> Result<Vec<&Package>> {
let meta = self.metadata()?;
Ok(self.workspace.partition_packages(meta).0)
}
pub fn packages(&self) -> Result<Vec<&Package>> {
Ok(self.metadata()?.packages.iter().collect::<Vec<&Package>>())
}
#[deprecated(note = "use add_args instead")]
pub fn add_cargo_args(&self, cmd: &mut Command) {
self.add_args(cmd);
}
pub fn get_deps(&self, p: &Package, dep_kind: DependencyKind) -> Result<Vec<&Package>> {
let packages = self
.metadata()?
.packages
.iter()
.map(|p| (format!("{}v{}", p.name, p.version), p))
.collect::<HashMap<String, &Package>>();
let edges = match dep_kind {
DependencyKind::Normal => "normal",
DependencyKind::Development => "dev",
DependencyKind::Build => "build",
_ => "all",
};
let stdout = Command::new("cargo")
.args([
"tree",
"--prefix",
"none",
"--edges",
edges,
"--manifest-path",
p.manifest_path.as_str(),
])
.output()
.with_context(|| format!("failed to run cargo tree on {}", p.name))?
.stdout;
let res = String::from_utf8(stdout)?
.lines()
.filter_map(|line| {
let s: Vec<&str> = line.split(' ').collect();
let package_id = format!("{}{}", s[0], s[1]);
let res = packages.get(&package_id).copied();
if let Some(r) = &res {
if r == &p {
return None;
}
}
res
})
.collect::<Vec<_>>();
Ok(res)
}
pub fn cargo_cmd(&self) -> Command {
let mut cmd = Command::new(self.cargo_bin.bin());
if cmd.get_program().eq_ignore_ascii_case("cargo") {
cmd.arg(format!("+{}", self.channel()));
}
if self.cargo_build.link_args || self.cargo_build.optimize {
cmd.env("RUSTFLAGS", "-C link-args=-s");
}
cmd
}
pub fn channel(&self) -> &str {
if self.cargo_build.optimize {
"nightly"
} else {
self.cargo_bin.channel()
}
}
#[cfg(feature = "std")]
pub fn build_cmd(&self) -> Command {
let mut cmd = self.cargo_cmd();
self.add_args(cmd.arg("build"));
cmd
}
pub fn find_package(&self, name: &str) -> Result<Option<&Package>> {
let mut found_close_pair: Option<&str> = None;
let package = self.packages()?.into_iter().find(|p| {
let res = p.name == name;
if !res && p.name.to_shouty_kebab_case() == name.to_shouty_kebab_case() {
found_close_pair = Some(&p.name);
};
res
});
if let (Some(similar_package), None) = (found_close_pair, package) {
bail!("Found similar package for {name} ~ {similar_package}");
}
Ok(package)
}
pub fn built_bin(&self, target: &Target) -> Result<PathBuf> {
self.target_dir().map(|target_dir| {
target_dir
.join("wasm32-unknown-unknown")
.join(self.cargo_build.profile())
.join(target.wasm_bin_name())
})
}
}
impl Merge for ClapCargo {
fn merge(&mut self, other: Self) {
let Self {
features,
manifest,
workspace,
cargo_bin,
cargo_build: build,
mut slop,
} = other;
self.features.merge(features);
self.manifest.merge(manifest);
self.workspace.merge(workspace);
self.cargo_bin.merge(cargo_bin);
self.cargo_build.merge(build);
self.slop.append(&mut slop);
}
}
impl Args for ClapCargo {
fn to_args(&self) -> Vec<OsString> {
let mut args = self.workspace.to_args();
args.extend(self.features.to_args());
args.extend(self.cargo_build.to_args());
args.extend(self.manifest.to_args());
args
}
}