diff --git a/src/error.rs b/src/error.rs index d97d0b02..acc75255 100644 --- a/src/error.rs +++ b/src/error.rs @@ -41,6 +41,9 @@ pub enum ErrorKind { #[fail(display = "Self-update failure")] #[cfg(feature = "self-update")] SelfUpdate, + + #[fail(display = "A step should be skipped")] + SkipStep, } impl Fail for Error { diff --git a/src/main.rs b/src/main.rs index 11b16fcb..998c4fbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,14 +15,16 @@ use self::report::Report; use self::steps::*; use self::terminal::*; use failure::{Fail, ResultExt}; +use log::debug; use std::borrow::Cow; use std::env; +use std::fmt::Debug; use std::io; #[cfg(windows)] use std::path::PathBuf; use std::process::exit; -fn execute<'a, F, M>(func: F, no_retry: bool) -> Result, Error> +fn execute_legacy<'a, F, M>(func: F, no_retry: bool) -> Result, Error> where M: Into>, F: Fn() -> Option<(M, bool)>, @@ -48,6 +50,42 @@ where Ok(None) } +fn execute<'a, F, M>(report: &mut Report<'a>, key: M, func: F, no_retry: bool) -> Result<(), Error> +where + F: Fn() -> Result<(), Error>, + M: Into> + Debug, +{ + debug!("Executing {:?}", key); + + loop { + match func() { + Ok(()) => { + report.push_result(Some((key, true))); + break; + } + Err(ref e) if e.kind() == ErrorKind::SkipStep => { + break; + } + Err(_) => { + let interrupted = ctrlc::interrupted(); + if interrupted { + ctrlc::unset_interrupted(); + } + + let should_ask = interrupted || !no_retry; + let should_retry = should_ask && should_retry(interrupted).context(ErrorKind::Retry)?; + + if !should_retry { + report.push_result(Some((key, false))); + break; + } + } + } + } + + Ok(()) +} + fn run() -> Result<(), Error> { ctrlc::set_handler(); @@ -96,7 +134,10 @@ fn run() -> Result<(), Error> { #[cfg(windows)] { if powershell.profile().is_some() && config.should_run(Step::Powershell) { - report.push_result(execute(|| powershell.update_modules(run_type), config.no_retry())?); + report.push_result(execute_legacy( + || powershell.update_modules(run_type), + config.no_retry(), + )?); } } @@ -108,7 +149,7 @@ fn run() -> Result<(), Error> { if config.should_run(Step::System) { match &distribution { Ok(distribution) => { - report.push_result(execute( + report.push_result(execute_legacy( || distribution.upgrade(&sudo, config.cleanup(), run_type), config.no_retry(), )?); @@ -117,28 +158,31 @@ fn run() -> Result<(), Error> { println!("Error detecting current distribution: {}", e); } } - report.push_result(execute(|| linux::run_etc_update(&sudo, run_type), config.no_retry())?); + report.push_result(execute_legacy( + || linux::run_etc_update(&sudo, run_type), + config.no_retry(), + )?); } } #[cfg(windows)] - report.push_result(execute(|| windows::run_chocolatey(run_type), config.no_retry())?); + report.push_result(execute_legacy(|| windows::run_chocolatey(run_type), config.no_retry())?); #[cfg(windows)] - report.push_result(execute(|| windows::run_scoop(run_type), config.no_retry())?); + report.push_result(execute_legacy(|| windows::run_scoop(run_type), config.no_retry())?); #[cfg(unix)] - report.push_result(execute( + report.push_result(execute_legacy( || unix::run_homebrew(config.cleanup(), run_type), config.no_retry(), )?); #[cfg(target_os = "freebsd")] - report.push_result(execute( + report.push_result(execute_legacy( || freebsd::upgrade_packages(&sudo, run_type), config.no_retry(), )?); #[cfg(unix)] - report.push_result(execute(|| unix::run_nix(run_type), config.no_retry())?); + report.push_result(execute_legacy(|| unix::run_nix(run_type), config.no_retry())?); if config.should_run(Step::Emacs) { #[cfg(unix)] @@ -182,48 +226,97 @@ fn run() -> Result<(), Error> { } } for repo in git_repos.repositories() { - report.push_result(execute(|| git.pull(&repo, run_type), config.no_retry())?); + report.push_result(execute_legacy(|| git.pull(&repo, run_type), config.no_retry())?); } #[cfg(unix)] { - report.push_result(execute(|| unix::run_zplug(&base_dirs, run_type), config.no_retry())?); - report.push_result(execute(|| unix::run_fisher(&base_dirs, run_type), config.no_retry())?); - report.push_result(execute(|| tmux::run_tpm(&base_dirs, run_type), config.no_retry())?); + report.push_result(execute_legacy( + || unix::run_zplug(&base_dirs, run_type), + config.no_retry(), + )?); + report.push_result(execute_legacy( + || unix::run_fisher(&base_dirs, run_type), + config.no_retry(), + )?); + report.push_result(execute_legacy( + || tmux::run_tpm(&base_dirs, run_type), + config.no_retry(), + )?); } - report.push_result(execute( + execute( + &mut report, + "rustup", || generic::run_rustup(&base_dirs, run_type), config.no_retry(), - )?); - report.push_result(execute(|| generic::run_cargo_update(run_type), config.no_retry())?); + )?; + execute( + &mut report, + "cargo", + || generic::run_cargo_update(run_type), + config.no_retry(), + )?; if config.should_run(Step::Emacs) { - report.push_result(execute(|| generic::run_emacs(&base_dirs, run_type), config.no_retry())?); + execute( + &mut report, + "Emacs", + || generic::run_emacs(&base_dirs, run_type), + config.no_retry(), + )?; } - report.push_result(execute(|| generic::run_opam_update(run_type), config.no_retry())?); - report.push_result(execute(|| generic::run_vcpkg_update(run_type), config.no_retry())?); - report.push_result(execute(|| generic::run_pipx_update(run_type), config.no_retry())?); - report.push_result(execute(|| generic::run_jetpack(run_type), config.no_retry())?); + execute( + &mut report, + "opam", + || generic::run_opam_update(run_type), + config.no_retry(), + )?; + execute( + &mut report, + "vcpkg", + || generic::run_vcpkg_update(run_type), + config.no_retry(), + )?; + execute( + &mut report, + "pipx", + || generic::run_pipx_update(run_type), + config.no_retry(), + )?; + execute( + &mut report, + "jetpak", + || generic::run_jetpack(run_type), + config.no_retry(), + )?; if config.should_run(Step::Vim) { - report.push_result(execute(|| vim::upgrade_vim(&base_dirs, run_type), config.no_retry())?); - report.push_result(execute( + report.push_result(execute_legacy( + || vim::upgrade_vim(&base_dirs, run_type), + config.no_retry(), + )?); + report.push_result(execute_legacy( || vim::upgrade_neovim(&base_dirs, run_type), config.no_retry(), )?); } - report.push_result(execute( + report.push_result(execute_legacy( || node::run_npm_upgrade(&base_dirs, run_type), config.no_retry(), )?); - report.push_result(execute( + execute( + &mut report, + "composer", || generic::run_composer_update(&base_dirs, run_type), config.no_retry(), + )?; + report.push_result(execute_legacy( + || node::yarn_global_update(run_type), + config.no_retry(), )?); - report.push_result(execute(|| node::yarn_global_update(run_type), config.no_retry())?); #[cfg(not(any( target_os = "freebsd", @@ -231,21 +324,26 @@ fn run() -> Result<(), Error> { target_os = "netbsd", target_os = "dragonfly" )))] - report.push_result(execute(|| generic::run_apm(run_type), config.no_retry())?); + execute(&mut report, "apm", || generic::run_apm(run_type), config.no_retry())?; if config.should_run(Step::Gem) { - report.push_result(execute(|| generic::run_gem(&base_dirs, run_type), config.no_retry())?); + execute( + &mut report, + "gem", + || generic::run_gem(&base_dirs, run_type), + config.no_retry(), + )?; } #[cfg(target_os = "linux")] { - report.push_result(execute(|| linux::flatpak_update(run_type), config.no_retry())?); - report.push_result(execute(|| linux::run_snap(&sudo, run_type), config.no_retry())?); + report.push_result(execute_legacy(|| linux::flatpak_update(run_type), config.no_retry())?); + report.push_result(execute_legacy(|| linux::run_snap(&sudo, run_type), config.no_retry())?); } if let Some(commands) = config.commands() { for (name, command) in commands { - report.push_result(execute( + report.push_result(execute_legacy( || Some((name, generic::run_custom_command(&name, &command, run_type).is_ok())), config.no_retry(), )?); @@ -254,21 +352,24 @@ fn run() -> Result<(), Error> { #[cfg(target_os = "linux")] { - report.push_result(execute(|| linux::run_fwupdmgr(run_type), config.no_retry())?); - report.push_result(execute(|| linux::run_needrestart(&sudo, run_type), config.no_retry())?); + report.push_result(execute_legacy(|| linux::run_fwupdmgr(run_type), config.no_retry())?); + report.push_result(execute_legacy( + || linux::run_needrestart(&sudo, run_type), + config.no_retry(), + )?); } #[cfg(target_os = "macos")] { if config.should_run(Step::System) { - report.push_result(execute(|| macos::upgrade_macos(run_type), config.no_retry())?); + report.push_result(execute_legacy(|| macos::upgrade_macos(run_type), config.no_retry())?); } } #[cfg(target_os = "freebsd")] { if config.should_run(Step::System) { - report.push_result(execute( + report.push_result(execute_legacy( || freebsd::upgrade_freebsd(&sudo, run_type), config.no_retry(), )?); @@ -278,7 +379,10 @@ fn run() -> Result<(), Error> { #[cfg(windows)] { if config.should_run(Step::System) { - report.push_result(execute(|| powershell.windows_update(run_type), config.no_retry())?); + report.push_result(execute_legacy( + || powershell.windows_update(run_type), + config.no_retry(), + )?); } } diff --git a/src/steps/generic.rs b/src/steps/generic.rs index eaf19882..15640153 100644 --- a/src/steps/generic.rs +++ b/src/steps/generic.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use crate::error::{Error, ErrorKind}; use crate::executor::{CommandExt, RunType}; use crate::terminal::print_separator; use crate::utils::{self, PathExt}; @@ -8,223 +8,120 @@ use std::process::Command; const EMACS_UPGRADE: &str = include_str!("emacs.el"); -#[must_use] -pub fn run_cargo_update(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(cargo_update) = utils::which("cargo-install-update") { - print_separator("Cargo"); +pub fn run_cargo_update(run_type: RunType) -> Result<(), Error> { + let cargo_update = utils::require("cargo-install-update")?; - let success = || -> Result<(), Error> { - run_type - .execute(cargo_update) - .args(&["install-update", "--git", "--all"]) - .check_run()?; + print_separator("Cargo"); - Ok(()) - }() - .is_ok(); - - return Some(("Cargo", success)); - } - - None + run_type + .execute(cargo_update) + .args(&["install-update", "--git", "--all"]) + .check_run() } -#[must_use] -pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(gem) = utils::which("gem") { - if base_dirs.home_dir().join(".gem").exists() { - print_separator("RubyGems"); +pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<(), Error> { + let gem = utils::require("gem")?; + base_dirs.home_dir().join(".gem").require()?; - let success = || -> Result<(), Error> { - run_type.execute(&gem).args(&["update", "--user-install"]).check_run()?; + print_separator("RubyGems"); - Ok(()) - }() - .is_ok(); - - return Some(("RubyGems", success)); - } - } - None + run_type.execute(&gem).args(&["update", "--user-install"]).check_run() } -#[must_use] -pub fn run_emacs(base_dirs: &BaseDirs, run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(emacs) = utils::which("emacs") { - if let Some(init_file) = base_dirs.home_dir().join(".emacs.d/init.el").if_exists() { - print_separator("Emacs"); +pub fn run_emacs(base_dirs: &BaseDirs, run_type: RunType) -> Result<(), Error> { + let emacs = utils::require("emacs")?; + let init_file = base_dirs.home_dir().join(".emacs.d/init.el").require()?; - let success = || -> Result<(), Error> { - run_type - .execute(&emacs) - .args(&["--batch", "-l", init_file.to_str().unwrap(), "--eval", EMACS_UPGRADE]) - .check_run()?; + print_separator("Emacs"); - Ok(()) - }() - .is_ok(); - - return Some(("Emacs", success)); - } - } - None + run_type + .execute(&emacs) + .args(&["--batch", "-l", init_file.to_str().unwrap(), "--eval", EMACS_UPGRADE]) + .check_run() } -#[must_use] #[cfg(not(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly" )))] -pub fn run_apm(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(apm) = utils::which("apm") { - print_separator("Atom Package Manager"); +pub fn run_apm(run_type: RunType) -> Result<(), Error> { + let apm = utils::require("apm")?; - let success = || -> Result<(), Error> { - run_type - .execute(&apm) - .args(&["upgrade", "--confirm=false"]) - .check_run()?; + print_separator("Atom Package Manager"); - Ok(()) - }() - .is_ok(); - - return Some(("apm", success)); - } - - None + run_type.execute(&apm).args(&["upgrade", "--confirm=false"]).check_run() } -#[must_use] -pub fn run_rustup(base_dirs: &BaseDirs, run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(rustup) = utils::which("rustup") { - print_separator("rustup"); +pub fn run_rustup(base_dirs: &BaseDirs, run_type: RunType) -> Result<(), Error> { + let rustup = utils::require("rustup")?; - let success = || -> Result<(), Error> { - if rustup.is_descendant_of(base_dirs.home_dir()) { - run_type.execute(&rustup).args(&["self", "update"]).check_run()?; - } + print_separator("rustup"); - run_type.execute(&rustup).arg("update").check_run()?; - Ok(()) - }() - .is_ok(); - - return Some(("rustup", success)); + if rustup.is_descendant_of(base_dirs.home_dir()) { + run_type.execute(&rustup).args(&["self", "update"]).check_run()?; } - None + run_type.execute(&rustup).arg("update").check_run() } -#[must_use] -pub fn run_jetpack(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(jetpack) = utils::which("jetpack") { - print_separator("Jetpack"); +pub fn run_jetpack(run_type: RunType) -> Result<(), Error> { + let jetpack = utils::require("jetpack")?; - let success = || -> Result<(), Error> { - run_type.execute(&jetpack).args(&["global", "update"]).check_run()?; - Ok(()) - }() - .is_ok(); + print_separator("Jetpack"); - return Some(("Jetpack", success)); - } - - None + run_type.execute(&jetpack).args(&["global", "update"]).check_run() } -#[must_use] -pub fn run_opam_update(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(opam) = utils::which("opam") { - print_separator("OCaml Package Manager"); +pub fn run_opam_update(run_type: RunType) -> Result<(), Error> { + let opam = utils::require("opam")?; - let success = || -> Result<(), Error> { - run_type.execute(&opam).arg("update").check_run()?; - run_type.execute(&opam).arg("upgrade").check_run()?; - Ok(()) - }() - .is_ok(); + print_separator("OCaml Package Manager"); - return Some(("OPAM", success)); - } - - None + run_type.execute(&opam).arg("update").check_run()?; + run_type.execute(&opam).arg("upgrade").check_run() } -#[must_use] -pub fn run_vcpkg_update(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(vcpkg) = utils::which("vcpkg") { - print_separator("vcpkg"); +pub fn run_vcpkg_update(run_type: RunType) -> Result<(), Error> { + let vcpkg = utils::require("vcpkg")?; + print_separator("vcpkg"); - let success = || -> Result<(), Error> { - run_type - .execute(&vcpkg) - .args(&["upgrade", "--no-dry-run"]) - .check_run()?; - Ok(()) - }() - .is_ok(); - - return Some(("vcpkg", success)); - } - - None + run_type.execute(&vcpkg).args(&["upgrade", "--no-dry-run"]).check_run() } -#[must_use] -pub fn run_pipx_update(run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(pipx) = utils::which("pipx") { - print_separator("pipx"); +pub fn run_pipx_update(run_type: RunType) -> Result<(), Error> { + let pipx = utils::require("pipx")?; + print_separator("pipx"); - let success = || -> Result<(), Error> { - run_type.execute(&pipx).arg("upgrade-all").check_run()?; - Ok(()) - }() - .is_ok(); - - return Some(("pipx", success)); - } - - None + run_type.execute(&pipx).arg("upgrade-all").check_run() } -#[must_use] pub fn run_custom_command(name: &str, command: &str, run_type: RunType) -> Result<(), Error> { print_separator(name); - run_type.execute("sh").arg("-c").arg(command).check_run()?; + run_type.execute("sh").arg("-c").arg(command).check_run() +} + +pub fn run_composer_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<(), Error> { + let composer = utils::require("composer")?; + let composer_home = Command::new(&composer) + .args(&["global", "config", "--absolute", "home"]) + .check_output() + .map_err(|_| Error::from(ErrorKind::SkipStep)) + .map(PathBuf::from) + .and_then(|p| p.require())?; + + if !composer_home.is_descendant_of(base_dirs.home_dir()) { + Err(ErrorKind::SkipStep)?; + } + + print_separator("Composer"); + + run_type.execute(&composer).args(&["global", "update"]).check_run()?; + + if let Some(valet) = utils::which("valet") { + run_type.execute(&valet).arg("install").check_run()?; + } Ok(()) } - -#[must_use] -pub fn run_composer_update(base_dirs: &BaseDirs, run_type: RunType) -> Option<(&'static str, bool)> { - if let Some(composer) = utils::which("composer") { - let composer_home = Command::new(&composer) - .args(&["global", "config", "--absolute", "home"]) - .check_output() - .map(PathBuf::from); - - if let Ok(composer_home) = composer_home { - if composer_home.is_descendant_of(base_dirs.home_dir()) { - print_separator("Composer"); - - let success = || -> Result<(), Error> { - run_type.execute(&composer).args(&["global", "update"]).check_run()?; - - if let Some(valet) = utils::which("valet") { - run_type.execute(&valet).arg("install").check_run()?; - } - - Ok(()) - }() - .is_ok(); - - return Some(("Composer", success)); - } - } - } - - None -} diff --git a/src/utils.rs b/src/utils.rs index 83b77a59..723d54d2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -32,6 +32,9 @@ where { fn if_exists(self) -> Option; fn is_descendant_of(&self, ancestor: &Path) -> bool; + + /// Returns the path if it exists or ErrorKind::SkipStep otherwise + fn require(self) -> Result; } impl PathExt for PathBuf { @@ -46,6 +49,14 @@ impl PathExt for PathBuf { fn is_descendant_of(&self, ancestor: &Path) -> bool { self.iter().zip(ancestor.iter()).all(|(a, b)| a == b) } + + fn require(self) -> Result { + if self.exists() { + Ok(self) + } else { + Err(ErrorKind::SkipStep)? + } + } } pub fn which + Debug>(binary_name: T) -> Option { @@ -151,3 +162,21 @@ mod tests { assert_eq!("C:\\hi", humanize("//?/C:/hi")); } } + +pub fn require + Debug>(binary_name: T) -> Result { + match which_crate::which(&binary_name) { + Ok(path) => { + debug!("Detected {:?} as {:?}", &path, &binary_name); + Ok(path) + } + Err(e) => match e.kind() { + which_crate::ErrorKind::CannotFindBinaryPath => { + debug!("Cannot find {:?}", &binary_name); + Err(ErrorKind::SkipStep)? + } + _ => { + panic!("Detecting {:?} failed: {}", &binary_name, e); + } + }, + } +}