diff --git a/src/bin/bender.rs b/src/bin/bender.rs index 60b461364..102ed5a83 100644 --- a/src/bin/bender.rs +++ b/src/bin/bender.rs @@ -3,7 +3,7 @@ fn main() { if let Err(e) = bender::cli::main() { - bender::errorln!("{}", e); + eprintln!("{e:?}"); std::process::exit(1); } } diff --git a/src/cli.rs b/src/cli.rs index 6f8ee5938..92993bae3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,18 +17,20 @@ use dunce::canonicalize; use clap::builder::styling::{AnsiColor, Effects, Styles}; use clap::{ArgAction, CommandFactory, Parser, Subcommand, value_parser}; use indexmap::IndexSet; +use miette::{Context as _, IntoDiagnostic as _}; use serde_yaml_ng; use tokio::runtime::Runtime; +use crate::Result; use crate::cmd; use crate::cmd::fusesoc::FusesocArgs; use crate::config::{ Config, Manifest, Merge, PartialConfig, PrefixPaths, Validate, ValidationContext, }; -use crate::diagnostic::{Diagnostics, Warnings}; -use crate::error::*; +use crate::diagnostic::{Diagnostics, ENABLE_DEBUG, Warnings}; use crate::lockfile::*; use crate::sess::{Session, SessionArenas, SessionIo}; +use crate::{bail, err}; use crate::{debugln, fmt_path, fmt_pkg, stageln}; #[derive(Parser, Debug)] @@ -169,11 +171,12 @@ pub fn main() -> Result<()> { // the -d/--dir switch, or by searching upwards in the file system // hierarchy. let root_dir: PathBuf = match &cli.dir { - Some(d) => canonicalize(d).map_err(|cause| { - Error::chain(format!("Failed to canonicalize path {:?}.", d), cause) - })?, - None => find_package_root(Path::new(".")) - .map_err(|cause| Error::chain("Cannot find root directory of package.", cause))?, + Some(d) => canonicalize(d) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to canonicalize path {:?}.", d))?, + None => { + find_package_root(Path::new(".")).wrap_err("Cannot find root directory of package.")? + } }; debugln!("main: root dir {:?}", root_dir); @@ -242,7 +245,7 @@ pub fn main() -> Result<()> { // Checkout if we are running update or package path does not exist yet if matches!(cli.command, Commands::Update(_)) || !pkg_path.clone().exists() { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; rt.block_on(io.checkout(sess.dependency_with_name(pkg_name)?, false, &[]))?; } @@ -255,23 +258,18 @@ pub fn main() -> Result<()> { // Check if there is something at the destination path that needs to be // removed. if path.exists() { - let meta = path.symlink_metadata().map_err(|cause| { - Error::chain( - format!("Failed to read metadata of path {:?}.", path), - cause, - ) - })?; + let meta = path + .symlink_metadata() + .into_diagnostic() + .wrap_err_with(|| format!("Failed to read metadata of path {:?}.", path))?; if !meta.file_type().is_symlink() { Warnings::SkippingPackageLink(pkg_name.clone(), path.clone()).emit(); continue; } if path.read_link().map(|d| d != pkg_path).unwrap_or(true) { debugln!("main: removing existing link {:?}", path); - remove_symlink_dir(path).map_err(|cause| { - Error::chain( - format!("Failed to remove symlink at path {:?}.", path), - cause, - ) + remove_symlink_dir(path).wrap_err_with(|| { + format!("Failed to remove symlink at path {:?}.", path) })?; } } @@ -279,9 +277,9 @@ pub fn main() -> Result<()> { // Create the symlink if there is nothing at the destination. if !path.exists() { if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).map_err(|cause| { - Error::chain(format!("Failed to create directory {:?}.", parent), cause) - })?; + std::fs::create_dir_all(parent) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}.", parent))?; } let previous_dir = match path.parent() { Some(parent) => { @@ -291,13 +289,10 @@ pub fn main() -> Result<()> { } None => None, }; - symlink_dir(&pkg_path, path).map_err(|cause| { - Error::chain( - format!( - "Failed to create symlink to {:?} at path {:?}.", - pkg_path, path - ), - cause, + symlink_dir(&pkg_path, path).wrap_err_with(|| { + format!( + "Failed to create symlink to {:?} at path {:?}.", + pkg_path, path ) })?; if let Some(d) = previous_dir { @@ -332,7 +327,7 @@ pub fn main() -> Result<()> { Commands::Plugin(args) => { let (plugin_name, plugin_args) = args .split_first() - .ok_or_else(|| Error::new("No command specified.".to_string()))?; + .ok_or_else(|| err!("No command specified."))?; execute_plugin(&sess, plugin_name, plugin_args) } Commands::Completion(_) | Commands::Init | Commands::Clean(_) => { @@ -343,22 +338,22 @@ pub fn main() -> Result<()> { #[cfg(unix)] pub fn symlink_dir(p: &Path, q: &Path) -> Result<()> { - Ok(std::os::unix::fs::symlink(p, q)?) + std::os::unix::fs::symlink(p, q).into_diagnostic() } #[cfg(windows)] pub fn symlink_dir(p: &Path, q: &Path) -> Result<()> { - Ok(std::os::windows::fs::symlink_dir(p, q)?) + std::os::windows::fs::symlink_dir(p, q).into_diagnostic() } #[cfg(unix)] pub fn remove_symlink_dir(path: &Path) -> Result<()> { - Ok(std::fs::remove_file(path)?) + std::fs::remove_file(path).into_diagnostic() } #[cfg(windows)] pub fn remove_symlink_dir(path: &Path) -> Result<()> { - Ok(std::fs::remove_dir(path)?) + std::fs::remove_dir(path).into_diagnostic() } /// Find the root directory of a package. @@ -370,7 +365,8 @@ fn find_package_root(from: &Path) -> Result { // Canonicalize the path. This will resolve any intermediate links. let mut path = canonicalize(from) - .map_err(|cause| Error::chain(format!("Failed to canonicalize path {:?}.", from), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Failed to canonicalize path {:?}.", from))?; debugln!("find_package_root: canonicalized to {:?}", path); // Look up the device at the current path. This information will then be @@ -391,10 +387,10 @@ fn find_package_root(from: &Path) -> Result { // Abort if we have reached the filesystem root. if !path.pop() { - return Err(Error::new(format!( + bail!( "No manifest (`Bender.yml` file) found. Stopped searching at filesystem root {:?}.", path - ))); + ); } // Abort if we have crossed the filesystem boundary. @@ -403,15 +399,15 @@ fn find_package_root(from: &Path) -> Result { let rdev: Option<_> = metadata(&path).map(|m| m.dev()).ok(); debugln!("find_package_root: rdev = {:?}", rdev); if rdev != limit_rdev { - return Err(Error::new(format!( + bail!( "No manifest (`Bender.yml` file) found. Stopped searching at filesystem boundary {:?}.", path - ))); + ); } } } - Err(Error::new( + Err(err!( "No manifest (`Bender.yml` file) found. Reached maximum number of search steps.", )) } @@ -422,14 +418,16 @@ pub fn read_manifest(path: &Path) -> Result { use std::fs::File; debugln!("read_manifest: {:?}", path); let file = File::open(path) - .map_err(|cause| Error::chain(format!("Cannot open manifest {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot open manifest {:?}.", path))?; let partial: PartialManifest = serde_yaml_ng::from_reader(file) - .map_err(|cause| Error::chain(format!("Syntax error in manifest {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Syntax error in manifest {:?}.", path))?; partial .prefix_paths(path.parent().unwrap()) - .map_err(|cause| Error::chain(format!("Error in manifest prefixing {:?}.", path), cause))? + .wrap_err_with(|| format!("Error in manifest prefixing {:?}.", path))? .validate(&ValidationContext::default()) - .map_err(|cause| Error::chain(format!("Error in manifest {:?}.", path), cause)) + .wrap_err_with(|| format!("Error in manifest {:?}.", path)) } /// Load a configuration by traversing a directory hierarchy upwards. @@ -441,7 +439,8 @@ fn load_config(from: &Path, warn_config_loaded: bool) -> Result { // Canonicalize the path. This will resolve any intermediate links. let mut path = canonicalize(from) - .map_err(|cause| Error::chain(format!("Failed to canonicalize path {:?}.", from), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Failed to canonicalize path {:?}.", from))?; debugln!("load_config: canonicalized to {:?}", path); // Look up the device at the current path. This information will then be @@ -508,7 +507,7 @@ fn load_config(from: &Path, warn_config_loaded: bool) -> Result { // Validate the configuration. let mut out = out .validate(&ValidationContext::default()) - .map_err(|cause| Error::chain("Invalid configuration:", cause))?; + .wrap_err("Invalid configuration:")?; out.overrides = out .overrides @@ -527,9 +526,11 @@ fn maybe_load_config(path: &Path, warn_config_loaded: bool) -> Result Result<()> { debugln!("main: execute plugin `{}`", plugin); // Obtain a list of declared plugins. - let runtime = Runtime::new()?; + let runtime = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let plugins = runtime.block_on(io.plugins(false))?; // Lookup the requested plugin and complain if it does not exist. let plugin = match plugins.get(plugin) { Some(p) => p, - None => return Err(Error::new(format!("Unknown command `{}`.", plugin))), + None => bail!("Unknown command `{}`.", plugin), }; debugln!("main: found plugin {:#?}", plugin); @@ -561,25 +562,24 @@ fn execute_plugin(sess: &Session, plugin: &str, args: &[String]) -> Result<()> { cmd.env( "BENDER", std::env::current_exe() - .map_err(|cause| Error::chain("Failed to determine current executable.", cause))?, + .into_diagnostic() + .wrap_err("Failed to determine current executable.")?, ); cmd.env( "BENDER_CALL_DIR", std::env::current_dir() - .map_err(|cause| Error::chain("Failed to determine current directory.", cause))?, + .into_diagnostic() + .wrap_err("Failed to determine current directory.")?, ); cmd.env("BENDER_MANIFEST_DIR", sess.root); cmd.current_dir(sess.root); cmd.args(args); debugln!("main: executing plugin {:#?}", cmd); - let stat = cmd.status().map_err(|cause| { - Error::chain( - format!( - "Unable to spawn process for plugin `{}`. Command was {:#?}.", - plugin.name, cmd - ), - cause, + let stat = cmd.status().into_diagnostic().wrap_err_with(|| { + format!( + "Unable to spawn process for plugin `{}`. Command was {:#?}.", + plugin.name, cmd ) })?; diff --git a/src/cmd/audit.rs b/src/cmd/audit.rs index 60c42cfd4..6cf01ce90 100644 --- a/src/cmd/audit.rs +++ b/src/cmd/audit.rs @@ -8,12 +8,13 @@ use std::io::Write; use clap::Args; use futures::future::join_all; +use miette::IntoDiagnostic as _; use semver::VersionReq; use tabwriter::TabWriter; use tokio::runtime::Runtime; +use crate::Result; use crate::cmd::parents::get_parent_array; -use crate::error::*; use crate::sess::{DependencyVersions, Session, SessionIo}; /// Get information about version conflicts and possible updates. @@ -34,7 +35,7 @@ pub struct AuditArgs { /// Execute the `audit` subcommand. pub fn run(sess: &Session, args: &AuditArgs) -> Result<()> { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let binding = sess.packages().clone(); diff --git a/src/cmd/checkout.rs b/src/cmd/checkout.rs index d2c23de01..530228eb6 100644 --- a/src/cmd/checkout.rs +++ b/src/cmd/checkout.rs @@ -4,9 +4,10 @@ //! The `checkout` subcommand. use clap::Args; +use miette::IntoDiagnostic as _; use tokio::runtime::Runtime; -use crate::error::*; +use crate::Result; use crate::fmt_dim; use crate::infoln; use crate::sess::{Session, SessionIo}; @@ -27,7 +28,7 @@ pub fn run(sess: &Session, args: &CheckoutArgs) -> Result<()> { /// Execute a checkout (for the `checkout` subcommand). pub fn run_plain(sess: &Session, force: bool, update_list: &[String]) -> Result<()> { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let start_time = std::time::Instant::now(); let _srcs = rt.block_on(io.sources(force, update_list))?; diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index e00150673..249378540 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -3,9 +3,11 @@ use clap::Args; use std::path::Path; +use miette::{Context as _, IntoDiagnostic as _}; use std::fs; -use crate::error::*; +use crate::Result; +use crate::infoln; use crate::sess::Session; /// Clean all bender related dependencies @@ -18,40 +20,37 @@ pub struct CleanArgs { /// Execute the `clean` subcommand. pub fn run(sess: &Session, all: bool, path: &Path) -> Result<()> { - eprintln!("Cleaning all dependencies"); - // Clean the checkout directory if let Some(checkout_dir) = &sess.manifest.workspace.checkout_dir { let checkout_path = Path::new(checkout_dir); if checkout_path.exists() && checkout_path.is_dir() { - fs::remove_dir_all(checkout_path).map_err(|e| { - eprintln!("Failed to clean checkout directory: {:?}", e); - e - })?; - eprintln!("Successfully cleaned {}", checkout_dir.display()); + fs::remove_dir_all(checkout_path) + .into_diagnostic() + .wrap_err_with(|| { + format!("Failed to clean checkout directory {:?}.", checkout_path) + })?; + infoln!("Successfully cleaned {}", checkout_dir.display()); } else { - eprintln!("No checkout directory found."); + infoln!("No checkout directory found."); } } // Clean the .bender directory let bender_dir = path.join(".bender"); if bender_dir.exists() && bender_dir.is_dir() { - fs::remove_dir_all(&bender_dir).map_err(|e| { - eprintln!("Failed to clean .bender directory: {:?}", e); - e - })?; - eprintln!("Successfully cleaned .bender directory."); + fs::remove_dir_all(&bender_dir) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to clean directory {:?}.", bender_dir))?; + infoln!("Successfully cleaned .bender directory."); } // Clean the Bender.lock file let bender_lock = path.join("Bender.lock"); if bender_lock.exists() && bender_lock.is_file() && all { - fs::remove_file(&bender_lock).map_err(|e| { - eprintln!("Failed to remove Bender.lock file: {:?}", e); - e - })?; - eprintln!("Successfully removed Bender.lock file."); + fs::remove_file(&bender_lock) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to remove file {:?}.", bender_lock))?; + infoln!("Successfully removed Bender.lock file."); } Ok(()) diff --git a/src/cmd/clone.rs b/src/cmd/clone.rs index 20217324f..96e2a4ae7 100644 --- a/src/cmd/clone.rs +++ b/src/cmd/clone.rs @@ -8,13 +8,16 @@ use std::process::Command as SysCommand; use clap::Args; use indexmap::IndexMap; +use miette::{Context as _, IntoDiagnostic as _}; use tokio::runtime::Runtime; +use crate::Result; +use crate::bail; use crate::cli::{remove_symlink_dir, symlink_dir}; use crate::config; use crate::config::{Locked, LockedSource}; use crate::diagnostic::Warnings; -use crate::error::*; +use crate::infoln; use crate::sess::{DependencyRef, DependencySource, Session, SessionIo}; use crate::{debugln, fmt_path, fmt_pkg, stageln}; @@ -39,14 +42,15 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { if sess.config.overrides.contains_key(dep) { match &sess.config.overrides[dep] { config::Dependency::Path { path: p, .. } => { - Err(Error::new(format!( - "Dependency `{}` already has a path override at\n\t{}\n\tPlease check Bender.local or .bender.yml", + bail!( + help = "Please check `Bender.local` or `.bender.yml`.", + "Dependency `{}` already has a path override at {}", dep, p.to_str().unwrap() - )))?; + ); } _ => { - eprintln!("A non-path override is already present, proceeding anyways"); + infoln!("A non-path override is already present, proceeding anyways"); } } } @@ -55,10 +59,10 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { match sess.dependency_source(depref) { DependencySource::Git { .. } | DependencySource::Registry => {} DependencySource::Path { .. } => { - Err(Error::new(format!( + bail!( "Dependency `{}` is a path dependency. `clone` is only implemented for git dependencies.", dep - )))?; + ); } } @@ -71,9 +75,9 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { .unwrap() .success() { - Err(Error::new(format!("Creating dir {} failed", path_mod,)))?; + bail!("Creating dir {} failed", path_mod); } - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); // Copy dependency to dir for proper workflow @@ -87,7 +91,7 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { debugln!("main: checkout {:#?}", checkout); if let Some(s) = checkout.to_str() { if !Path::new(s).exists() { - Err(Error::new(format!("`{dep}` path `{s}` does not exist")))?; + bail!("`{dep}` path `{s}` does not exist"); } let command = SysCommand::new("cp") .arg("-rf") @@ -95,7 +99,7 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { .arg(path.join(path_mod).join(dep).to_str().unwrap()) .status(); if !command.unwrap().success() { - Err(Error::new(format!("Copying {} failed", dep,)))?; + bail!("Copying {} failed", dep); } } @@ -110,7 +114,7 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { .unwrap() .success() { - Err(Error::new("git renaming remote origin failed".to_string()))?; + bail!("git renaming remote origin failed"); } if !SysCommand::new(&sess.config.git) @@ -127,7 +131,7 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { .unwrap() .success() { - Err(Error::new("git adding remote failed".to_string()))?; + bail!("git adding remote failed"); } if !sess.local_only { @@ -139,7 +143,7 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { .unwrap() .success() { - Err(Error::new("git fetch failed".to_string()))?; + bail!("git fetch failed"); } } else { Warnings::LocalNoFetch.emit(); @@ -159,13 +163,9 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { dep, path_mod ); if local_path.exists() { - let local_file_str = match std::fs::read_to_string(&local_path) { - Err(why) => Err(Error::new(format!( - "Reading Bender.local failed with msg:\n\t{}", - why - )))?, - Ok(local_file_str) => local_file_str, - }; + let local_file_str = std::fs::read_to_string(&local_path) + .into_diagnostic() + .wrap_err_with(|| format!("Reading Bender.local failed at {:?}.", local_path))?; let mut new_str = String::new(); if local_file_str.contains("overrides:") { let split = local_file_str.split('\n'); @@ -188,17 +188,13 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { new_str.push_str(&dep_str); new_str.push_str(&local_file_str); } - if let Err(why) = std::fs::write(local_path, new_str) { - Err(Error::new(format!( - "Writing new Bender.local failed with msg:\n\t{}", - why - )))? - } - } else if let Err(why) = std::fs::write(local_path, format!("overrides:\n{}", dep_str)) { - Err(Error::new(format!( - "Writing new Bender.local failed with msg:\n\t{}", - why - )))? + std::fs::write(local_path.clone(), new_str) + .into_diagnostic() + .wrap_err("Writing new Bender.local failed ")?; + } else { + std::fs::write(local_path.clone(), format!("overrides:\n{}", dep_str)) + .into_diagnostic() + .wrap_err("Writing new Bender.local failed ")?; }; eprintln!("{} dependency added to Bender.local", dep); @@ -206,9 +202,11 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { // Update Bender.lock to enforce usage use std::fs::File; let file = File::open(path.join("Bender.lock")) - .map_err(|cause| Error::chain(format!("Cannot open lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot open lockfile {:?}.", path))?; let mut locked: Locked = serde_yaml_ng::from_reader(&file) - .map_err(|cause| Error::chain(format!("Syntax error in lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Syntax error in lockfile {:?}.", path))?; let path_deps = get_path_subdeps(&io, &rt, &path.join(path_mod).join(dep), depref)?; @@ -238,9 +236,11 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { } let file = File::create(path.join("Bender.lock")) - .map_err(|cause| Error::chain(format!("Cannot create lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot create lockfile {:?}.", path))?; serde_yaml_ng::to_writer(&file, &locked) - .map_err(|cause| Error::chain(format!("Cannot write lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot write lockfile {:?}.", path))?; eprintln!("Lockfile updated"); @@ -259,23 +259,20 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { // Check if there is something at the destination path that needs to be // removed. if link_path.exists() { - let meta = link_path.symlink_metadata().map_err(|cause| { - Error::chain( - format!("Failed to read metadata of path {:?}.", link_path), - cause, - ) - })?; + let meta = link_path + .symlink_metadata() + .into_diagnostic() + .wrap_err_with(|| { + format!("Failed to read metadata of path {:?}.", link_path) + })?; if !meta.file_type().is_symlink() { Warnings::SkippingPackageLink(pkg_name.clone(), link_path.to_path_buf()).emit(); continue; } if link_path.read_link().map(|d| d != pkg_path).unwrap_or(true) { debugln!("main: removing existing link {:?}", link_path); - remove_symlink_dir(link_path).map_err(|cause| { - Error::chain( - format!("Failed to remove symlink at path {:?}.", link_path), - cause, - ) + remove_symlink_dir(link_path).wrap_err_with(|| { + format!("Failed to remove symlink at path {:?}.", link_path) })?; } } @@ -283,9 +280,9 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { // Create the symlink if there is nothing at the destination. if !link_path.exists() { if let Some(parent) = link_path.parent() { - std::fs::create_dir_all(parent).map_err(|cause| { - Error::chain(format!("Failed to create directory {:?}.", parent), cause) - })?; + std::fs::create_dir_all(parent) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}.", parent))?; } let previous_dir = match link_path.parent() { Some(parent) => { @@ -295,13 +292,10 @@ pub fn run(sess: &Session, path: &Path, args: &CloneArgs) -> Result<()> { } None => None, }; - symlink_dir(&pkg_path, link_path).map_err(|cause| { - Error::chain( - format!( - "Failed to create symlink to {:?} at path {:?}.", - pkg_path, link_path - ), - cause, + symlink_dir(&pkg_path, link_path).wrap_err_with(|| { + format!( + "Failed to create symlink to {:?} at path {:?}.", + pkg_path, link_path ) })?; if let Some(d) = previous_dir { diff --git a/src/cmd/completion.rs b/src/cmd/completion.rs index 1ab9931cb..828fc318e 100644 --- a/src/cmd/completion.rs +++ b/src/cmd/completion.rs @@ -5,7 +5,7 @@ use std::io; -use crate::error::*; +use crate::Result; use clap::{Args, Command}; use clap_complete::{Shell, generate}; diff --git a/src/cmd/config.rs b/src/cmd/config.rs index 3a18b85b3..cae6a96a9 100644 --- a/src/cmd/config.rs +++ b/src/cmd/config.rs @@ -6,9 +6,10 @@ use std; use std::io::Write; +use miette::{Context as _, IntoDiagnostic as _}; use serde_json; -use crate::error::*; +use crate::Result; use crate::sess::Session; /// Execute the `config` subcommand. @@ -19,5 +20,7 @@ pub fn run(sess: &Session) -> Result<()> { serde_json::to_writer_pretty(handle, sess.config) }; let _ = writeln!(std::io::stdout(),); - result.map_err(|cause| Error::chain("Failed to serialize configuration.", cause)) + result + .into_diagnostic() + .wrap_err("Failed to serialize configuration.") } diff --git a/src/cmd/fusesoc.rs b/src/cmd/fusesoc.rs index 4a0ee50ea..906ed4898 100644 --- a/src/cmd/fusesoc.rs +++ b/src/cmd/fusesoc.rs @@ -15,12 +15,14 @@ use std::path::PathBuf; use clap::{ArgAction, Args}; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; +use miette::{Context as _, IntoDiagnostic as _}; use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; use walkdir::{DirEntry, WalkDir}; +use crate::Result; use crate::diagnostic::Warnings; -use crate::error::*; +use crate::err; use crate::sess::{Session, SessionIo}; use crate::src::{SourceFile, SourceGroup}; use crate::target::TargetSet; @@ -53,9 +55,11 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> { let bender_generate_flag = "Created by bender from the available manifest file."; let vendor_string = args.fuse_vendor.as_deref().unwrap_or(""); let version_string = match &args.fuse_version { - Some(version) => Some(semver::Version::parse(version).map_err(|cause| { - Error::chain(format!("Unable to parse version {}.", version), cause) - })?), + Some(version) => Some( + semver::Version::parse(version) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to parse version {}.", version))?, + ), None => None, }; let name = &sess.manifest.package.name; @@ -70,7 +74,7 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> { version_string.clone(), ) .flatten()), - None => Err(Error::new("Error in loading sources")), + None => Err(err!("Error in loading sources")), }?; let core_path = &sess.root.join(format!("{}.core", name)); @@ -81,10 +85,10 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> { }; if !file_str.contains(bender_generate_flag) { - Err(Error::new(format!( + Err(err!( "{}.core already exists, please delete to generate.", name - )))? + ))? } let fuse_depend_string = sess @@ -129,9 +133,9 @@ pub fn run_single(sess: &Session, args: &FusesocArgs) -> Result<()> { &args.license, )?; - fs::write(core_path, fuse_str).map_err(|cause| { - Error::chain(format!("Unable to write corefile for {:?}.", &name), cause) - })?; + fs::write(core_path, fuse_str) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to write corefile for {:?}.", &name))?; if fuse_depend_string.len() > 1 { Warnings::DependStringMaybeWrong.emit(); @@ -145,13 +149,15 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { let bender_generate_flag = "Created by bender from the available manifest file."; let vendor_string = args.fuse_vendor.as_deref().unwrap_or(""); let version_string = match &args.fuse_version { - Some(version) => Some(semver::Version::parse(version).map_err(|cause| { - Error::chain(format!("Unable to parse version {}.", version), cause) - })?), + Some(version) => Some( + semver::Version::parse(version) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to parse version {}.", version))?, + ), None => None, }; - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let srcs = rt.block_on(io.sources(false, &[]))?; @@ -174,9 +180,8 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { .iter() .map(|(pkg, dir)| { let paths = fs::read_dir(dir) - .map_err(|err| { - Error::chain(format!("Unable to read package directory {:?}", dir), err) - })? + .into_diagnostic() + .wrap_err_with(|| format!("Unable to read package directory {:?}", dir))? .filter(|path| { path.as_ref().unwrap().path().extension() == Some(OsStr::new("core")) }) @@ -223,15 +228,10 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { ) .unwrap(); for i in 0..present_core_files[pkg].len() { - let file_str = - read_to_string(&present_core_files[pkg][index]).map_err(|cause| { - Error::chain( - format!( - "Cannot open .core file {:?}.", - &present_core_files[pkg][index] - ), - cause, - ) + let file_str = read_to_string(&present_core_files[pkg][i]) + .into_diagnostic() + .wrap_err_with(|| { + format!("Cannot open .core file {:?}.", &present_core_files[pkg][i]) })?; let fuse_core = parse_fuse_file( @@ -261,7 +261,7 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { let mut buffer = String::new(); io::stdin().read_line(&mut buffer).unwrap(); if buffer.starts_with('\n') { - break Err(Error::new(msg)); + break Err(err!("{}", msg)); } let choice = match buffer.trim().parse::() { Ok(u) => u, @@ -279,15 +279,14 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { }; } } - let file_str = read_to_string(&present_core_files[pkg][index]).map_err(|cause| { - Error::chain( + let file_str = read_to_string(&present_core_files[pkg][index]) + .into_diagnostic() + .wrap_err_with(|| { format!( "Cannot open .core file {:?}.", &present_core_files[pkg][index] - ), - cause, - ) - })?; + ) + })?; if file_str.contains(bender_generate_flag) { generate_files.insert(pkg.to_string(), present_core_files[pkg][index].clone()); @@ -328,9 +327,9 @@ pub fn run(sess: &Session, args: &FusesocArgs) -> Result<()> { &args.license, )?; - fs::write(&generate_files[pkg], fuse_str).map_err(|cause| { - Error::chain(format!("Unable to write corefile for {:?}.", &pkg), cause) - })?; + fs::write(&generate_files[pkg], fuse_str) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to write corefile for {:?}.", &pkg))?; } Ok(()) @@ -474,28 +473,25 @@ fn get_fuse_file_str( fuse_str.push('\n'); fuse_str.push_str( &serde_yaml_ng::to_string(&fuse_pkg) - .map_err(|err| Error::chain("Failed to serialize.", err))?, + .into_diagnostic() + .wrap_err("Failed to serialize.")?, ); Ok(fuse_str) } fn parse_fuse_file(file_str: String, filename: String) -> Result { serde_yaml_ng::from_value({ - let mut value = serde_yaml_ng::from_str::(&file_str).map_err(|cause| { - Error::chain( - format!("Unable to parse core file to value {:?}.", &filename), - cause, - ) - })?; - value.apply_merge().map_err(|cause| { - Error::chain( - format!("Unable to apply merge to file {:?}.", &filename), - cause, - ) - })?; + let mut value = serde_yaml_ng::from_str::(&file_str) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to parse core file to value {:?}.", &filename))?; + value + .apply_merge() + .into_diagnostic() + .wrap_err_with(|| format!("Unable to apply merge to file {:?}.", &filename))?; value }) - .map_err(|cause| Error::chain(format!("Unable to parse core file {:?}.", &filename), cause)) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to parse core file {:?}.", &filename)) } fn get_fuse_depend_string( diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 04c96bd25..587ffd886 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -10,12 +10,15 @@ use std::io::Write; use std::path::Path; use std::process::Command as SysCommand; -use crate::error::*; +use miette::IntoDiagnostic as _; + +use crate::Result; +use crate::bail; /// Execute the `init` subcommand. pub fn run() -> Result<()> { // Get working directory name - let binding = current_dir()?; + let binding = current_dir().into_diagnostic()?; let cwd = binding .as_path() .file_name() @@ -27,7 +30,8 @@ pub fn run() -> Result<()> { let name = String::from_utf8( SysCommand::new("git") .args(["config", "user.name"]) - .output()? + .output() + .into_diagnostic()? .stdout, ) .unwrap_or("Your Name".to_string()); @@ -37,7 +41,8 @@ pub fn run() -> Result<()> { let email = String::from_utf8( SysCommand::new("git") .args(["config", "user.email"]) - .output()? + .output() + .into_diagnostic()? .stdout, ) .unwrap_or("you@example.com".to_string()); @@ -47,10 +52,10 @@ pub fn run() -> Result<()> { // Create Bender.yml if Path::new("Bender.yml").exists() { - return Err(Error::new("Bender.yml already exists")); + bail!("Bender.yml already exists"); } - let mut file = File::create("Bender.yml")?; + let mut file = File::create("Bender.yml").into_diagnostic()?; writeln!( file, @@ -71,7 +76,8 @@ sources: # levels 1 and 0, etc. Files within a level are ordered alphabetically. # Level 0", cwd, name, email - )?; + ) + .into_diagnostic()?; Ok(()) } diff --git a/src/cmd/packages.rs b/src/cmd/packages.rs index f7bf0e05b..b7dfab139 100644 --- a/src/cmd/packages.rs +++ b/src/cmd/packages.rs @@ -7,10 +7,11 @@ use std::io::Write; use clap::Args; use indexmap::IndexSet; +use miette::IntoDiagnostic as _; use tabwriter::TabWriter; use tokio::runtime::Runtime; -use crate::error::*; +use crate::Result; use crate::sess::{DependencySource, Session, SessionIo}; /// Information about the dependency graph @@ -46,7 +47,7 @@ pub struct PackagesArgs { /// Execute the `packages` subcommand. pub fn run(sess: &Session, args: &PackagesArgs) -> Result<()> { if args.targets { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let srcs = rt.block_on(io.sources(false, &[]))?; let mut target_str = String::from(""); diff --git a/src/cmd/parents.rs b/src/cmd/parents.rs index efe52298c..62226ecf3 100644 --- a/src/cmd/parents.rs +++ b/src/cmd/parents.rs @@ -8,11 +8,12 @@ use std::io::Write; use crate::diagnostic::Warnings; use clap::Args; use indexmap::IndexMap; +use miette::IntoDiagnostic as _; use tabwriter::TabWriter; use tokio::runtime::Runtime; +use crate::Result; use crate::config::Dependency; -use crate::error::*; use crate::sess::{DependencyConstraint, DependencySource}; use crate::sess::{Session, SessionIo}; use crate::{fmt_path, fmt_version}; @@ -33,7 +34,7 @@ pub struct ParentsArgs { pub fn run(sess: &Session, args: &ParentsArgs) -> Result<()> { let dep = &args.name.to_lowercase(); let mydep = sess.dependency_with_name(dep)?; - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let parent_array = get_parent_array(sess, &rt, &io, dep, args.targets)?; diff --git a/src/cmd/path.rs b/src/cmd/path.rs index 5c0eedce6..7d7939f33 100644 --- a/src/cmd/path.rs +++ b/src/cmd/path.rs @@ -7,10 +7,11 @@ use std::io::Write; use clap::Args; use futures::future::join_all; +use miette::IntoDiagnostic as _; use tokio::runtime::Runtime; +use crate::Result; use crate::debugln; -use crate::error::*; use crate::sess::{Session, SessionIo}; /// Get the path to a dependency @@ -44,7 +45,7 @@ pub fn run(sess: &Session, args: &PathArgs) -> Result<()> { // Check out if requested or not done yet if args.checkout || !paths.iter().all(|p| p.exists()) { debugln!("main: obtain checkouts {:?}", ids); - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let _checkouts = rt .block_on(join_all( ids.iter() diff --git a/src/cmd/script.rs b/src/cmd/script.rs index ac74ec377..aa8114856 100644 --- a/src/cmd/script.rs +++ b/src/cmd/script.rs @@ -8,14 +8,15 @@ use std::path::{Path, PathBuf}; use clap::{ArgAction, Args, Subcommand, ValueEnum}; use indexmap::{IndexMap, IndexSet}; +use miette::{Context as _, IntoDiagnostic as _}; use serde::Serialize; use tera::{Context, Tera}; use tokio::runtime::Runtime; +use crate::Result; use crate::cmd::sources::get_passed_targets; use crate::config::{Validate, ValidationContext}; use crate::diagnostic::Warnings; -use crate::error::*; use crate::sess::{Session, SessionIo}; use crate::src::{SourceFile, SourceGroup, SourceType}; use crate::target::TargetSet; @@ -228,7 +229,7 @@ where /// Execute the `script` subcommand. pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let mut srcs = rt.block_on(io.sources(false, &[]))?; @@ -380,7 +381,9 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { include_str!("../script_fmt/vivado_tcl.tera") } ScriptFormat::Precision => include_str!("../script_fmt/precision_tcl.tera"), - ScriptFormat::Template { template } => &std::fs::read_to_string(template)?, + ScriptFormat::Template { template } => { + &std::fs::read_to_string(template).into_diagnostic()? + } ScriptFormat::TemplateJson => JSON, }; @@ -665,7 +668,8 @@ fn emit_template( "{}", Tera::default() .render_str(template, &tera_context) - .map_err(|e| { Error::chain("Failed to render template.", e) })? + .into_diagnostic() + .wrap_err("Failed to render template.")? ); Ok(()) diff --git a/src/cmd/snapshot.rs b/src/cmd/snapshot.rs index bd95cebfd..8125865a9 100644 --- a/src/cmd/snapshot.rs +++ b/src/cmd/snapshot.rs @@ -8,13 +8,16 @@ use std::process::Command as SysCommand; use clap::Args; use indexmap::IndexMap; +use miette::{Context as _, IntoDiagnostic as _}; use tokio::runtime::Runtime; +use crate::Result; +use crate::bail; use crate::cli::{remove_symlink_dir, symlink_dir}; use crate::cmd::clone::get_path_subdeps; use crate::config::{Dependency, Locked, LockedSource}; use crate::diagnostic::Warnings; -use crate::error::*; +use crate::infoln; use crate::sess::{DependencySource, Session, SessionIo}; use crate::{debugln, fmt_path, fmt_pkg, stageln}; @@ -59,7 +62,8 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { .arg("status") .arg("--porcelain") .current_dir(&dep_path) - .output()? + .output() + .into_diagnostic()? .stdout .is_empty() && !args.no_skip @@ -75,22 +79,24 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { .arg("get-url") .arg("origin") .current_dir(&dep_path) - .output()? + .output() + .into_diagnostic()? .stdout, ) { Ok(url) => url.trim_end_matches(&['\r', '\n'][..]).to_string(), - Err(_) => Err(Error::new("Failed to get git url.".to_string()))?, + Err(_) => bail!("Failed to get git url."), }; let hash = match String::from_utf8( SysCommand::new(&sess.config.git) .arg("rev-parse") .arg("HEAD") .current_dir(&dep_path) - .output()? + .output() + .into_diagnostic()? .stdout, ) { Ok(hash) => hash.trim_end_matches(&['\r', '\n'][..]).to_string(), - Err(_) => Err(Error::new("Failed to get git hash.".to_string()))?, + Err(_) => bail!("Failed to get git hash."), }; eprintln!("Snapshotting {} at {} from {}", name, hash, url); @@ -105,13 +111,9 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { // Update the Bender.local to keep changes let local_path = sess.root.join("Bender.local"); if local_path.exists() && !snapshot_list.is_empty() { - let local_file_str = match std::fs::read_to_string(&local_path) { - Err(why) => Err(Error::new(format!( - "Reading Bender.local failed with msg:\n\t{}", - why - )))?, - Ok(local_file_str) => local_file_str, - }; + let local_file_str = std::fs::read_to_string(&local_path) + .into_diagnostic() + .wrap_err("Reading Bender.local failed")?; let mut new_str = String::new(); if local_file_str.contains("overrides:") { let split = local_file_str.split('\n'); @@ -138,17 +140,14 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { // Ensure trailing newline is not duplicated new_str.pop(); } - if let Err(why) = std::fs::write(local_path, new_str) { - Err(Error::new(format!( - "Writing new Bender.local failed with msg:\n\t{}", - why - )))? - } - eprintln!("Bender.local updated with snapshots."); + std::fs::write(local_path.clone(), new_str) + .into_diagnostic() + .wrap_err("Writing new Bender.local failed")?; + infoln!("Bender.local updated with snapshots."); } } - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let mut path_subdeps: IndexMap = IndexMap::new(); @@ -170,10 +169,11 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { // Update the Bender.lock file with the new hash use std::fs::File; let file = File::open(sess.root.join("Bender.lock")) - .map_err(|cause| Error::chain(format!("Cannot open lockfile {:?}.", sess.root), cause))?; - let mut locked: Locked = serde_yaml_ng::from_reader(&file).map_err(|cause| { - Error::chain(format!("Syntax error in lockfile {:?}.", sess.root), cause) - })?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot open lockfile {:?}.", sess.root))?; + let mut locked: Locked = serde_yaml_ng::from_reader(&file) + .into_diagnostic() + .wrap_err_with(|| format!("Syntax error in lockfile {:?}.", sess.root))?; for (name, url, hash) in &snapshot_list { let mut mod_package = locked.packages.get_mut(name).unwrap().clone(); @@ -197,14 +197,16 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { } let file = File::create(sess.root.join("Bender.lock")) - .map_err(|cause| Error::chain(format!("Cannot create lockfile {:?}.", sess.root), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot create lockfile {:?}.", sess.root))?; serde_yaml_ng::to_writer(&file, &locked) - .map_err(|cause| Error::chain(format!("Cannot write lockfile {:?}.", sess.root), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot write lockfile {:?}.", sess.root))?; if args.checkout { sess.load_locked(&locked)?; - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let _srcs = rt.block_on(io.sources(args.force, &[]))?; } @@ -251,23 +253,20 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { // Check if there is something at the destination path that needs to be // removed. if link_path.exists() { - let meta = link_path.symlink_metadata().map_err(|cause| { - Error::chain( - format!("Failed to read metadata of path {:?}.", link_path), - cause, - ) - })?; + let meta = link_path + .symlink_metadata() + .into_diagnostic() + .wrap_err_with(|| { + format!("Failed to read metadata of path {:?}.", link_path) + })?; if !meta.file_type().is_symlink() { Warnings::SkippingPackageLink(pkg_name.clone(), link_path.to_path_buf()).emit(); continue; } if link_path.read_link().map(|d| d != pkg_path).unwrap_or(true) { debugln!("main: removing existing link {:?}", link_path); - remove_symlink_dir(link_path).map_err(|cause| { - Error::chain( - format!("Failed to remove symlink at path {:?}.", link_path), - cause, - ) + remove_symlink_dir(link_path).wrap_err_with(|| { + format!("Failed to remove symlink at path {:?}.", link_path) })?; } } @@ -275,9 +274,9 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { // Create the symlink if there is nothing at the destination. if !link_path.exists() { if let Some(parent) = link_path.parent() { - std::fs::create_dir_all(parent).map_err(|cause| { - Error::chain(format!("Failed to create directory {:?}.", parent), cause) - })?; + std::fs::create_dir_all(parent) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}.", parent))?; } let previous_dir = match link_path.parent() { Some(parent) => { @@ -287,13 +286,10 @@ pub fn run(sess: &Session, args: &SnapshotArgs) -> Result<()> { } None => None, }; - symlink_dir(&pkg_path, link_path).map_err(|cause| { - Error::chain( - format!( - "Failed to create symlink to {:?} at path {:?}.", - pkg_path, link_path - ), - cause, + symlink_dir(&pkg_path, link_path).wrap_err_with(|| { + format!( + "Failed to create symlink to {:?} at path {:?}.", + pkg_path, link_path ) })?; if let Some(d) = previous_dir { diff --git a/src/cmd/sources.rs b/src/cmd/sources.rs index b6ab02693..a998e16b2 100644 --- a/src/cmd/sources.rs +++ b/src/cmd/sources.rs @@ -10,11 +10,12 @@ use std::io::Write; use clap::{ArgAction, Args}; use futures::future::join_all; use indexmap::IndexSet; +use miette::{Context as _, IntoDiagnostic as _}; use serde_json; use tokio::runtime::Runtime; +use crate::Result; use crate::config::{Dependency, Validate, ValidationContext}; -use crate::error::*; use crate::sess::{Session, SessionIo}; use crate::target::{TargetSet, TargetSpec}; @@ -67,7 +68,7 @@ where /// Execute the `sources` subcommand. pub fn run(sess: &Session, args: &SourcesArgs) -> Result<()> { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(sess); let mut srcs = rt.block_on(io.sources(false, &[]))?; @@ -75,7 +76,8 @@ pub fn run(sess: &Session, args: &SourcesArgs) -> Result<()> { let stdout = std::io::stdout(); let handle = stdout.lock(); return serde_json::to_writer_pretty(handle, &srcs.flatten()) - .map_err(|err| Error::chain("Failed to serialize source file manifest.", err)); + .into_diagnostic() + .wrap_err("Failed to serialize source file manifest."); } // Filter the sources by target. @@ -131,7 +133,9 @@ pub fn run(sess: &Session, args: &SourcesArgs) -> Result<()> { } }; let _ = writeln!(std::io::stdout(),); - result.map_err(|cause| Error::chain("Failed to serialize source file manifest.", cause)) + result + .into_diagnostic() + .wrap_err("Failed to serialize source file manifest.") } /// Get the targets passed to dependencies from calling packages. diff --git a/src/cmd/update.rs b/src/cmd/update.rs index a3800a5c4..c5daa987d 100644 --- a/src/cmd/update.rs +++ b/src/cmd/update.rs @@ -10,11 +10,12 @@ use clap::Args; use indexmap::IndexSet; use tabwriter::TabWriter; +use crate::Result; use crate::cmd; use crate::config::{Locked, LockedPackage}; use crate::debugln; use crate::diagnostic::Warnings; -use crate::error::*; +use crate::ensure; use crate::lockfile::*; use crate::resolver::DependencyResolver; use crate::sess::Session; @@ -79,12 +80,12 @@ pub fn run<'ctx>( }; for dep in requested.iter() { - if !keep_locked.contains(&dep) { - return Err(Error::new(format!( - "Dependency {} is not present, cannot update {}.", - dep, dep - ))); - } + ensure!( + keep_locked.contains(&dep), + help = "Check the dependency name or run `bender update` without selecting specific dependencies.", + "Dependency `{}` is not present and cannot be updated.", + dep + ); } // Unlock dependencies recursively @@ -114,13 +115,11 @@ pub fn run_plain<'ctx>( existing: Option<&'ctx Locked>, keep_locked: IndexSet<&'ctx String>, ) -> Result<(Locked, Vec)> { - if sess.manifest.frozen { - return Err(Error::new(format!( - "Refusing to update dependencies because the package is frozen. - Remove the `frozen: true` from {:?} to proceed; there be dragons.", - sess.root.join("Bender.yml") - ))); - } + ensure!( + !sess.manifest.frozen, + help = "Remove `frozen: true` from `Bender.yml` to proceed.", + "Refusing to update dependencies because the package is frozen." + ); debugln!( "main: lockfile {:?} outdated", sess.root.join("Bender.lock") diff --git a/src/cmd/vendor.rs b/src/cmd/vendor.rs index a93ab84e5..cbe1f3d51 100644 --- a/src/cmd/vendor.rs +++ b/src/cmd/vendor.rs @@ -12,16 +12,18 @@ use std::path::PathBuf; use clap::{Args, Subcommand}; use futures::TryFutureExt; use glob::Pattern; +use miette::{Context as _, IntoDiagnostic as _}; use tempfile::TempDir; use tokio::runtime::Runtime; use crate::config; use crate::config::PrefixPaths; use crate::diagnostic::Warnings; -use crate::error::*; use crate::git::Git; use crate::progress::{GitProgressOps, ProgressHandler}; -use crate::sess::{DependencySource, Session}; +use crate::sess::{DependencySource, Session, SessionErrors}; +use crate::{Error, Result}; +use crate::{bail, ensure}; use crate::{fmt_path, fmt_pkg, stageln}; /// A patch linkage @@ -90,12 +92,12 @@ pub enum VendorSubcommand { /// Execute the `vendor` subcommand. pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; for vendor_package in &sess.manifest.vendor_package { // Clone upstream into a temporary directory (or make use of .bender/db?) let dep_src = DependencySource::from(&vendor_package.upstream); - let tmp_dir = TempDir::new()?; + let tmp_dir = TempDir::new().into_diagnostic()?; let tmp_path = tmp_dir.path(); let dep_path = match dep_src { DependencySource::Path(path) => path, @@ -107,31 +109,47 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { GitProgressOps::Clone, vendor_package.name.as_str(), ); - git.clone().spawn_with(|c| c.arg("clone").arg(url).arg("."), Some(pb)) - .map_err(move |cause| { - Warnings::GitInitFailed { - is_ssh: url.contains("git@"), - }.emit(); - Error::chain( - format!("Failed to initialize git database in {:?}.", tmp_path), - cause, - ) - }).await?; - let rev_hash = match vendor_package.upstream { - config::Dependency::GitRevision { ref rev, .. } => Ok(rev), - _ => Err(Error::new("Please ensure your vendor reference is a commit hash to avoid upstream changes impacting your checkout")), - }?; + git.clone() + .spawn_with(|c| c.arg("clone").arg(url).arg("."), Some(pb)) + .await + .map_err(move |cause| { + Error::from(SessionErrors::GitDatabaseAccess( + "initialize", + url.contains("git@"), + cause.into(), + )) + })?; + let rev_hash = if let config::Dependency::GitRevision { ref rev, .. } = + vendor_package.upstream + { + rev + } else { + bail!( + "Please ensure your vendor reference is a commit hash to avoid upstream changes impacting your checkout" + ); + }; let pb = ProgressHandler::new( sess.multiprogress.clone(), GitProgressOps::Checkout, vendor_package.name.as_str(), ); git.clone().spawn_with(|c| c.arg("checkout").arg(rev_hash), Some(pb)).await?; - if *rev_hash != git.spawn_with(|c| c.arg("rev-parse").arg("--verify").arg(format!("{}^{{commit}}", rev_hash)), None).await?.trim_end_matches('\n') { - Err(Error::new("Please ensure your vendor reference is a commit hash to avoid upstream changes impacting your checkout")) - } else { - Ok(()) - } + ensure!( + *rev_hash + == git + .spawn_with( + |c| c + .arg("rev-parse") + .arg("--verify") + .arg(format!("{}^{{commit}}", rev_hash)), + None + ) + .await? + .trim_end_matches('\n'), + help = "Use a full commit hash in `upstream` to make vendoring reproducible.", + "Vendor reference must resolve to a commit hash." + ); + Ok(()) })?; tmp_path.to_path_buf() @@ -212,7 +230,7 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { // Print diff for each link sorted_links.into_iter().try_for_each(|patch_link| { let get_diff = diff(&rt, git.clone(), vendor_package, patch_link, dep_path.clone()) - .map_err(|cause| Error::chain("Failed to get diff.", cause))?; + .wrap_err("Failed to get diff.")?; if !get_diff.is_empty() { let _ = write!(std::io::stdout(), "{}", get_diff); // If desired, return an error (e.g. for CI) @@ -222,7 +240,7 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { Some(err_msg) => err_msg.to_string(), _ => "Found differences, please patch (e.g. using bender vendor patch).".to_string() }; - return Err(Error::new(err_msg)) + bail!(err_msg) } } Ok(()) @@ -242,9 +260,8 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { } else { std::fs::remove_file(target_path.clone()) } - .map_err(|cause| { - Error::chain(format!("Failed to remove {:?}.", target_path), cause) - })?; + .into_diagnostic() + .wrap_err_with(|| format!("Failed to remove {:?}.", target_path))?; } // init @@ -277,7 +294,7 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { apply_patches(&rt, git.clone(), vendor_package.name.clone(), patch_link) .map(|num| num_patches += num) }) - .map_err(|cause| Error::chain("Failed to apply patch.", cause))?; + .wrap_err("Failed to apply patch.")?; // Commit applied patches to clean working tree if num_patches > 0 { @@ -297,7 +314,7 @@ pub fn run(sess: &Session, args: &VendorArgs) -> Result<()> { patch_link, dep_path.clone(), ) - .map_err(|cause| Error::chain("Failed to get diff.", cause))?; + .wrap_err("Failed to get diff.")?; gen_plain_patch(get_diff, patch_dir, false) } else { gen_format_patch( @@ -346,12 +363,9 @@ pub fn init( .clone() .prefix_paths(&vendor_package.target_dir)?; let link_from = patch_link.from_prefix.clone().prefix_paths(dep_path)?; - std::fs::create_dir_all(link_to.parent().unwrap()).map_err(|cause| { - Error::chain( - format!("Failed to create directory {:?}", link_to.parent()), - cause, - ) - })?; + std::fs::create_dir_all(link_to.parent().unwrap()) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}", link_to.parent()))?; if no_patch { apply_patches( @@ -386,16 +400,15 @@ pub fn init( )?, false => { if link_from.exists() { - std::fs::copy(&link_from, &link_to).map_err(|cause| { - Error::chain( + std::fs::copy(&link_from, &link_to) + .into_diagnostic() + .wrap_err_with(|| { format!( "Failed to copy {} to {}.", link_from.to_str().unwrap(), link_to.to_str().unwrap(), - ), - cause, - ) - })?; + ) + })?; } else { Warnings::NotInUpstream { path: link_from.to_str().unwrap().to_string(), @@ -421,11 +434,12 @@ pub fn apply_patches( }; // Create directory in case it does not already exist - std::fs::create_dir_all(patch_dir).map_err(|cause| { - Error::chain(format!("Failed to create directory {patch_dir:?}"), cause) - })?; + std::fs::create_dir_all(patch_dir) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {patch_dir:?}"))?; - let mut patches = std::fs::read_dir(patch_dir)? + let mut patches = std::fs::read_dir(patch_dir) + .into_diagnostic()? .map(move |f| f.unwrap().path()) .filter(|f| f.extension().is_some()) .filter(|f| f.extension().unwrap() == "patch") @@ -466,9 +480,7 @@ pub fn apply_patches( None, ) .await - .map_err(|cause| { - Error::chain(format!("Failed to apply patch {patch:?}."), cause) - })?; + .wrap_err_with(|| format!("Failed to apply patch {patch:?}."))?; stageln!( "Patched", @@ -500,10 +512,11 @@ pub fn diff( .clone() .prefix_paths(vendor_package.target_dir.as_ref())?; if !&link_to.exists() { - return Err(Error::new(format!( - "Could not find {}. Did you run bender vendor init?", + bail!( + help = "Did you run `bender vendor init`", + "Could not find {}", link_to.to_str().unwrap() - ))); + ); } // Copy src to dst recursively. match &link_to.is_dir() { @@ -523,16 +536,15 @@ pub fn diff( .collect(), )?, false => { - std::fs::copy(&link_to, &link_from).map_err(|cause| { - Error::chain( + std::fs::copy(&link_to, &link_from) + .into_diagnostic() + .wrap_err_with(|| { format!( "Failed to copy {} to {}.", link_to.to_str().unwrap(), link_from.to_str().unwrap(), - ), - cause, - ) - })?; + ) + })?; } }; // Get diff @@ -558,14 +570,12 @@ pub fn gen_plain_patch(diff: String, patch_dir: impl AsRef, no_patch: bool if !diff.is_empty() { // if let Some(patch) = patch_dir { // Create directory in case it does not already exist - std::fs::create_dir_all(patch_dir.as_ref()).map_err(|cause| { - Error::chain( - format!("Failed to create directory {:?}", patch_dir.as_ref()), - cause, - ) - })?; + std::fs::create_dir_all(patch_dir.as_ref()) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}", patch_dir.as_ref()))?; - let mut patches = std::fs::read_dir(patch_dir.as_ref())? + let mut patches = std::fs::read_dir(patch_dir.as_ref()) + .into_diagnostic()? .map(move |f| f.unwrap().path()) .filter(|f| f.extension().unwrap() == "patch") .collect::>(); @@ -574,7 +584,7 @@ pub fn gen_plain_patch(diff: String, patch_dir: impl AsRef, no_patch: bool let new_patch = if no_patch || patches.is_empty() { // Remove all old patches for patch_file in patches { - std::fs::remove_file(patch_file)?; + std::fs::remove_file(patch_file).into_diagnostic()?; } "0001-bender-vendor.patch".to_string() } else { @@ -588,10 +598,10 @@ pub fn gen_plain_patch(diff: String, patch_dir: impl AsRef, no_patch: bool .iter() .all(|s| s.chars().all(char::is_numeric)) { - Err(Error::new(format!( + bail!( "Please ensure all patches start with four numbers for proper ordering in {}", patch_dir.as_ref().to_str().unwrap() - )))?; + ); } let max_number = leading_numbers .iter() @@ -602,7 +612,7 @@ pub fn gen_plain_patch(diff: String, patch_dir: impl AsRef, no_patch: bool }; // write patch - std::fs::write(patch_dir.as_ref().join(new_patch), diff)?; + std::fs::write(patch_dir.as_ref().join(new_patch), diff).into_diagnostic()?; // } } @@ -624,10 +634,10 @@ pub fn gen_format_patch( .clone() .prefix_paths(target_dir.as_ref())?; if !&to_path.exists() { - return Err(Error::new(format!( + bail!( "Could not find {}. Did you run bender vendor init?", to_path.to_str().unwrap() - ))); + ); } let git_parent = Git::new( if to_path.is_dir() { @@ -684,14 +694,14 @@ pub fn gen_format_patch( // Get staged changes in dependency let get_diff_cached = rt .block_on(async { git_parent.spawn_with(|c| c.args(&diff_args), None).await }) - .map_err(|cause| Error::chain("Failed to generate diff", cause))?; + .wrap_err("Failed to generate diff")?; if !get_diff_cached.is_empty() { // Write diff into new temp dir. TODO: pipe directly to "git apply" - let tmp_format_dir = TempDir::new()?; + let tmp_format_dir = TempDir::new().into_diagnostic()?; let tmp_format_path = tmp_format_dir.keep(); let diff_cached_path = tmp_format_path.join("staged.diff"); - std::fs::write(diff_cached_path.clone(), get_diff_cached)?; + std::fs::write(diff_cached_path.clone(), get_diff_cached).into_diagnostic()?; // Apply diff and stage changes in ghost repo rt.block_on(async { @@ -704,20 +714,18 @@ pub fn gen_format_patch( }, None) .and_then(|_| git.clone().spawn_with(|c| c.arg("add").arg("--all"), None)) .await - }).map_err(|cause| Error::chain("Could not apply staged changes on top of patched upstream repository. Did you commit all previously patched modifications?", cause))?; + }).wrap_err("Could not apply staged changes on top of patched upstream repository. Did you commit all previously patched modifications?")?; // Commit all staged changes in ghost repo rt.block_on(git.clone().commit(message))?; // Create directory in case it does not already exist - std::fs::create_dir_all(patch_dir.clone()).map_err(|cause| { - Error::chain( - format!("Failed to create directory {:?}", patch_dir.clone()), - cause, - ) - })?; + std::fs::create_dir_all(patch_dir.clone()) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}", patch_dir.clone()))?; - let mut patches = std::fs::read_dir(patch_dir.clone())? + let mut patches = std::fs::read_dir(patch_dir.clone()) + .into_diagnostic()? .map(move |f| f.unwrap().path()) .filter(|f| f.extension().is_some()) .filter(|f| f.extension().unwrap() == "patch") @@ -737,10 +745,10 @@ pub fn gen_format_patch( .iter() .all(|s| s.chars().all(char::is_numeric)) { - Err(Error::new(format!( + bail!( "Please ensure all patches start with four numbers for proper ordering in {}", patch_dir.to_str().unwrap() - )))?; + ); } leading_numbers .iter() @@ -779,14 +787,11 @@ pub fn copy_recursively( includes: &Vec, ignore: &Vec, ) -> Result<()> { - std::fs::create_dir_all(&destination).map_err(|cause| { - Error::chain( - format!("Failed to create directory {:?}", &destination), - cause, - ) - })?; - for entry in std::fs::read_dir(source)? { - let entry = entry?; + std::fs::create_dir_all(&destination) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create directory {:?}", &destination))?; + for entry in std::fs::read_dir(source).into_diagnostic()? { + let entry = entry.into_diagnostic()?; if !includes.iter().any(|include| { PathBuf::from(include).ancestors().any(|include_path| { @@ -802,18 +807,19 @@ pub fn copy_recursively( continue; } - let filetype = entry.file_type()?; - let canonical_path_filetype = - std::fs::metadata(std::fs::canonicalize(entry.path()).map_err(|cause| { - Error::chain( + let filetype = entry.file_type().into_diagnostic()?; + let canonical_path_filetype = std::fs::metadata( + std::fs::canonicalize(entry.path()) + .into_diagnostic() + .wrap_err_with(|| { format!( "Failed to canonicalize {:?}.", entry.path().to_str().unwrap() - ), - cause, - ) - })?)? - .file_type(); + ) + })?, + ) + .into_diagnostic()? + .file_type(); if filetype.is_dir() { copy_recursively( entry.path(), @@ -825,22 +831,19 @@ pub fn copy_recursively( let orig = std::fs::read_link(entry.path()); symlink_dir(orig.unwrap(), destination.as_ref().join(entry.file_name()))?; } else { - std::fs::copy(entry.path(), destination.as_ref().join(entry.file_name())).map_err( - |cause| { - Error::chain( - format!( - "Failed to copy {} to {}.", - entry.path().to_str().unwrap(), - destination - .as_ref() - .join(entry.file_name()) - .to_str() - .unwrap() - ), - cause, + std::fs::copy(entry.path(), destination.as_ref().join(entry.file_name())) + .into_diagnostic() + .wrap_err_with(|| { + format!( + "Failed to copy {} to {}.", + entry.path().to_str().unwrap(), + destination + .as_ref() + .join(entry.file_name()) + .to_str() + .unwrap() ) - }, - )?; + })?; } } Ok(()) @@ -867,10 +870,10 @@ pub fn extend_paths( #[cfg(unix)] fn symlink_dir(p: PathBuf, q: PathBuf) -> Result<()> { - Ok(std::os::unix::fs::symlink(p, q)?) + std::os::unix::fs::symlink(p, q).into_diagnostic() } #[cfg(windows)] fn symlink_dir(p: PathBuf, q: PathBuf) -> Result<()> { - Ok(std::os::windows::fs::symlink_dir(p, q)?) + std::os::windows::fs::symlink_dir(p, q).into_diagnostic() } diff --git a/src/config.rs b/src/config.rs index e54b7e220..01a57ff63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,16 +19,18 @@ use std::str::FromStr; use glob::glob; use indexmap::IndexMap; +use miette::{Context as _, IntoDiagnostic as _}; use semver; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_yaml_ng::Value; #[cfg(unix)] use subst; -use crate::diagnostic::{Diagnostics, Warnings}; -use crate::error::*; +use crate::diagnostic::Warnings; use crate::target::TargetSpec; use crate::util::*; +use crate::{Error, Result}; +use crate::{bail, err}; /// A package manifest. /// @@ -502,17 +504,14 @@ impl Validate for PartialManifest { } p } - None => return Err(Error::new("Missing package information.")), + None => bail!("Missing package information."), }; - let remotes = self + let remotes: Option> = self .remotes .map(|r| { r.validate(vctx).map_err(|(key, cause)| { - Error::chain( - format!("In remote `{key}` of package `{}`:", pkg.name), - cause, - ) + cause.wrap_err(format!("In remote `{key}` of package `{}`.", pkg.name)) }) }) .transpose()?; @@ -532,9 +531,7 @@ impl Validate for PartialManifest { [remote] => Some(*remote), // Multiple default remotes. Not allowed. _ => { - return Err(Error::new( - "Multiple remotes marked as default. Only one allowed.", - )); + bail!("Multiple remotes marked as default. Only one allowed.",); } } } @@ -558,11 +555,8 @@ impl Validate for PartialManifest { default_remote, }; - let validated = v.validate(&dep_vctx).map_err(|cause| { - Error::chain( - format!("In dependency `{dep_name}` of package `{}`:", pkg.name), - cause, - ) + let validated = v.validate(&dep_vctx).wrap_err_with(|| { + format!("In dependency `{dep_name}` of package `{}`.", pkg.name) })?; Ok((dep_name, validated)) @@ -571,9 +565,10 @@ impl Validate for PartialManifest { None => IndexMap::new(), }; let srcs = match self.sources { - Some(s) => Some(s.validate(vctx).map_err(|cause| { - Error::chain(format!("In source list of package `{}`:", pkg.name), cause) - })?), + Some(s) => Some( + s.validate(vctx) + .wrap_err_with(|| format!("In source list of package `{}`.", pkg.name))?, + ), None => None, }; let exp_inc_dirs = self.export_include_dirs.unwrap_or_default(); @@ -586,15 +581,13 @@ impl Validate for PartialManifest { }; let frozen = self.frozen.unwrap_or(false); let workspace = match self.workspace { - Some(w) => w - .validate(vctx) - .map_err(|cause| Error::chain("In workspace configuration:", cause))?, + Some(w) => w.validate(vctx).wrap_err("In workspace configuration.")?, None => Workspace::default(), }; let vendor_package = match self.vendor_package { Some(vend) => vend .validate(vctx) - .map_err(|cause| Error::chain("Unable to parse vendor_package", cause))?, + .wrap_err("Unable to parse vendor_package.")?, None => Vec::new(), }; if !vctx.pre_output { @@ -634,15 +627,10 @@ impl Validate for PartialManifest { Some(Ok(parsed_path)) } Err(cause) => { - if Diagnostics::is_suppressed("E30") { - Warnings::IgnoredPath { - cause: cause.to_string(), - } - .emit(); - None - } else { - Some(Err(Error::chain("[E30]", cause))) - } + let warning = Warnings::IgnoredPath { + cause: cause.to_string(), + }; + warning.emit_or_error().err().map(Err) } }) .collect::>>()?, @@ -732,12 +720,11 @@ impl Validate for PartialDependency { let version = self .version .map(|v| { - semver::VersionReq::parse(&v).map_err(|cause| { - Error::chain( - format!("\"{}\" is not a valid semantic version requirement.", v), - cause, - ) - }) + semver::VersionReq::parse(&v) + .into_diagnostic() + .wrap_err_with(|| { + format!("\"{}\" is not a valid semantic version requirement.", v) + }) }) .transpose()?; if !vctx.pre_output { @@ -766,7 +753,7 @@ impl Validate for PartialDependency { pass_targets, }) } else { - Err(Error::new( + Err(err!( "A dependency with only a `version` field requires a default remote to be set.", )) } @@ -785,10 +772,10 @@ impl Validate for PartialDependency { pass_targets, }) } else { - Err(Error::new(format!( + Err(err!( "Remote `{remote_name}` not found for dependency `{}`.", vctx.package_name - ))) + )) } } // Git dependencies with explicit git and version, e.g.: @@ -821,34 +808,34 @@ impl Validate for PartialDependency { path: env_path_from_string(path)?, pass_targets, }), - (_, _, Some(_), Some(_), _) => Err(Error::new(format!( + (_, _, Some(_), Some(_), _) => Err(err!( "Dependency `{}` cannot specify both `version` and `rev` fields.", vctx.package_name - ))), + )), (git, Some(_), rev, version, _) if git.is_some() || rev.is_some() || version.is_some() => { - Err(Error::new(format!( + Err(err!( "Dependency `{}` cannot simultaneously specify `path` with `git`, `rev`, or `version` fields.", vctx.package_name - ))) + )) } - (Some(_), _, None, None, _) => Err(Error::new(format!( + (Some(_), _, None, None, _) => Err(err!( "Dependency `{}` with `git` must also specify `rev` or `version`.", vctx.package_name - ))), - (None, None, None, None, _) => Err(Error::new(format!( + )), + (None, None, None, None, _) => Err(err!( "Dependency `{}` must specify at least one of `path`, `git`, `rev`, or `version`.", vctx.package_name - ))), - (Some(_), _, _, _, Some(_)) => Err(Error::new(format!( + )), + (Some(_), _, _, _, Some(_)) => Err(err!( "Dependency `{}` cannot simultaneously specify `git` and `remote`", vctx.package_name - ))), - cfg => Err(Error::new(format!( + )), + cfg => Err(err!( "Invalid configuration for dependency `{}`: {cfg:?}", vctx.package_name - ))), + )), } } } @@ -973,15 +960,10 @@ impl Validate for PartialSources { .filter_map(|path| match env_path_from_string(path.to_string()) { Ok(p) => Some(Ok(p)), Err(cause) => { - if Diagnostics::is_suppressed("E30") { - Warnings::IgnoredPath { - cause: cause.to_string(), - } - .emit(); - None - } else { - Some(Err(Error::chain("[E30]", cause))) - } + let warning = Warnings::IgnoredPath { + cause: cause.to_string(), + }; + warning.emit_or_error().err().map(Err) } }) .collect(); @@ -989,21 +971,15 @@ impl Validate for PartialSources { let external_flist_list: Result)>> = external_flists? .into_iter() .map(|filename| { - let file = File::open(&filename).map_err(|cause| { - Error::chain( - format!("Unable to open external flist file {:?}", filename), - cause, - ) + let file = File::open(&filename).into_diagnostic().wrap_err_with(|| { + format!("Unable to open external flist file {:?}", filename) })?; let reader = BufReader::new(file); let lines: Vec = reader .lines() .map(|line| { - line.map_err(|cause| { - Error::chain( - format!("Error reading external flist file {:?}", filename), - cause, - ) + line.into_diagnostic().wrap_err_with(|| { + format!("Error reading external flist file {:?}", filename) }) }) .collect::>>()?; @@ -1121,15 +1097,10 @@ impl Validate for PartialSources { _ => unreachable!(), }, Err(cause) => { - if Diagnostics::is_suppressed("E30") { - Warnings::IgnoredPath { - cause: cause.to_string(), - } - .emit(); - None - } else { - Some(Err(Error::chain("[E30]", cause))) - } + let warning = Warnings::IgnoredPath { + cause: cause.to_string(), + }; + warning.emit_or_error().err().map(Err) } } } @@ -1168,15 +1139,10 @@ impl Validate for PartialSources { .filter_map(|path| match env_path_from_string(path.to_string()) { Ok(p) => Some(Ok(p)), Err(cause) => { - if Diagnostics::is_suppressed("E30") { - Warnings::IgnoredPath { - cause: cause.to_string(), - } - .emit(); - None - } else { - Some(Err(Error::chain("[E30]", cause))) - } + let warning = Warnings::IgnoredPath { + cause: cause.to_string(), + }; + warning.emit_or_error().err().map(Err) } }) .collect::>>()?; @@ -1223,10 +1189,10 @@ impl Validate for PartialSources { external_flists: None, override_files: None, extra: _, - } => Err(Error::new( + } => Err(err!( "Only a single source with a single type is supported.", )), - _ => Err(Error::new( + _ => Err(err!( "Do not mix `sv`, `v`, or `vhd` with `files`, `target`, `include_dirs`, and `defines`.", )), } @@ -1370,15 +1336,14 @@ impl GlobFile for PartialSourceFile { | PartialSourceFile::VhdlFile(ref path) => { // Check if glob patterns used if path.contains("*") || path.contains("?") { - let glob_matches = glob(path).map_err(|cause| { - Error::chain(format!("Invalid glob pattern for {:?}", path), cause) - })?; + let glob_matches = glob(path) + .into_diagnostic() + .wrap_err_with(|| format!("Invalid glob pattern for {:?}", path))?; let out = glob_matches .map(|glob_match| { let file_str = glob_match - .map_err(|cause| { - Error::chain(format!("Glob match failed for {:?}", path), cause) - })? + .into_diagnostic() + .wrap_err_with(|| format!("Glob match failed for {:?}", path))? .to_str() .unwrap() .to_string(); @@ -1623,22 +1588,22 @@ impl Validate for PartialConfig { Ok(Config { database: match self.database { Some(db) => env_path_from_string(db)?, - None => return Err(Error::new("Database directory not configured")), + None => bail!("Database directory not configured"), }, git: match self.git { Some(git) => git, - None => return Err(Error::new("Git command or path to binary not configured")), + None => bail!("Git command or path to binary not configured"), }, overrides: match self.overrides { - Some(d) => d.validate(vctx).map_err(|(key, cause)| { - Error::chain(format!("In override `{}`:", key), cause) - })?, + Some(d) => d + .validate(vctx) + .map_err(|(key, cause)| cause.wrap_err(format!("In override `{key}`.")))?, None => IndexMap::new(), }, plugins: match self.plugins { Some(d) => d .validate(vctx) - .map_err(|(key, cause)| Error::chain(format!("In plugin `{}`:", key), cause))?, + .map_err(|(key, cause)| cause.wrap_err(format!("In plugin `{key}`.")))?, None => IndexMap::new(), }, git_throttle: self.git_throttle, @@ -1756,17 +1721,17 @@ impl Validate for PartialVendorPackage { Ok(VendorPackage { name: match self.name { Some(name) => name, - None => return Err(Error::new("external import name missing")), + None => bail!("external import name missing"), }, target_dir: match self.target_dir { Some(target_dir) => env_path_from_string(target_dir)?, - None => return Err(Error::new("external import target dir missing")), + None => bail!("external import target dir missing"), }, upstream: match self.upstream { - Some(upstream) => upstream.validate(vctx).map_err(|cause| { - Error::chain("Unable to parse external import upstream", cause) - })?, - None => return Err(Error::new("external import upstream missing")), + Some(upstream) => upstream + .validate(vctx) + .wrap_err("Unable to parse external import upstream.")?, + None => bail!("external import upstream missing"), }, mapping: self.mapping.unwrap_or_default(), patch_dir: match self.patch_dir { @@ -1823,7 +1788,7 @@ impl Validate for PartialPassedTarget { target: self.target.unwrap_or_default(), pass: match self.pass { Some(p) => p.to_lowercase(), - None => return Err(Error::new("passed target missing pass value")), + None => bail!("passed target missing pass value"), }, }) } @@ -1878,10 +1843,10 @@ impl Validate for RemoteConfig { } 1 => raw_url.to_string(), _ => { - return Err(Error::new(format!( + bail!( "Remote URL '{}' contains multiple '{{}}' placeholders. Only one is allowed.", raw_url - ))); + ); } }; @@ -1931,12 +1896,9 @@ pub enum LockedSource { #[cfg(unix)] fn env_string_from_string(path_str: String) -> Result { - subst::substitute(&path_str, &subst::Env).map_err(|cause| { - Error::chain( - format!("Unable to substitute with env: {}", path_str), - cause, - ) - }) + subst::substitute(&path_str, &subst::Env) + .into_diagnostic() + .wrap_err_with(|| format!("Unable to substitute with env: {}", path_str)) } #[cfg(windows)] diff --git a/src/diagnostic.rs b/src/diagnostic.rs index 0e7a9b5e0..9ae63b5fd 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -4,16 +4,62 @@ use std::collections::HashSet; use std::fmt; use std::path::PathBuf; +use std::sync::atomic::AtomicBool; use std::sync::{Mutex, OnceLock}; use indicatif::MultiProgress; -use miette::{Diagnostic, ReportHandler}; +use miette::{Diagnostic, MietteDiagnostic, ReportHandler, Severity}; use owo_colors::Style; use thiserror::Error; use crate::{fmt_dim, fmt_field, fmt_path, fmt_pkg, fmt_version, fmt_with_style}; static GLOBAL_DIAGNOSTICS: OnceLock = OnceLock::new(); +pub static ENABLE_DEBUG: AtomicBool = AtomicBool::new(false); + +/// Emit a diagnostic message. +#[macro_export] +macro_rules! diagnostic { + ($prefix:expr; $($arg:tt)*) => { + $crate::diagnostic::Diagnostics::eprintln(&format!("{:>14} {}", $prefix, format!($($arg)*))) + } +} + +/// Print an error. +#[macro_export] +macro_rules! errorln { + ($($arg:tt)*) => { $crate::diagnostic!($crate::fmt_with_style!("Error", owo_colors::Style::new().red().bold()); $($arg)*); } +} + +/// Print an informational note. +#[macro_export] +macro_rules! infoln { + ($($arg:tt)*) => { $crate::diagnostic!($crate::fmt_with_style!("Info", owo_colors::Style::new().white().bold()); $($arg)*); } +} + +/// Print debug information. Omitted in release builds. +#[macro_export] +#[cfg(debug_assertions)] +macro_rules! debugln { + ($($arg:tt)*) => { + if $crate::diagnostic::ENABLE_DEBUG.load(std::sync::atomic::Ordering::Relaxed) { + $crate::diagnostic!($crate::fmt_with_style!("Debug", owo_colors::Style::new().blue().bold()); $($arg)*); + } + } +} + +/// Print debug information. Omitted in release builds. +#[macro_export] +#[cfg(not(debug_assertions))] +macro_rules! debugln { + ($($arg:tt)*) => {}; +} + +/// Format and print stage progress. +#[macro_export] +macro_rules! stageln { + ($stage_name:expr, $($arg:tt)*) => { $crate::diagnostic!($crate::fmt_with_style!($stage_name, owo_colors::Style::new().green().bold()); $($arg)*); } +} /// A diagnostics manager that handles warnings (and errors). #[derive(Debug)] @@ -106,6 +152,34 @@ impl Warnings { let report = miette::Report::new(self.clone()); Diagnostics::eprintln(&format!("{report:?}")); } + + /// Checks if the warning is suppressed as an error. If so, it emits the warning. + /// Otherwise, it returns the warning as an error. + pub fn emit_or_error(self) -> miette::Result<()> { + let warning_code = self + .code() + .expect("All warning diagnostics must define a code") + .to_string(); + let error_code = format!( + "E{}", + warning_code + .strip_prefix('W') + .expect("Warning code must start with `W`") + ); + if Diagnostics::is_suppressed(&error_code) { + self.emit(); + Ok(()) + } else { + // Construct a dynamic diagnostic with the same message, help, but with the error code and severity. + let mut err = MietteDiagnostic::new(self.to_string()) + .with_code(error_code) + .with_severity(Severity::Error); + if let Some(help) = self.help().map(|h| h.to_string()) { + err = err.with_help(help); + } + Err(err.into()) + } + } } pub struct DiagnosticRenderer; @@ -133,7 +207,21 @@ impl ReportHandler for DiagnosticRenderer { // We collect all footer lines into a vector. let mut annotations: Vec = Vec::new(); - // First, we write the help message(s) if any + let mut cause = std::error::Error::source(diagnostic); + while let Some(current_cause) = cause { + annotations.push(format!( + "{} {}", + fmt_with_style!("caused by:", Style::new().bold()), + fmt_dim!( + current_cause + .to_string() + .replace("\x1b[0m", "\x1b[0m\x1b[2m") + ) + )); + cause = current_cause.source(); + } + + // Then, we write the help message(s) if any if let Some(help) = diagnostic.help() { let help_str = help.to_string(); for line in help_str.lines() { @@ -224,27 +312,6 @@ pub enum Warnings { )] DirtyGitDependency(String, PathBuf), - // TODO(fischeti): This is part of an error, not a warning. Could be converted to an Error in the future. - #[error("Failed to initialize git database.")] - #[diagnostic( - code(W07), - help("Please ensure the url is correct and you have access to the repository. {}", - if *is_ssh { - "\nEnsure your SSH keys are set up correctly." - } else { - "" - }) - )] - GitInitFailed { is_ssh: bool }, - - // TODO(fischeti): This is part of an error, not a warning. Could be converted to an Error in the future. - #[error("Revision {} not found in repository {}.", fmt_version!(.0), fmt_pkg!(.1))] - #[diagnostic( - code(W08), - help("Check that the revision exists in the remote repository or run `bender update`.") - )] - RevisionNotFound(String, String), - #[error("Path dependency {} inside git dependency {} detected. This is currently not fully supported. Your mileage may vary.", fmt_pkg!(pkg), fmt_pkg!(top_pkg))] #[diagnostic(code(W09))] PathDepInGitDep { pkg: String, top_pkg: String }, @@ -399,164 +466,127 @@ pub enum Warnings { #[cfg(test)] mod tests { use super::*; - use std::sync::Once; + use std::sync::{Mutex, Once}; static TEST_INIT: Once = Once::new(); + static TEST_LOCK: Mutex<()> = Mutex::new(()); /// Helper to initialize diagnostics once for the entire test run. fn setup_diagnostics() { TEST_INIT.call_once(|| { // We use an empty set for the global init in tests // or a specific set if needed. - Diagnostics::init(HashSet::from(["W02".to_string()])); + Diagnostics::init(HashSet::from(["W02".to_string(), "E30".to_string()])); }); } - #[test] - fn test_is_suppressed() { + fn with_test_lock(f: impl FnOnce()) { + let _guard = TEST_LOCK.lock().unwrap(); setup_diagnostics(); - assert!(Diagnostics::is_suppressed("W02")); - assert!(!Diagnostics::is_suppressed("W01")); + Diagnostics::get().emitted.lock().unwrap().clear(); + f(); } #[test] - fn test_suppression_works() { - setup_diagnostics(); // Assumes this suppresses W02 - let diag = Diagnostics::get(); - - let warn = Warnings::UsingConfigForOverride { - path: PathBuf::from("/example/path"), - }; - - // Clear state - diag.emitted.lock().unwrap().clear(); - - // Call emit (The Gatekeeper) - warn.clone().emit(); - - let emitted = diag.emitted.lock().unwrap(); - assert!(!emitted.contains(&warn)); - } - - #[test] - fn test_all_suppressed() { - // Since we can't re-init the GLOBAL_DIAGNOSTICS with different values - // in the same process, we test the logic via a local instance. - let diag = Diagnostics { - suppressed: HashSet::new(), - all_suppressed: true, - emitted: Mutex::new(HashSet::new()), - multiprogress: Mutex::new(None), - }; - - // Manual check of the logic inside emit() - let warn = Warnings::LocalNoFetch; - let code = warn.code().unwrap().to_string(); - assert!(diag.all_suppressed || diag.suppressed.contains(&code)); + fn test_is_suppressed() { + with_test_lock(|| { + assert!(Diagnostics::is_suppressed("W02")); + assert!(!Diagnostics::is_suppressed("W01")); + }); } #[test] - fn test_deduplication_logic() { - setup_diagnostics(); - let diag = Diagnostics::get(); - let warn1 = Warnings::NoRevisionInLockFile { - pkg: "example_pkg".into(), - }; - let warn2 = Warnings::NoRevisionInLockFile { - pkg: "other_pkg".into(), - }; - - // Clear state - diag.emitted.lock().unwrap().clear(); - - // Emit first warning - warn1.clone().emit(); - { - let emitted = diag.emitted.lock().unwrap(); - assert!(emitted.contains(&warn1)); - assert_eq!(emitted.len(), 1); - } - - // Emit second warning (different data) - warn2.clone().emit(); - { - let emitted = diag.emitted.lock().unwrap(); - assert!(emitted.contains(&warn2)); - assert_eq!(emitted.len(), 2); - } - - // Emit first warning again - warn1.clone().emit(); - { + fn test_suppression_works() { + with_test_lock(|| { + let diag = Diagnostics::get(); + let warn = Warnings::UsingConfigForOverride { + path: PathBuf::from("/example/path"), + }; + warn.clone().emit(); let emitted = diag.emitted.lock().unwrap(); - // The length should STILL be 2, because warn1 was already there - assert_eq!(emitted.len(), 2); - } - } - - #[test] - fn test_contains_code() { - let warn = Warnings::LocalNoFetch; - let code = warn.code().unwrap().to_string(); - assert_eq!(code, "W14".to_string()); + assert!(!emitted.contains(&warn)); + }); } #[test] - fn test_contains_help() { - let warn = Warnings::SkippingPackageLink( - "example_pkg".to_string(), - PathBuf::from("/example/path"), - ); - let help = warn.help().unwrap().to_string(); - assert!(help.contains("Check the existing file or directory")); + fn test_all_suppressed() { + with_test_lock(|| { + // Since we can't re-init the GLOBAL_DIAGNOSTICS with different values + // in the same process, we test the logic via a local instance. + let diag = Diagnostics { + suppressed: HashSet::new(), + all_suppressed: true, + emitted: Mutex::new(HashSet::new()), + multiprogress: Mutex::new(None), + }; + + // Manual check of the logic inside emit() + let warn = Warnings::LocalNoFetch; + let code = warn.code().unwrap().to_string(); + assert!(diag.all_suppressed || diag.suppressed.contains(&code)); + }); } #[test] - fn test_contains_no_help() { - let warn = Warnings::NoRevisionInLockFile { - pkg: "example_pkg".to_string(), - }; - let help = warn.help(); - assert!(help.is_none()); - } + fn test_deduplication_logic() { + with_test_lock(|| { + let diag = Diagnostics::get(); + let warn1 = Warnings::NoRevisionInLockFile { + pkg: "example_pkg".into(), + }; + let warn2 = Warnings::NoRevisionInLockFile { + pkg: "other_pkg".into(), + }; + + // Emit first warning + warn1.clone().emit(); + { + let emitted = diag.emitted.lock().unwrap(); + assert!(emitted.contains(&warn1)); + assert_eq!(emitted.len(), 1); + } - #[test] - fn test_stderr_contains_code() { - setup_diagnostics(); - let warn = Warnings::LocalNoFetch; - let code = warn.code().unwrap().to_string(); - let report = format!("{:?}", miette::Report::new(warn)); - assert!(report.contains(&code)); - } + // Emit second warning (different data) + warn2.clone().emit(); + { + let emitted = diag.emitted.lock().unwrap(); + assert!(emitted.contains(&warn2)); + assert_eq!(emitted.len(), 2); + } - #[test] - fn test_stderr_contains_help() { - setup_diagnostics(); - let warn = Warnings::SkippingPackageLink( - "example_pkg".to_string(), - PathBuf::from("/example/path"), - ); - let report = format!("{:?}", miette::Report::new(warn)); - assert!(report.contains("Check the existing file or directory")); + // Emit first warning again + warn1.clone().emit(); + { + let emitted = diag.emitted.lock().unwrap(); + // The length should STILL be 2, because warn1 was already there + assert_eq!(emitted.len(), 2); + } + }); } #[test] - fn test_stderr_contains_no_help() { - setup_diagnostics(); - let warn = Warnings::NoRevisionInLockFile { - pkg: "example_pkg".to_string(), - }; - let report = format!("{:?}", miette::Report::new(warn)); - assert!(!report.contains("help:")); + fn test_emit_or_error_suppressed() { + with_test_lock(|| { + let diag = Diagnostics::get(); + let warning = Warnings::IgnoredPath { + cause: "bad env var".to_string(), + }; + assert!(warning.clone().emit_or_error().is_ok()); + let emitted = diag.emitted.lock().unwrap(); + assert!(emitted.contains(&warning)); + }); } #[test] - fn test_stderr_contains_two_help() { - setup_diagnostics(); - let warn = - Warnings::NotAGitDependency("example_dep".to_string(), PathBuf::from("/example/path")); - let report = format!("{:?}", miette::Report::new(warn)); - let help_count = report.matches("help:").count(); - assert_eq!(help_count, 2); + fn test_emit_or_error_not_suppressed() { + with_test_lock(|| { + let warning = Warnings::FileMissing { + path: PathBuf::from("/definitely/missing/file.sv"), + }; + let err = warning.emit_or_error().expect_err("expected error"); + let report = format!("{err:?}"); + assert!(report.contains("E31")); + assert!(report.contains("doesn't exist")); + }); } } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 02a1b9cc9..000000000 --- a/src/error.rs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2017 ETH Zurich -// Fabian Schuiki - -//! Error chaining and reporting facilities. - -use std; -use std::fmt; -use std::sync::Arc; -use std::sync::atomic::AtomicBool; - -use owo_colors::Style; - -pub static ENABLE_DEBUG: AtomicBool = AtomicBool::new(false); - -/// Print an error. -#[macro_export] -macro_rules! errorln { - ($($arg:tt)*) => { $crate::diagnostic!($crate::error::Severity::Error; $($arg)*); } -} - -/// Print an informational note. -#[macro_export] -macro_rules! infoln { - ($($arg:tt)*) => { $crate::diagnostic!($crate::error::Severity::Info; $($arg)*); } -} - -/// Print debug information. Omitted in release builds. -#[macro_export] -#[cfg(debug_assertions)] -macro_rules! debugln { - ($($arg:tt)*) => { - if $crate::error::ENABLE_DEBUG.load(std::sync::atomic::Ordering::Relaxed) { - $crate::diagnostic!($crate::error::Severity::Debug; $($arg)*); - } - } -} - -/// Format and print stage progress. -#[macro_export] -macro_rules! stageln { - ($stage_name:expr, $($arg:tt)*) => { $crate::diagnostic!($crate::error::Severity::Stage($stage_name); $($arg)*); } -} - -/// Print debug information. Omitted in release builds. -#[macro_export] -#[cfg(not(debug_assertions))] -macro_rules! debugln { - ($($arg:tt)*) => {}; -} - -/// Emit a diagnostic message. -#[macro_export] -macro_rules! diagnostic { - ($severity:expr; $($arg:tt)*) => { - $crate::diagnostic::Diagnostics::eprintln(&format!("{} {}", $severity, format!($($arg)*))) - } -} - -/// The severity of a diagnostic message. -#[derive(PartialEq, Eq)] -pub enum Severity { - Debug, - Info, - Error, - Stage(&'static str), -} - -impl fmt::Display for Severity { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (severity, style) = match *self { - Severity::Error => ("Error:", Style::new().red().bold()), - Severity::Info => ("Info:", Style::new().white().bold()), - Severity::Debug => ("Debug:", Style::new().blue().bold()), - Severity::Stage(name) => (name, Style::new().green().bold()), - }; - write!(f, "{:>14}", crate::fmt_with_style!(severity, style)) - } -} - -/// A result with our custom `Error` type. -pub type Result = std::result::Result; - -/// An error message with optional underlying cause. -#[derive(Debug)] -pub struct Error { - /// A formatted error message. - pub msg: String, - /// An optional underlying cause. - pub cause: Option>, -} - -impl Error { - /// Create a new error without cause. - pub fn new>(msg: S) -> Error { - Error { - msg: msg.into(), - cause: None, - } - } - - /// Create a new error with cause. - pub fn chain(msg: S, cause: E) -> Error - where - S: Into, - E: std::error::Error + Send + Sync + 'static, - { - Error { - msg: msg.into(), - cause: Some(Arc::new(cause)), - } - } -} - -impl std::error::Error for Error { - fn description(&self) -> &str { - &self.msg - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - match self.cause { - Some(ref b) => Some(b.as_ref()), - None => None, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.msg)?; - if let Some(ref c) = self.cause { - write!(f, " {}", c)? - } - Ok(()) - } -} - -impl From for String { - fn from(err: Error) -> String { - format!("{}", err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Error { - Error::chain("Cannot startup runtime.".to_string(), err) - } -} diff --git a/src/git.rs b/src/git.rs index f67546c4e..9f35f0290 100644 --- a/src/git.rs +++ b/src/git.rs @@ -6,11 +6,13 @@ #![deny(missing_docs)] use std::ffi::OsStr; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::Arc; use futures::TryFutureExt; +use miette::{Context as _, Diagnostic, IntoDiagnostic as _}; +use thiserror::Error; use tokio::io::AsyncReadExt; use tokio::process::Command; use tokio::sync::Semaphore; @@ -19,7 +21,31 @@ use walkdir::WalkDir; use crate::progress::{ProgressHandler, monitor_stderr}; use crate::debugln; -use crate::error::*; +use crate::err; +use crate::{Error, Result}; + +#[derive(Debug, Error, Diagnostic)] +enum GitSpawnError { + #[error("Failed to spawn git command `{}` in directory {:?}.", .0, .1)] + Spawn(String, PathBuf, #[source] std::io::Error), + #[error("Failed to spawn git command `{}` in directory {:?}.", .0, .1)] + #[diagnostic(help("Please consider increasing your `ulimit -n`."))] + TooManyOpenFiles(String, PathBuf, #[source] std::io::Error), +} + +fn format_command(cmd: &Command) -> String { + let std_cmd = cmd.as_std(); + let program = std_cmd.get_program().to_string_lossy(); + let args = std_cmd + .get_args() + .map(|arg| arg.to_string_lossy().to_string()) + .collect::>(); + if args.is_empty() { + program.to_string() + } else { + format!("{program} {}", args.join(" ")) + } +} /// A git repository. /// @@ -66,7 +92,6 @@ impl<'ctx> Git<'ctx> { /// /// If `check` is false, the stdout will be returned regardless of the /// command's exit code. - #[allow(clippy::format_push_string)] pub async fn spawn( self, mut cmd: Command, @@ -74,7 +99,12 @@ impl<'ctx> Git<'ctx> { pb: Option, ) -> Result { // Acquire the throttle semaphore - let permit = self.throttle.clone().acquire_owned().await.unwrap(); + let permit = self + .throttle + .clone() + .acquire_owned() + .await + .into_diagnostic()?; // Configure pipes for streaming cmd.stdout(Stdio::piped()); @@ -84,6 +114,7 @@ impl<'ctx> Git<'ctx> { // This ensures git fails immediately with a specific error message // instead of hanging indefinitely if auth is missing. cmd.env("GIT_TERMINAL_PROMPT", "0"); + let command = format_command(&cmd); // Spawn the child process let mut child = cmd.spawn().map_err(|cause| { @@ -92,10 +123,17 @@ impl<'ctx> Git<'ctx> { .to_lowercase() .contains("too many open files") { - eprintln!("Please consider increasing your `ulimit -n`..."); - Error::chain("Failed to spawn child process.", cause) + Error::from(GitSpawnError::TooManyOpenFiles( + command.clone(), + self.path.to_path_buf(), + cause, + )) } else { - Error::chain("Failed to spawn child process.", cause) + Error::from(GitSpawnError::Spawn( + command.clone(), + self.path.to_path_buf(), + cause, + )) } })?; @@ -103,7 +141,10 @@ impl<'ctx> Git<'ctx> { // Setup Streaming for Stderr (Progress + Error Collection) // We need to capture stderr in case the command fails, so we collect it while parsing. - let stderr = child.stderr.take().unwrap(); + let stderr = child + .stderr + .take() + .ok_or_else(|| err!("Failed to capture git stderr",))?; // Spawn a background task to handle stderr so it doesn't block let stderr_handle = tokio::spawn(async move { @@ -113,52 +154,41 @@ impl<'ctx> Git<'ctx> { // Read Stdout (for the success return value) let mut stdout_buffer = Vec::new(); - if let Some(mut stdout) = child.stdout.take() { - // We just read all of stdout. - if let Err(e) = stdout.read_to_end(&mut stdout_buffer).await { - return Err(Error::chain("Failed to read stdout", e)); - } - } + let mut stdout = child + .stdout + .take() + .ok_or_else(|| err!("Failed to capture git stdout.",))?; + stdout + .read_to_end(&mut stdout_buffer) + .await + .into_diagnostic()?; // Wait for child process to finish - let status = child - .wait() - .await - .map_err(|e| Error::chain("Failed to wait on child", e))?; + let status = child.wait().await.into_diagnostic()?; // Join the stderr task to get the error log - let collected_stderr = stderr_handle - .await - .unwrap_or_else(|_| String::from("")); + let collected_stderr = stderr_handle.await.into_diagnostic()?; // We can release the throttle here since we're done with the process drop(permit); // Process the output based on success and check flag if status.success() || !check { - String::from_utf8(stdout_buffer).map_err(|cause| { - Error::chain( - format!( - "Output of git command ({:?}) in directory {:?} is not valid UTF-8.", - cmd, self.path - ), - cause, - ) - }) + String::from_utf8(stdout_buffer) + .into_diagnostic() + .wrap_err("Output of git command is not valid UTF-8.") } else { - let mut msg = format!("Git command ({:?}) in directory {:?}", cmd, self.path); - match status.code() { - Some(code) => msg.push_str(&format!(" failed with exit code {}", code)), - None => msg.push_str(" failed"), + let exit = match status.code() { + Some(code) => format!("exit code {}", code), + None => String::from("unknown exit status"), }; - - // Use the stderr we collected in the background task - if !collected_stderr.is_empty() { - msg.push_str(":\n\n"); - msg.push_str(&collected_stderr); - } - - Err(Error::new(msg)) + Err(err!( + help = format!("git failed with stderr output:\n{}", collected_stderr), + "Git command `{}` failed in directory {:?} with {}.", + command, + self.path, + exit + )) } } @@ -202,7 +232,11 @@ impl<'ctx> Git<'ctx> { let mut cmd = Command::new(self.git); cmd.current_dir(self.path); f(&mut cmd); - cmd.spawn()?.wait().await?; + cmd.spawn() + .into_diagnostic()? + .wait() + .await + .into_diagnostic()?; Ok(()) } @@ -233,7 +267,7 @@ impl<'ctx> Git<'ctx> { })) }) .await - .map_err(|cause| Error::chain("Failed to join blocking task", cause))? + .into_diagnostic()? } /// Fetch the tags and refs of a remote. diff --git a/src/lib.rs b/src/lib.rs index 38b9c910b..6830c1776 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ pub mod cli; pub mod cmd; pub mod config; pub mod diagnostic; -pub mod error; pub mod git; pub mod lockfile; pub mod progress; @@ -13,3 +12,8 @@ pub mod sess; pub mod src; pub mod target; pub mod util; + +pub use miette::{bail, ensure, miette as err}; + +pub type Error = miette::Report; +pub type Result = miette::Result; diff --git a/src/lockfile.rs b/src/lockfile.rs index 86c0aafaf..8d193e961 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -4,18 +4,22 @@ use std; use std::path::Path; +use miette::{Context as _, IntoDiagnostic as _}; + +use crate::Result; use crate::config::{Locked, LockedPackage, LockedSource, PrefixPaths}; use crate::debugln; -use crate::error::*; /// Read a lock file. pub fn read_lockfile(path: &Path, root_dir: &Path) -> Result { debugln!("read_lockfile: {:?}", path); use std::fs::File; let file = File::open(path) - .map_err(|cause| Error::chain(format!("Cannot open lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot open lockfile {:?}.", path))?; let locked_loaded: Result = serde_yaml_ng::from_reader(file) - .map_err(|cause| Error::chain(format!("Syntax error in lockfile {:?}.", path), cause)); + .into_diagnostic() + .wrap_err_with(|| format!("Syntax error in lockfile {:?}.", path)); // Make relative paths absolute Ok(Locked { packages: locked_loaded? @@ -70,8 +74,10 @@ pub fn write_lockfile(locked: &Locked, path: &Path, root_dir: &Path) -> Result<( use std::fs::File; let file = File::create(path) - .map_err(|cause| Error::chain(format!("Cannot create lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot create lockfile {:?}.", path))?; serde_yaml_ng::to_writer(file, &adapted_locked) - .map_err(|cause| Error::chain(format!("Cannot write lockfile {:?}.", path), cause))?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot write lockfile {:?}.", path))?; Ok(()) } diff --git a/src/progress.rs b/src/progress.rs index a039677a4..3a8691df8 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -11,7 +11,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use regex::Regex; use tokio::io::{AsyncReadExt, BufReader}; -use crate::{errorln, fmt_completed, fmt_dim, fmt_pkg, fmt_stage}; +use crate::{fmt_completed, fmt_dim, fmt_pkg, fmt_stage}; static RE_GIT: OnceLock = OnceLock::new(); @@ -300,14 +300,8 @@ impl ProgressHandler { target_pb.set_position(percent as u64); } // Handle errors by finishing and clearing the target bar, then logging the error - GitProgress::Error(err_msg) => { + GitProgress::Error(_) => { target_pb.finish_and_clear(); - errorln!( - "{} {}: {}", - "Error during git operation of", - fmt_pkg!(&self.name), - err_msg - ); } _ => {} } diff --git a/src/resolver.rs b/src/resolver.rs index 4c9983abd..364f877f9 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -17,6 +17,7 @@ use std::process::Command as SysCommand; use futures::future::join_all; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; +use miette::IntoDiagnostic as _; use semver::{Version, VersionReq}; use tabwriter::TabWriter; use tokio::runtime::Runtime; @@ -24,13 +25,14 @@ use tokio::runtime::Runtime; use crate::config::{self, Locked, LockedPackage, LockedSource, Manifest}; use crate::debugln; use crate::diagnostic::Warnings; -use crate::error::*; use crate::sess::{ DependencyConstraint, DependencyRef, DependencySource, DependencyVersion, DependencyVersions, Session, SessionIo, }; use crate::target::TargetSpec; use crate::util::{version_req_bottom_bound, version_req_top_bound}; +use crate::{Error, Result}; +use crate::{bail, err}; use crate::{fmt_path, fmt_pkg, fmt_version}; /// A dependency resolver. @@ -73,7 +75,7 @@ impl<'ctx> DependencyResolver<'ctx> { ignore_checkout: bool, keep_locked: IndexSet<&'ctx String>, ) -> Result { - let rt = Runtime::new()?; + let rt = Runtime::new().into_diagnostic()?; let io = SessionIo::new(self.sess); // Load the dependencies in the lockfile. @@ -116,7 +118,8 @@ impl<'ctx> DependencyResolver<'ctx> { .arg("status") .arg("--porcelain") .current_dir(dir.as_ref().unwrap().path()) - .output()? + .output() + .into_diagnostic()? .stdout .is_empty()) { @@ -202,10 +205,10 @@ impl<'ctx> DependencyResolver<'ctx> { } } DependencyVersions::Registry(ref _rv) => { - return Err(Error::new(format!( + bail!( "Registry dependencies such as `{}` not yet supported.", name - ))); + ); } DependencyVersions::Git(ref gv) => { let url = match sess_src { @@ -334,18 +337,20 @@ impl<'ctx> DependencyResolver<'ctx> { // Register the versions. for (name, id) in names { if name == self.sess.manifest.package.name { - return Err(Error::new(format!( + bail!( "Please ensure no packages with same name as top package\n\ \tCurrently {} is called in {}", - name, calling_package - ))); + name, + calling_package + ); } if name == calling_package { - return Err(Error::new(format!( + bail!( "Please ensure no packages with same name as calling package\n\ \tCurrently {} is called in {}", - name, calling_package - ))); + name, + calling_package + ); } self.register_dependency(name, id, versions[&id].clone()); } @@ -543,10 +548,10 @@ impl<'ctx> DependencyResolver<'ctx> { let ids: IndexSet = match src.versions { DependencyVersions::Path => (0..1).collect(), DependencyVersions::Registry(ref _rv) => { - return Err(Error::new(format!( + bail!( "Resolution of registry dependency `{}` not yet implemented", dep.name - ))); + ); } DependencyVersions::Git(ref gv) => (0..gv.revs.len()).collect(), }; @@ -747,13 +752,13 @@ impl<'ctx> DependencyResolver<'ctx> { } else { "".to_string() }; - return Err(Error::new(format!( + bail!( "Dependency `{}` from `{}` cannot satisfy requirement `{}`.{}", dep.name, self.sess.dependency_source(*id), con, additional_str - ))); + ); } Ok((*id, indices_list)) }) @@ -926,10 +931,10 @@ impl<'ctx> DependencyResolver<'ctx> { let mut buffer = String::new(); io::stdin().read_line(&mut buffer).unwrap(); if buffer.starts_with('\n') { - break Err(Error::new(format!( + break Err(err!( "Dependency requirements conflict with each other on dependency `{}`. Manual resolution aborted.\n", name - ))); + )); } let choice = match buffer.trim().parse::() { Ok(u) => u, @@ -993,7 +998,7 @@ impl<'ctx> DependencyResolver<'ctx> { } } } else { - Err(Error::new(msg)) + Err(err!(msg)) } } @@ -1062,25 +1067,27 @@ impl<'ctx> DependencyResolver<'ctx> { revs.sort(); Ok(revs) } - (DepCon::Version(_con), DepVer::Registry(_rv)) => Err(Error::new(format!( + (DepCon::Version(_con), DepVer::Registry(_rv)) => Err(err!( "Constraints on registry dependency `{}` not implemented", name - ))), + )), // Handle the error cases. // TODO: These need to improve a lot! - (con, &DepVer::Git(..)) => Err(Error::new(format!( + (con, &DepVer::Git(..)) => Err(err!( "Requirement `{}` cannot be applied to git dependency `{}`", - con, name - ))), - (con, &DepVer::Registry(..)) => Err(Error::new(format!( + con, + name + )), + (con, &DepVer::Registry(..)) => Err(err!( "Requirement `{}` cannot be applied to registry dependency `{}`", - con, name - ))), - (_, &DepVer::Path) => Err(Error::new(format!( + con, + name + )), + (_, &DepVer::Path) => Err(err!( "`{}` is not declared as a path dependency everywhere.", name - ))), + )), } } @@ -1121,10 +1128,10 @@ impl<'ctx> DependencyResolver<'ctx> { )), }) } - DependencyVersions::Registry(..) => Err(Error::new(format!( + DependencyVersions::Registry(..) => Err(err!( "Version picking for registry dependency `{}` not yet implemented", dep.name - ))), + )), }) .collect::)>>>>()?; let src_map = src_map @@ -1143,10 +1150,10 @@ impl<'ctx> DependencyResolver<'ctx> { ) } None => { - return Err(Error::new(format!( + bail!( "No versions available for `{}`. This may be due to a conflict in the dependency requirements.", dep.name - ))); + ); } } } diff --git a/src/sess.rs b/src/sess.rs index e826d93cb..ceb063b22 100644 --- a/src/sess.rs +++ b/src/sess.rs @@ -28,7 +28,9 @@ use futures::TryFutureExt; use futures::future::join_all; use indexmap::{IndexMap, IndexSet}; use indicatif::MultiProgress; +use miette::{Context as _, Diagnostic, IntoDiagnostic as _}; use semver::Version; +use thiserror::Error; use tokio::sync::Semaphore; use typed_arena::Arena; @@ -36,12 +38,40 @@ use crate::cli::read_manifest; use crate::config::{self, Config, Manifest, PartialManifest}; use crate::debugln; use crate::diagnostic::{Diagnostics, Warnings}; -use crate::error::*; use crate::git::Git; use crate::progress::{GitProgressOps, ProgressHandler}; use crate::src::SourceGroup; use crate::target::TargetSpec; use crate::util::try_modification_time; +use crate::{Error, Result}; +use crate::{bail, ensure, err}; + +#[derive(Debug, Error, Diagnostic)] +pub(crate) enum SessionErrors { + #[error("Failed to checkout commit {} for {} given in Bender.lock.", .0, .1)] + #[diagnostic(help( + "Check that the revision exists in the remote repository or run `bender update`." + ))] + LockedRevisionCheckout( + String, + String, + #[source] Box, + ), + #[error("Failed to {} git database.", .0)] + #[diagnostic(help( + "Please ensure the url is correct and you have access to the repository. {}", + if *.1 { + "\nEnsure your SSH keys are set up correctly." + } else { + "" + } + ))] + GitDatabaseAccess( + &'static str, + bool, + #[source] Box, + ), +} /// A session on the command line. /// @@ -204,10 +234,11 @@ impl<'ctx> Session<'ctx> { v.iter() .map(|name| match names.get(name) { Some(id) => Ok(*id), - None => Err(Error::new(format!( - "Failed to match dependency {}, please run `bender update`!", + None => Err(err!( + help = "Please run `bender update`", + "Failed to match dependency {}", name - ))), + )), }) .collect::>(), ) @@ -228,11 +259,11 @@ impl<'ctx> Session<'ctx> { let mut pending = IndexSet::new(); for name in self.manifest.dependencies.keys() { if !(names.contains_key(name)) { - return Err(Error::new(format!( - "`Bender.yml` contains dependency `{}` but `Bender.lock` does not.\n\ - \tYou may need to run `bender update`.", + bail!( + help = "You may need to run `bender update`", + "`Bender.yml` contains dependency `{}` but `Bender.lock` does not", name - ))); + ); } } pending.extend(self.manifest.dependencies.keys().map(|name| names[name])); @@ -259,11 +290,11 @@ impl<'ctx> Session<'ctx> { for element in pending.iter() { pend_str.push(self.dependency_name(*element)); } - return Err(Error::new(format!( - "a cyclical dependency was discovered, likely relates to one of {:?}.\n\ - \tPlease ensure no dependency loops.", + bail!( + help = "Please ensure no dependency loops", + "A cyclical dependency was discovered, likely relates to one of {:?}", pend_str - ))); + ); } } debugln!("sess: topological ranks {:#?}", ranks); @@ -319,10 +350,10 @@ impl<'ctx> Session<'ctx> { let result = self.names.lock().unwrap().get(name).copied(); match result { Some(id) => Ok(id), - None => Err(Error::new(format!( + None => Err(err!( "Dependency `{}` does not exist. Did you forget to add it to the manifest?", name - ))), + )), } } @@ -574,15 +605,9 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { .join("db") .join(db_name); let db_dir = self.sess.intern_path(db_dir); - match std::fs::create_dir_all(db_dir) { - Ok(_) => (), - Err(cause) => { - return Err(Error::chain( - format!("Failed to create git database directory {:?}.", db_dir), - cause, - )); - } - }; + std::fs::create_dir_all(db_dir) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to create git database directory {:?}.", db_dir))?; let git = Git::new( db_dir, &self.sess.config.git, @@ -591,12 +616,11 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { // Either initialize the repository or update it if needed. if !db_dir.join("config").exists() { - if self.sess.local_only { - return Err(Error::new( - "Bender --local argument set, unable to initialize git dependency. \n\ - \tPlease update without --local, or provide a path to the missing dependency.", - )); - } + ensure!( + !self.sess.local_only, + help = "Re-run without `--local`, or provide a local path dependency.", + "Cannot initialize git dependency while `--local` is enabled." + ); // Initialize. self.sess.stats.num_database_init.increment(); // The progress bar object for cloning. We only use it for the @@ -623,14 +647,11 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }) .await .map_err(move |cause| { - Warnings::GitInitFailed { - is_ssh: url.contains("git@"), - } - .emit(); - Error::chain( - format!("Failed to initialize git database in {:?}.", db_dir), - cause, - ) + Error::from(SessionErrors::GitDatabaseAccess( + "initialize", + url.contains("git@"), + cause.into(), + )) }) .map(move |_| git) } else { @@ -663,14 +684,11 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }) .await .map_err(move |cause| { - Warnings::GitInitFailed { - is_ssh: url.contains("git@"), - } - .emit(); - Error::chain( - format!("Failed to update git database in {:?}.", db_dir), - cause, - ) + Error::from(SessionErrors::GitDatabaseAccess( + "update", + url.contains("git@"), + cause.into(), + )) }) .map(move |_| git) } @@ -960,12 +978,9 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }; if path.exists() && clear == CheckoutState::ToClone { debugln!("checkout_git: clear checkout {:?}", path); - std::fs::remove_dir_all(path).map_err(|cause| { - Error::chain( - format!("Failed to remove checkout directory {:?}.", path), - cause, - ) - }) + std::fs::remove_dir_all(path) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to remove checkout directory {:?}.", path)) } else { Ok(()) }?; @@ -1021,18 +1036,14 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }, None, ) + .await .map_err(|cause| { - Warnings::RevisionNotFound(revision.to_string(), name.to_string()) - .emit(); - Error::chain( - format!( - "Failed to checkout commit {} for {} given in Bender.lock.\n", - revision, name - ), - cause, - ) + Error::from(SessionErrors::LockedRevisionCheckout( + revision.to_string(), + name.to_string(), + cause.into(), + )) }) - .await } }?; // Check if the revision is reachable from any upstream branch or @@ -1220,15 +1231,16 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { if let Some(full_sub_data) = sub_data.clone() { if !tmp_path.exists() { - std::fs::create_dir_all(tmp_path.clone())?; + std::fs::create_dir_all(tmp_path.clone()).into_diagnostic()?; } let mut sub_file = std::fs::OpenOptions::new() .write(true) .truncate(true) .create(true) - .open(tmp_path.join(format!("{}_manifest.yml", dep.0)))?; - writeln!(&mut sub_file, "{}", full_sub_data)?; - sub_file.flush()?; + .open(tmp_path.join(format!("{}_manifest.yml", dep.0))) + .into_diagnostic()?; + writeln!(&mut sub_file, "{}", full_sub_data).into_diagnostic()?; + sub_file.flush().into_diagnostic()?; } *dep.1 = config::Dependency::Path { @@ -1241,26 +1253,22 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { let _manifest: Result<_> = match sub_data { Some(data) => { let partial: config::PartialManifest = serde_yaml_ng::from_str(&data) - .map_err(|cause| { - Error::chain( + .into_diagnostic() + .wrap_err_with(|| { format!( "Syntax error in manifest of dependency `{}` at \ revision `{}`.", dep.0, used_git_rev - ), - cause, - ) - })?; - let mut full = partial.validate_ignore_sources().map_err(|cause| { - Error::chain( + ) + })?; + let mut full = + partial.validate_ignore_sources().wrap_err_with(|| { format!( "Error in manifest of dependency `{}` at revision \ `{}`.", dep.0, used_git_rev - ), - cause, - ) - })?; + ) + })?; self.sub_dependency_fixing( &mut full.dependencies, full.package.name.clone(), @@ -1345,13 +1353,11 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { .join("tmp") .join(format!("{}_manifest.yml", dep.name)), ) - .map_err(|cause| { - Error::chain(format!("Cannot open manifest {:?}.", path), cause) - })?; - let partial: PartialManifest = - serde_yaml_ng::from_reader(file).map_err(|cause| { - Error::chain(format!("Syntax error in manifest {:?}.", path), cause) - })?; + .into_diagnostic() + .wrap_err_with(|| format!("Cannot open manifest {:?}.", path))?; + let partial: PartialManifest = serde_yaml_ng::from_reader(file) + .into_diagnostic() + .wrap_err_with(|| format!("Syntax error in manifest {:?}.", path))?; match partial.validate_ignore_sources() { Ok(m) => { @@ -1367,23 +1373,12 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { Err(e) => Err(e), } } else { - if !(Diagnostics::is_suppressed("E32")) { - if let DepSrc::Path(ref path) = dep.source { - if !path.exists() { - if Diagnostics::is_suppressed("E32") { - Warnings::DepPathMissing { - pkg: dep.name.clone(), - path: path.to_path_buf(), - } - .emit(); - } else { - return Err(Error::new(format!( - "[E32] Path {:?} for dependency {:?} does not exist.", - path, dep.name - ))); - } - } + if !path.exists() { + Warnings::DepPathMissing { + pkg: dep.name.clone(), + path: path.to_path_buf(), } + .emit_or_error()?; } Warnings::ManifestNotFound { pkg: dep.name.clone(), @@ -1407,24 +1402,19 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { let manifest: Result<_> = match data { Some(data) => { let partial: config::PartialManifest = serde_yaml_ng::from_str(&data) - .map_err(|cause| { - Error::chain( - format!( - "Syntax error in manifest of dependency `{}` at \ + .into_diagnostic() + .wrap_err_with(|| { + format!( + "Syntax error in manifest of dependency `{}` at \ revision `{}`.", - dep_name, rev - ), - cause, + dep_name, rev ) })?; - let mut full = partial.validate_ignore_sources().map_err(|cause| { - Error::chain( - format!( - "Error in manifest of dependency `{}` at revision \ + let mut full = partial.validate_ignore_sources().wrap_err_with(|| { + format!( + "Error in manifest of dependency `{}` at revision \ `{}`.", - dep_name, rev - ), - cause, + dep_name, rev ) })?; @@ -1717,12 +1707,12 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }, ); if let Some(existing) = existing { - return Err(Error::new(format!( + bail!( "Plugin `{}` declared by multiple packages (`{}` and `{}`).", name, self.sess.dependency_name(existing.package), self.sess.dependency_name(package), - ))); + ); } } } @@ -1738,12 +1728,12 @@ impl<'io, 'sess: 'io, 'ctx: 'sess> SessionIo<'sess, 'ctx> { }, ); if let Some(existing) = existing { - return Err(Error::new(format!( + bail!( "Plugin `{}` declared by multiple packages (`{}` and `{}`).", name, self.sess.dependency_name(existing.package), "root", - ))); + ); } } let allocd = self.sess.arenas.plugins.alloc(plugins) as &_; diff --git a/src/src.rs b/src/src.rs index f3ccd3a1f..68d4b761f 100644 --- a/src/src.rs +++ b/src/src.rs @@ -14,9 +14,9 @@ use std::path::Path; use indexmap::{IndexMap, IndexSet}; use serde::{Serialize, Serializer}; +use crate::Error; use crate::config::{Validate, ValidationContext}; -use crate::diagnostic::{Diagnostics, Warnings}; -use crate::error::Error; +use crate::diagnostic::Warnings; use crate::target::{TargetSet, TargetSpec}; use semver; @@ -48,7 +48,7 @@ pub struct SourceGroup<'ctx> { impl<'ctx> Validate for SourceGroup<'ctx> { type Output = SourceGroup<'ctx>; type Error = Error; - fn validate(self, vctx: &ValidationContext) -> crate::error::Result> { + fn validate(self, vctx: &ValidationContext) -> crate::Result> { Ok(SourceGroup { files: self .files @@ -400,19 +400,14 @@ impl<'ctx> Validate for SourceFile<'ctx> { let env_path_buf = crate::config::env_path_from_string(path.to_string_lossy().to_string())?; let exists = env_path_buf.exists() && env_path_buf.is_file(); - if exists || Diagnostics::is_suppressed("E31") { - if !exists { - Warnings::FileMissing { - path: env_path_buf.clone(), - } - .emit(); - } + if exists { Ok(SourceFile::File(path, ty)) } else { - Err(Error::new(format!( - "[E31] File {} doesn't exist", - env_path_buf.to_string_lossy() - ))) + Warnings::FileMissing { + path: env_path_buf.clone(), + } + .emit_or_error()?; + Ok(SourceFile::File(path, ty)) } } SourceFile::Group(srcs) => Ok(SourceFile::Group(Box::new(srcs.validate(vctx)?))), diff --git a/src/target.rs b/src/target.rs index 08aebc8c0..cac52c510 100644 --- a/src/target.rs +++ b/src/target.rs @@ -14,9 +14,11 @@ use std::fmt; use std::str::FromStr; use indexmap::IndexSet; +use miette::Context as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::error::*; +use crate::{Error, Result}; +use crate::{bail, err}; /// A target specification. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Default, Hash)] @@ -77,12 +79,7 @@ impl FromStr for TargetSpec { partial: None, next, }; - parse(&mut lexer).map_err(|cause| { - Error::chain( - format!("Syntax error in target specification `{}`.", s), - cause, - ) - }) + parse(&mut lexer).wrap_err_with(|| format!("Syntax error in target specification `{}`.", s)) } } @@ -233,7 +230,7 @@ where Some(')') => return Some(Ok(TargetToken::RParen)), Some(',') => return Some(Ok(TargetToken::Comma)), Some(c) if c.is_whitespace() => (), - Some(c) => return Some(Err(Error::new(format!("Invalid character `{}`.", c)))), + Some(c) => return Some(Err(err!("Invalid character `{}`.", c))), None => return None, } } @@ -255,10 +252,10 @@ where } Some(Ok(TargetToken::Ident(name))) => { if name.contains(':') { - return Err(Error::new("Targets names cannot contain colons (`:`).")); + bail!("Targets names cannot contain colons (`:`)."); } if name.starts_with('-') { - return Err(Error::new("Target names cannot start with a hyphen (`-`).")); + bail!("Target names cannot start with a hyphen (`-`)."); } TargetSpec::Name(name) } @@ -296,23 +293,21 @@ where match lexer.next() { Some(Ok(ref tkn)) if tkn == &token => Ok(()), Some(Err(e)) => Err(e), - _ => Err(Error::new(msg)), + _ => Err(err!("{}", msg)), } } fn parse_wrong(wrong: Option>) -> Result { match wrong { - Some(Ok(TargetToken::All)) => Err(Error::new("Unexpected `all` keyword.")), - Some(Ok(TargetToken::Any)) => Err(Error::new("Unexpected `any` keyword.")), - Some(Ok(TargetToken::Not)) => Err(Error::new("Unexpected `not` keyword.")), - Some(Ok(TargetToken::Ident(name))) => { - Err(Error::new(format!("Unexpected identifier `{}`.", name))) - } - Some(Ok(TargetToken::LParen)) => Err(Error::new("Unexpected `(`.")), - Some(Ok(TargetToken::RParen)) => Err(Error::new("Unexpected `)`.")), - Some(Ok(TargetToken::Comma)) => Err(Error::new("Unexpected `,`.")), + Some(Ok(TargetToken::All)) => Err(err!("Unexpected `all` keyword.")), + Some(Ok(TargetToken::Any)) => Err(err!("Unexpected `any` keyword.")), + Some(Ok(TargetToken::Not)) => Err(err!("Unexpected `not` keyword.")), + Some(Ok(TargetToken::Ident(name))) => Err(err!("Unexpected identifier `{}`.", name)), + Some(Ok(TargetToken::LParen)) => Err(err!("Unexpected `(`.")), + Some(Ok(TargetToken::RParen)) => Err(err!("Unexpected `)`.")), + Some(Ok(TargetToken::Comma)) => Err(err!("Unexpected `,`.")), Some(Err(e)) => Err(e), - None => Err(Error::new("Unexpected end of string.")), + None => Err(err!("Unexpected end of string.")), } } diff --git a/src/util.rs b/src/util.rs index b05281ccf..278925d01 100644 --- a/src/util.rs +++ b/src/util.rs @@ -21,7 +21,8 @@ use serde::ser::{Serialize, Serializer}; /// Re-export owo_colors for use in macros. pub use owo_colors::{OwoColorize, Stream, Style}; -use crate::error::*; +use crate::Result; +use crate::bail; /// A type that cannot be materialized. #[derive(Debug)] @@ -316,10 +317,7 @@ pub fn version_req_top_bound(req: &VersionReq) -> Result> { build: semver::BuildMetadata::EMPTY, }, _ => { - return Err(Error::new(format!( - "Cannot extract top bound from version requirement: {}", - req - ))); + bail!("Cannot extract top bound from version requirement: {req}"); } }; if top_bound > max_caret { @@ -349,10 +347,7 @@ pub fn version_req_top_bound(req: &VersionReq) -> Result> { } } _ => { - return Err(Error::new(format!( - "Cannot extract top bound from version requirement: {}", - req - ))); + bail!("Cannot extract top bound from version requirement: {req}"); } } } @@ -416,10 +411,7 @@ pub fn version_req_bottom_bound(req: &VersionReq) -> Result> { // No lower bound } _ => { - return Err(Error::new(format!( - "Cannot extract bottom bound from version requirement: {}", - req - ))); + bail!("Cannot extract bottom bound from version requirement: {req}"); } } }