mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 22:30:03 +00:00
feat: Impl new option --continue-on-failure
(#1559)
* feat: Impl new option `--continue-on-failure` Resolve #1548 Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new e2e-tests continue-on-failure Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm dup line ion `e2e-tests/live.sh` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix shellcheck Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `BinstallError::crate_errors` if `errors.len()` is 1 In that case, it should return `Some(Self::CrateContext(_))` instead of `Some(Self::Errors(_))` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more tests to `e2e-tests/continue-on-failure.sh` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Propagate crate errors on `confirm()` err Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Test having two errors in `e2e-tests/continue-on-failure.sh` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> --------- Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
f5da25cc56
commit
c08b8d232a
8 changed files with 365 additions and 60 deletions
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
io,
|
||||
fmt, io, ops,
|
||||
path::PathBuf,
|
||||
process::{ExitCode, ExitStatus, Termination},
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ use binstalk_downloader::{
|
|||
};
|
||||
use binstalk_fetchers::FetchError;
|
||||
use compact_str::CompactString;
|
||||
use itertools::Itertools;
|
||||
use miette::{Diagnostic, Report};
|
||||
use target_lexicon::ParseError as TargetTripleParseError;
|
||||
use thiserror::Error;
|
||||
|
@ -40,6 +41,90 @@ pub struct CrateContextError {
|
|||
err: BinstallError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CrateErrors(Box<[Box<CrateContextError>]>);
|
||||
|
||||
impl CrateErrors {
|
||||
fn iter(&self) -> impl Iterator<Item = &CrateContextError> + Clone {
|
||||
self.0.iter().map(ops::Deref::deref)
|
||||
}
|
||||
|
||||
fn get_iter_for<'a, T: 'a>(
|
||||
&'a self,
|
||||
f: fn(&'a CrateContextError) -> Option<T>,
|
||||
) -> Option<impl Iterator<Item = T> + 'a> {
|
||||
let iter = self.iter().filter_map(f);
|
||||
|
||||
if iter.clone().next().is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(iter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CrateErrors {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0.iter().format(", "), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CrateErrors {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.0.first().map(|e| e as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl miette::Diagnostic for CrateErrors {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
Some(Box::new("binstall::many_failure"))
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<miette::Severity> {
|
||||
self.iter().filter_map(miette::Diagnostic::severity).max()
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
Some(Box::new(
|
||||
self.get_iter_for(miette::Diagnostic::help)?.format("\n"),
|
||||
))
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||
Some(Box::new(
|
||||
self.get_iter_for(miette::Diagnostic::url)?.format("\n"),
|
||||
))
|
||||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
self.iter().find_map(miette::Diagnostic::source_code)
|
||||
}
|
||||
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
let get_iter = || self.iter().filter_map(miette::Diagnostic::labels).flatten();
|
||||
|
||||
if get_iter().next().is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(Box::new(get_iter()))
|
||||
}
|
||||
}
|
||||
|
||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
|
||||
Some(Box::new(
|
||||
self.iter().map(|e| e as _).chain(
|
||||
self.iter()
|
||||
.filter_map(miette::Diagnostic::related)
|
||||
.flatten(),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
|
||||
self.0.first().map(|err| &**err as _)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Invalid pkg-url {pkg_url} for {crate_name}@{version} on {target}: {reason}")]
|
||||
pub struct InvalidPkgFmtError {
|
||||
|
@ -344,6 +429,11 @@ pub enum BinstallError {
|
|||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
CrateContext(Box<CrateContextError>),
|
||||
|
||||
/// A wrapped error for failures of multiple crates when `--continue-on-failure` is specified.
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
Errors(CrateErrors),
|
||||
}
|
||||
|
||||
impl BinstallError {
|
||||
|
@ -380,6 +470,7 @@ impl BinstallError {
|
|||
GitError(_) => 98,
|
||||
LoadManifestFromWSError(_) => 99,
|
||||
CrateContext(context) => context.err.exit_number(),
|
||||
Errors(errors) => (errors.0)[0].err.exit_number(),
|
||||
};
|
||||
|
||||
// reserved codes
|
||||
|
@ -401,10 +492,27 @@ impl BinstallError {
|
|||
|
||||
/// Add crate context to the error
|
||||
pub fn crate_context(self, crate_name: impl Into<CompactString>) -> Self {
|
||||
Self::CrateContext(Box::new(CrateContextError {
|
||||
err: self,
|
||||
crate_name: crate_name.into(),
|
||||
}))
|
||||
self.crate_context_inner(crate_name.into())
|
||||
}
|
||||
|
||||
fn crate_context_inner(self, crate_name: CompactString) -> Self {
|
||||
match self {
|
||||
Self::CrateContext(mut crate_context_error) => {
|
||||
crate_context_error.crate_name = crate_name;
|
||||
Self::CrateContext(crate_context_error)
|
||||
}
|
||||
err => Self::CrateContext(Box::new(CrateContextError { err, crate_name })),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crate_errors(mut errors: Vec<Box<CrateContextError>>) -> Option<Self> {
|
||||
if errors.is_empty() {
|
||||
None
|
||||
} else if errors.len() == 1 {
|
||||
Some(Self::CrateContext(errors.pop().unwrap()))
|
||||
} else {
|
||||
Some(Self::Errors(CrateErrors(errors.into_boxed_slice())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,12 @@ impl Resolution {
|
|||
|
||||
impl ResolutionFetch {
|
||||
pub fn install(self, opts: &Options) -> Result<CrateInfo, BinstallError> {
|
||||
let crate_name = self.name.clone();
|
||||
self.install_inner(opts)
|
||||
.map_err(|err| err.crate_context(crate_name))
|
||||
}
|
||||
|
||||
fn install_inner(self, opts: &Options) -> Result<CrateInfo, BinstallError> {
|
||||
type InstallFp = fn(&bins::BinFile) -> Result<(), bins::Error>;
|
||||
|
||||
let (install_bin, install_link): (InstallFp, InstallFp) = match (opts.no_track, opts.force)
|
||||
|
@ -126,6 +132,13 @@ impl ResolutionFetch {
|
|||
|
||||
impl ResolutionSource {
|
||||
pub async fn install(self, opts: Arc<Options>) -> Result<(), BinstallError> {
|
||||
let crate_name = self.name.clone();
|
||||
self.install_inner(opts)
|
||||
.await
|
||||
.map_err(|err| err.crate_context(crate_name))
|
||||
}
|
||||
|
||||
async fn install_inner(self, opts: Arc<Options>) -> Result<(), BinstallError> {
|
||||
let target = if let Some(targets) = opts.desired_targets.get_initialized() {
|
||||
Some(targets.first().ok_or(BinstallError::NoViableTargets)?)
|
||||
} else {
|
||||
|
@ -171,7 +184,7 @@ impl ResolutionSource {
|
|||
cmd.arg("--no-track");
|
||||
}
|
||||
|
||||
debug!("Running `{}`", format_cmd(&cmd),);
|
||||
debug!("Running `{}`", format_cmd(&cmd));
|
||||
|
||||
if !opts.dry_run {
|
||||
let mut child = opts
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue