use std::{ffi::OsString, fmt::Write, fs, path::PathBuf};
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand};
use crate::{
config::{LogLevel, PackageFormat},
init_tracing_subscriber, package, parse_log_level, sign_outputs, util, SigningConfig,
};
mod config;
mod error;
mod signer;
use self::error::{Error, Result};
#[derive(Debug, Clone, Subcommand)]
enum Commands {
Signer(signer::Options),
}
#[derive(Parser, Debug)]
#[clap(
author,
version,
about,
bin_name("cargo-packager"),
propagate_version(true),
no_binary_name(true)
)]
pub(crate) struct Cli {
#[clap(short, long, global = true, action = ArgAction::Count)]
verbose: u8,
#[clap(short, long, global = true)]
quite: bool,
#[clap(short, long, value_enum, value_delimiter = ',')]
formats: Option<Vec<PackageFormat>>,
#[clap(short, long)]
config: Option<String>,
#[clap(short = 'k', long, env = "CARGO_PACKAGER_SIGN_PRIVATE_KEY")]
private_key: Option<String>,
#[clap(long, env = "CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD")]
password: Option<String>,
#[clap(short, long, value_delimiter = ',')]
pub(crate) packages: Option<Vec<String>>,
#[clap(short, long, alias = "out")]
out_dir: Option<PathBuf>,
#[clap(long)]
binaries_dir: Option<PathBuf>,
#[clap(short, long, group = "cargo-profile")]
release: bool,
#[clap(long, group = "cargo-profile")]
profile: Option<String>,
#[clap(long)]
manifest_path: Option<PathBuf>,
#[clap(long)]
target: Option<String>,
#[command(subcommand)]
command: Option<Commands>,
}
#[tracing::instrument(level = "trace", skip(cli))]
fn run_cli(cli: Cli) -> Result<()> {
tracing::trace!(cli= ?cli);
if let Some(command) = cli.command {
match command {
Commands::Signer(opts) => signer::command(opts)?,
}
return Ok(());
}
let configs = config::detect_configs(&cli)?;
if configs.is_empty() {
tracing::error!("Couldn't detect a valid configuration file or all configurations are disabled! Nothing to do here.");
std::process::exit(1);
}
let cli_out_dir = cli
.out_dir
.as_ref()
.map(|p| {
if p.exists() {
dunce::canonicalize(p).map_err(|e| Error::IoWithPath(p.clone(), e))
} else {
fs::create_dir_all(p).map_err(|e| Error::IoWithPath(p.clone(), e))?;
Ok(p.to_owned())
}
})
.transpose()?;
let private_key = match cli.private_key {
Some(path) if PathBuf::from(&path).exists() => Some(
fs::read_to_string(&path).map_err(|e| Error::IoWithPath(PathBuf::from(&path), e))?,
),
k => k,
};
let signing_config = private_key.map(|k| SigningConfig {
private_key: k,
password: cli.password,
});
let mut outputs = Vec::new();
let mut signatures = Vec::new();
for (config_dir, mut config) in configs {
tracing::trace!(config = ?config);
if let Some(dir) = &cli_out_dir {
config.out_dir.clone_from(dir)
}
if let Some(formats) = &cli.formats {
config.formats.replace(formats.clone());
}
if let Some(target_triple) = &cli.target {
config.target_triple.replace(target_triple.clone());
}
if config.log_level.is_none() && !cli.quite {
let level = match parse_log_level(cli.verbose) {
tracing::Level::ERROR => LogLevel::Error,
tracing::Level::WARN => LogLevel::Warn,
tracing::Level::INFO => LogLevel::Info,
tracing::Level::DEBUG => LogLevel::Debug,
tracing::Level::TRACE => LogLevel::Trace,
};
config.log_level.replace(level);
}
if let Some(path) = config_dir {
let parent = path
.parent()
.ok_or_else(|| crate::Error::ParentDirNotFound(path.clone()))?;
std::env::set_current_dir(parent)
.map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;
}
let mut packages = package(&config)?;
if let Some(signing_config) = &signing_config {
let s = sign_outputs(signing_config, &mut packages)?;
signatures.extend(s);
}
outputs.extend(packages);
}
let outputs = outputs
.into_iter()
.flat_map(|o| o.paths)
.collect::<Vec<_>>();
let len = outputs.len();
if len >= 1 {
let pluralised = if len == 1 { "package" } else { "packages" };
let mut printable_paths = String::new();
for path in outputs {
let _ = writeln!(printable_paths, " {}", util::display_path(path));
}
tracing::info!(
"Finished packaging {} {} at:\n{}",
len,
pluralised,
printable_paths
);
}
let len = signatures.len();
if len >= 1 {
let pluralised = if len == 1 { "signature" } else { "signatures" };
let mut printable_paths = String::new();
for path in signatures {
let _ = writeln!(printable_paths, " {}", util::display_path(path));
}
tracing::info!(
"Finished signing packages, {} {} at:\n{}",
len,
pluralised,
printable_paths
);
}
Ok(())
}
pub fn run<I, A>(args: I, bin_name: Option<String>)
where
I: IntoIterator<Item = A>,
A: Into<OsString> + Clone,
{
if let Err(e) = try_run(args, bin_name) {
tracing::error!("{}", e);
std::process::exit(1);
}
}
pub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>
where
I: IntoIterator<Item = A>,
A: Into<OsString> + Clone,
{
let cli = match &bin_name {
Some(bin_name) => Cli::command().bin_name(bin_name),
None => Cli::command(),
};
let matches = cli.get_matches_from(args);
let cli = Cli::from_arg_matches(&matches).map_err(|e| {
e.format(&mut match &bin_name {
Some(bin_name) => Cli::command().bin_name(bin_name),
None => Cli::command(),
})
})?;
if !cli.quite {
init_tracing_subscriber(cli.verbose);
if std::env::var_os("CARGO_TERM_COLOR").is_none() {
std::env::set_var("CARGO_TERM_COLOR", "always");
}
}
run_cli(cli)
}