mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 22:30:03 +00:00
Fix error reporting in main and move all arg validation into fn args::parse
(#585)
* Fix reporting parsing error from `args::parse` Report it in `args::parse` by using `Args::command().error(...).exit()` instead of returning `BinstallError`. * Rm unused variant `BinstallError::OverrideOptionUsedWithMultiInstall` * Refactor: Move `strategies` validation into `args::parse` * Rm unused variant `BinstallError::InvalidStrategies` * Add new unit test `args::test::verify_cli` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
b564b8ac4e
commit
a69db83aa6
4 changed files with 114 additions and 122 deletions
|
@ -7,14 +7,14 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use binstalk::{
|
use binstalk::{
|
||||||
errors::BinstallError,
|
|
||||||
helpers::remote::tls::Version,
|
helpers::remote::tls::Version,
|
||||||
manifests::cargo_toml_binstall::PkgFmt,
|
manifests::cargo_toml_binstall::PkgFmt,
|
||||||
ops::resolve::{CrateName, VersionReqExt},
|
ops::resolve::{CrateName, VersionReqExt},
|
||||||
};
|
};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{error::ErrorKind, CommandFactory, Parser, ValueEnum};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
|
use strum::EnumCount;
|
||||||
use strum_macros::EnumCount;
|
use strum_macros::EnumCount;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -42,7 +42,7 @@ pub struct Args {
|
||||||
help_heading = "Package selection",
|
help_heading = "Package selection",
|
||||||
value_name = "crate[@version]",
|
value_name = "crate[@version]",
|
||||||
required_unless_present_any = ["version", "help"],
|
required_unless_present_any = ["version", "help"],
|
||||||
)]
|
)]
|
||||||
pub crate_names: Vec<CrateName>,
|
pub crate_names: Vec<CrateName>,
|
||||||
|
|
||||||
/// Package version to install.
|
/// Package version to install.
|
||||||
|
@ -309,7 +309,7 @@ pub enum Strategy {
|
||||||
Compile,
|
Compile,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse() -> Result<Args, BinstallError> {
|
pub fn parse() -> Args {
|
||||||
// Filter extraneous arg when invoked by cargo
|
// Filter extraneous arg when invoked by cargo
|
||||||
// `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"]
|
// `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"]
|
||||||
// `cargo binstall --help` gives ["/home/ryan/.cargo/bin/cargo-binstall", "binstall", "--help"]
|
// `cargo binstall --help` gives ["/home/ryan/.cargo/bin/cargo-binstall", "binstall", "--help"]
|
||||||
|
@ -335,6 +335,9 @@ pub fn parse() -> Result<Args, BinstallError> {
|
||||||
opts.log_level = LevelFilter::Off;
|
opts.log_level = LevelFilter::Off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure no conflict
|
||||||
|
let mut command = Args::command();
|
||||||
|
|
||||||
if opts.crate_names.len() > 1 {
|
if opts.crate_names.len() > 1 {
|
||||||
let option = if opts.version_req.is_some() {
|
let option = if opts.version_req.is_some() {
|
||||||
"version"
|
"version"
|
||||||
|
@ -345,9 +348,95 @@ pub fn parse() -> Result<Args, BinstallError> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !option.is_empty() {
|
if !option.is_empty() {
|
||||||
return Err(BinstallError::OverrideOptionUsedWithMultiInstall { option });
|
command
|
||||||
|
.error(
|
||||||
|
ErrorKind::ArgumentConflict,
|
||||||
|
format_args!(
|
||||||
|
r#"override option used with multi package syntax.
|
||||||
|
You cannot use --{option} and specify multiple packages at the same time. Do one or the other."#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(opts)
|
// Check strategies for duplicates
|
||||||
|
let mut new_dup_strategy_err = || {
|
||||||
|
command.error(
|
||||||
|
ErrorKind::TooManyValues,
|
||||||
|
"--strategies should not contain duplicate strategy",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if opts.strategies.len() > Strategy::COUNT {
|
||||||
|
// If len of strategies is larger than number of variants of Strategy,
|
||||||
|
// then there must be duplicates by pigeon hole principle.
|
||||||
|
new_dup_strategy_err().exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether specific variant of Strategy is present
|
||||||
|
let mut is_variant_present = [false; Strategy::COUNT];
|
||||||
|
|
||||||
|
for strategy in &opts.strategies {
|
||||||
|
let index = *strategy as u8 as usize;
|
||||||
|
if is_variant_present[index] {
|
||||||
|
new_dup_strategy_err().exit()
|
||||||
|
} else {
|
||||||
|
is_variant_present[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default strategies if empty
|
||||||
|
if opts.strategies.is_empty() {
|
||||||
|
opts.strategies = vec![
|
||||||
|
Strategy::CrateMetaData,
|
||||||
|
Strategy::QuickInstall,
|
||||||
|
Strategy::Compile,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out all disabled strategies
|
||||||
|
if !opts.disable_strategies.is_empty() {
|
||||||
|
// Since order doesn't matter, we can sort it and remove all duplicates
|
||||||
|
// to speedup checking.
|
||||||
|
opts.disable_strategies.sort_unstable();
|
||||||
|
opts.disable_strategies.dedup();
|
||||||
|
|
||||||
|
// disable_strategies.len() <= Strategy::COUNT, of which is faster
|
||||||
|
// to just use [Strategy]::contains rather than
|
||||||
|
// [Strategy]::binary_search
|
||||||
|
opts.strategies
|
||||||
|
.retain(|strategy| !opts.disable_strategies.contains(strategy));
|
||||||
|
|
||||||
|
if opts.strategies.is_empty() {
|
||||||
|
command
|
||||||
|
.error(ErrorKind::TooFewValues, "You have disabled all strategies")
|
||||||
|
.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free disable_strategies as it will not be used again.
|
||||||
|
opts.disable_strategies = Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that Strategy::Compile is specified as the last strategy
|
||||||
|
if opts.strategies[..(opts.strategies.len() - 1)].contains(&Strategy::Compile) {
|
||||||
|
command
|
||||||
|
.error(
|
||||||
|
ErrorKind::InvalidValue,
|
||||||
|
"Compile strategy must be the last one",
|
||||||
|
)
|
||||||
|
.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
Args::command().debug_assert()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ use binstalk_manifests::{
|
||||||
use crates_io_api::AsyncClient as CratesIoApiClient;
|
use crates_io_api::AsyncClient as CratesIoApiClient;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use miette::{miette, Result, WrapErr};
|
use miette::{miette, Result, WrapErr};
|
||||||
use strum::EnumCount;
|
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
@ -28,9 +27,21 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -> Result<()> {
|
pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -> Result<()> {
|
||||||
// Compute strategies
|
// Compute Resolvers
|
||||||
let (resolvers, cargo_install_fallback) =
|
let mut cargo_install_fallback = false;
|
||||||
compute_resolvers(args.strategies, args.disable_strategies)?;
|
|
||||||
|
let resolvers: Vec<_> = args
|
||||||
|
.strategies
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|strategy| match strategy {
|
||||||
|
Strategy::CrateMetaData => Some(GhCrateMeta::new as Resolver),
|
||||||
|
Strategy::QuickInstall => Some(QuickInstall::new as Resolver),
|
||||||
|
Strategy::Compile => {
|
||||||
|
cargo_install_fallback = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Compute paths
|
// Compute paths
|
||||||
let (install_path, cargo_roots, metadata, temp_dir) =
|
let (install_path, cargo_roots, metadata, temp_dir) =
|
||||||
|
@ -183,79 +194,6 @@ pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return (resolvers, cargo_install_fallback)
|
|
||||||
fn compute_resolvers(
|
|
||||||
mut strategies: Vec<Strategy>,
|
|
||||||
mut disable_strategies: Vec<Strategy>,
|
|
||||||
) -> Result<(Vec<Resolver>, bool), BinstallError> {
|
|
||||||
let dup_strategy_err =
|
|
||||||
BinstallError::InvalidStrategies("--strategies should not contain duplicate strategy");
|
|
||||||
|
|
||||||
if strategies.len() > Strategy::COUNT {
|
|
||||||
// If len of strategies is larger than number of variants of Strategy,
|
|
||||||
// then there must be duplicates by pigeon hole principle.
|
|
||||||
return Err(dup_strategy_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether specific variant of Strategy is present
|
|
||||||
let mut is_variant_present = [false; Strategy::COUNT];
|
|
||||||
|
|
||||||
for strategy in &strategies {
|
|
||||||
let index = *strategy as u8 as usize;
|
|
||||||
if is_variant_present[index] {
|
|
||||||
return Err(dup_strategy_err);
|
|
||||||
} else {
|
|
||||||
is_variant_present[index] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default strategies if empty
|
|
||||||
if strategies.is_empty() {
|
|
||||||
strategies = vec![
|
|
||||||
Strategy::CrateMetaData,
|
|
||||||
Strategy::QuickInstall,
|
|
||||||
Strategy::Compile,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out all disabled strategies
|
|
||||||
if !disable_strategies.is_empty() {
|
|
||||||
// Since order doesn't matter, we can sort it and remove all duplicates
|
|
||||||
// to speedup checking.
|
|
||||||
disable_strategies.sort_unstable();
|
|
||||||
disable_strategies.dedup();
|
|
||||||
|
|
||||||
// disable_strategies.len() <= Strategy::COUNT, of which is faster
|
|
||||||
// to just use [T]::contains rather than [T]::binary_search
|
|
||||||
strategies.retain(|strategy| !disable_strategies.contains(strategy));
|
|
||||||
|
|
||||||
if strategies.is_empty() {
|
|
||||||
return Err(BinstallError::InvalidStrategies(
|
|
||||||
"You have disabled all strategies",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cargo_install_fallback = *strategies.last().unwrap() == Strategy::Compile;
|
|
||||||
|
|
||||||
if cargo_install_fallback {
|
|
||||||
strategies.pop().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let resolvers = strategies
|
|
||||||
.into_iter()
|
|
||||||
.map(|strategy| match strategy {
|
|
||||||
Strategy::CrateMetaData => Ok(GhCrateMeta::new as Resolver),
|
|
||||||
Strategy::QuickInstall => Ok(QuickInstall::new as Resolver),
|
|
||||||
Strategy::Compile => Err(BinstallError::InvalidStrategies(
|
|
||||||
"Compile strategy must be the last one",
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, BinstallError>>()?;
|
|
||||||
|
|
||||||
Ok((resolvers, cargo_install_fallback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return (install_path, cargo_roots, metadata, temp_dir)
|
/// Return (install_path, cargo_roots, metadata, temp_dir)
|
||||||
fn compute_paths_and_load_manifests(
|
fn compute_paths_and_load_manifests(
|
||||||
roots: Option<PathBuf>,
|
roots: Option<PathBuf>,
|
||||||
|
|
|
@ -18,10 +18,7 @@ fn main() -> MainExit {
|
||||||
// This must be the very first thing to happen
|
// This must be the very first thing to happen
|
||||||
let jobserver_client = LazyJobserverClient::new();
|
let jobserver_client = LazyJobserverClient::new();
|
||||||
|
|
||||||
let args = match args::parse() {
|
let args = args::parse();
|
||||||
Ok(args) => args,
|
|
||||||
Err(err) => return MainExit::Error(err),
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.version {
|
if args.version {
|
||||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
|
@ -205,28 +205,6 @@ pub enum BinstallError {
|
||||||
)]
|
)]
|
||||||
SuperfluousVersionOption,
|
SuperfluousVersionOption,
|
||||||
|
|
||||||
/// An override option is used when multiple packages are to be installed.
|
|
||||||
///
|
|
||||||
/// This is raised when more than one package name is provided and any of:
|
|
||||||
///
|
|
||||||
/// - `--version`
|
|
||||||
/// - `--manifest-path`
|
|
||||||
/// - `--bin-dir`
|
|
||||||
/// - `--pkg-fmt`
|
|
||||||
/// - `--pkg-url`
|
|
||||||
///
|
|
||||||
/// is provided.
|
|
||||||
///
|
|
||||||
/// - Code: `binstall::conflict::overrides`
|
|
||||||
/// - Exit: 85
|
|
||||||
#[error("override option used with multi package syntax")]
|
|
||||||
#[diagnostic(
|
|
||||||
severity(error),
|
|
||||||
code(binstall::conflict::overrides),
|
|
||||||
help("You cannot use --{option} and specify multiple packages at the same time. Do one or the other.")
|
|
||||||
)]
|
|
||||||
OverrideOptionUsedWithMultiInstall { option: &'static str },
|
|
||||||
|
|
||||||
/// No binaries were found for the crate.
|
/// No binaries were found for the crate.
|
||||||
///
|
///
|
||||||
/// When installing, either the binaries are specified in the crate's Cargo.toml, or they're
|
/// When installing, either the binaries are specified in the crate's Cargo.toml, or they're
|
||||||
|
@ -304,14 +282,6 @@ pub enum BinstallError {
|
||||||
#[diagnostic(severity(error), code(binstall::SourceFilePath))]
|
#[diagnostic(severity(error), code(binstall::SourceFilePath))]
|
||||||
EmptySourceFilePath,
|
EmptySourceFilePath,
|
||||||
|
|
||||||
/// Invalid strategies configured.
|
|
||||||
///
|
|
||||||
/// - Code: `binstall::strategies`
|
|
||||||
/// - Exit: 93
|
|
||||||
#[error("Invalid strategies configured: {0}")]
|
|
||||||
#[diagnostic(severity(error), code(binstall::strategies))]
|
|
||||||
InvalidStrategies(&'static str),
|
|
||||||
|
|
||||||
/// Fallback to `cargo-install` is disabled.
|
/// Fallback to `cargo-install` is disabled.
|
||||||
///
|
///
|
||||||
/// - Code: `binstall::no_fallback_to_cargo_install`
|
/// - Code: `binstall::no_fallback_to_cargo_install`
|
||||||
|
@ -344,7 +314,6 @@ impl BinstallError {
|
||||||
VersionParse { .. } => 80,
|
VersionParse { .. } => 80,
|
||||||
VersionMismatch { .. } => 82,
|
VersionMismatch { .. } => 82,
|
||||||
SuperfluousVersionOption => 84,
|
SuperfluousVersionOption => 84,
|
||||||
OverrideOptionUsedWithMultiInstall { .. } => 85,
|
|
||||||
UnspecifiedBinaries => 86,
|
UnspecifiedBinaries => 86,
|
||||||
NoViableTargets => 87,
|
NoViableTargets => 87,
|
||||||
BinFileNotFound(_) => 88,
|
BinFileNotFound(_) => 88,
|
||||||
|
@ -352,7 +321,6 @@ impl BinstallError {
|
||||||
DuplicateSourceFilePath { .. } => 90,
|
DuplicateSourceFilePath { .. } => 90,
|
||||||
InvalidSourceFilePath { .. } => 91,
|
InvalidSourceFilePath { .. } => 91,
|
||||||
EmptySourceFilePath => 92,
|
EmptySourceFilePath => 92,
|
||||||
InvalidStrategies(..) => 93,
|
|
||||||
NoFallbackToCargoInstall => 94,
|
NoFallbackToCargoInstall => 94,
|
||||||
CrateContext(context) => context.err.exit_number(),
|
CrateContext(context) => context.err.exit_number(),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue