Files
topgrade/src/main.rs

484 lines
13 KiB
Rust
Raw Normal View History

2018-11-18 14:25:16 +02:00
extern crate console;
extern crate directories;
2018-11-18 14:25:16 +02:00
extern crate env_logger;
2018-06-04 22:33:39 +03:00
extern crate failure;
extern crate failure_derive;
#[cfg(unix)]
extern crate lazy_static;
extern crate log;
#[cfg(unix)]
extern crate nix;
#[cfg(feature = "self-update")]
2018-11-07 14:31:44 +02:00
extern crate self_update;
extern crate serde;
2018-11-18 14:25:16 +02:00
extern crate serde_derive;
extern crate shellexpand;
2018-11-18 14:25:16 +02:00
extern crate structopt;
extern crate toml;
extern crate walkdir;
2018-11-18 14:25:16 +02:00
extern crate which;
2018-05-29 23:48:30 +03:00
2018-11-12 11:13:43 +02:00
#[cfg(target_os = "freebsd")]
mod freebsd;
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "linux")]
mod linux;
2018-06-28 12:16:54 +03:00
#[cfg(target_os = "macos")]
mod macos;
#[cfg(unix)]
2018-09-04 11:05:54 +03:00
mod tmux;
#[cfg(unix)]
2018-06-28 12:16:54 +03:00
mod unix;
#[cfg(target_os = "windows")]
mod windows;
mod config;
mod ctrlc;
2018-08-26 16:12:59 +03:00
mod executor;
2018-08-19 14:45:23 +03:00
mod generic;
2018-06-28 12:16:54 +03:00
mod git;
2018-08-19 14:45:23 +03:00
mod node;
2018-06-03 18:04:58 +03:00
mod report;
2018-05-31 16:00:01 +03:00
mod terminal;
2018-06-17 11:43:25 +03:00
mod utils;
2018-06-07 08:51:16 +03:00
mod vim;
2018-05-30 07:53:19 +03:00
2018-08-22 10:43:32 +03:00
use self::config::Config;
use self::git::{Git, Repositories};
2018-08-24 21:52:17 +03:00
use self::report::Report;
2018-08-22 10:43:32 +03:00
use self::terminal::Terminal;
2018-06-04 22:33:39 +03:00
use failure::Error;
2018-11-18 14:25:16 +02:00
use failure_derive::Fail;
2018-08-25 22:19:38 +03:00
use std::borrow::Cow;
2018-06-20 21:05:49 +03:00
use std::env;
use std::io::ErrorKind;
#[cfg(all(unix, feature = "self-update"))]
use std::os::unix::process::CommandExt;
2018-06-17 11:43:25 +03:00
use std::process::exit;
#[cfg(all(unix, feature = "self-update"))]
use std::process::Command;
2018-09-06 14:42:56 +03:00
use structopt::StructOpt;
2018-05-29 23:48:30 +03:00
#[derive(Fail, Debug)]
#[fail(display = "A step failed")]
struct StepFailed;
2018-07-07 02:18:19 +03:00
#[derive(Fail, Debug)]
#[fail(display = "Cannot find the user base directories")]
struct NoBaseDirectories;
#[derive(Fail, Debug)]
#[fail(display = "Process Interrupted")]
pub struct Interrupted;
struct ExecutionContext {
terminal: Terminal,
}
fn execute<'a, F, M>(func: F, execution_context: &mut ExecutionContext) -> Result<Option<(M, bool)>, Error>
2018-08-25 22:19:38 +03:00
where
M: Into<Cow<'a, str>>,
F: Fn(&mut Terminal) -> Option<(M, bool)>,
{
while let Some((key, success)) = func(&mut execution_context.terminal) {
2018-08-25 22:19:38 +03:00
if success {
return Ok(Some((key, success)));
}
let running = ctrlc::running();
if !running {
ctrlc::set_running(true);
2018-08-25 22:19:38 +03:00
}
let should_retry = execution_context.terminal.should_retry(running).map_err(|e| {
if e.kind() == ErrorKind::Interrupted {
Error::from(Interrupted)
} else {
Error::from(e)
}
})?;
if !should_retry {
return Ok(Some((key, success)));
2018-08-25 22:19:38 +03:00
}
}
Ok(None)
2018-08-25 22:19:38 +03:00
}
#[must_use]
#[cfg(feature = "self-update")]
pub fn self_update(terminal: &mut Terminal) -> Result<(), Error> {
terminal.print_separator("Self update");
#[cfg(unix)]
let current_exe = env::current_exe();
let target = self_update::get_target()?;
let result = self_update::backends::github::Update::configure()?
.repo_owner("r-darwish")
.repo_name("topgrade")
.target(&target)
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
.show_output(false)
.show_download_progress(true)
.current_version(self_update::cargo_crate_version!())
.no_confirm(true)
.build()?
.update()?;
if let self_update::Status::Updated(version) = &result {
println!("\nTopgrade upgraded to {}", version);
} else {
println!("Topgrade is up-to-date");
}
#[cfg(unix)]
{
if result.updated() {
terminal.print_warning("Respawning...");
let err = Command::new(current_exe?).args(env::args().skip(1)).exec();
Err(err)?
}
}
Ok(())
}
fn run() -> Result<(), Error> {
ctrlc::set_handler();
2018-09-06 14:42:56 +03:00
let opt = config::Opt::from_args();
if opt.run_in_tmux && env::var("TMUX").is_err() {
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2018-09-04 11:05:54 +03:00
tmux::run_in_tmux();
2018-06-20 21:05:49 +03:00
}
}
2018-06-17 14:17:36 +03:00
env_logger::init();
let mut execution_context = ExecutionContext {
terminal: Terminal::new(),
};
2018-11-07 14:31:44 +02:00
let base_dirs = directories::BaseDirs::new().ok_or(NoBaseDirectories)?;
let git = Git::new();
let mut git_repos = Repositories::new(&git);
2018-07-07 02:18:19 +03:00
let config = Config::read(&base_dirs)?;
2018-08-24 21:52:17 +03:00
let mut report = Report::new();
2018-05-30 07:53:19 +03:00
2018-11-12 11:13:43 +02:00
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
2018-06-27 23:04:39 +03:00
let sudo = utils::which("sudo");
#[cfg(feature = "self-update")]
{
if !opt.dry_run {
if let Err(e) = self_update(&mut execution_context.terminal) {
execution_context
.terminal
.print_warning(format!("Self update error: {}", e));
}
}
}
2018-11-07 14:31:44 +02:00
2018-06-20 20:26:08 +03:00
if let Some(commands) = config.pre_commands() {
for (name, command) in commands {
generic::run_custom_command(&name, &command, &mut execution_context.terminal, opt.dry_run)?;
2018-06-20 20:26:08 +03:00
}
}
2018-08-22 22:01:06 +03:00
#[cfg(windows)]
let powershell = windows::Powershell::new();
#[cfg(windows)]
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| powershell.update_modules(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-22 22:01:06 +03:00
#[cfg(target_os = "linux")]
let distribution = linux::Distribution::detect();
2018-08-22 22:18:48 +03:00
#[cfg(target_os = "linux")]
{
2018-09-06 15:14:59 +03:00
if !opt.no_system {
match &distribution {
Ok(distribution) => {
report.push_result(execute(
|terminal| distribution.upgrade(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
}
Err(e) => {
println!("Error detecting current distribution: {}", e);
}
}
2018-10-02 13:25:02 +03:00
report.push_result(execute(
|terminal| linux::run_etc_update(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-22 22:18:48 +03:00
}
2018-06-28 07:47:51 +03:00
}
2018-08-22 22:18:48 +03:00
#[cfg(windows)]
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| windows::run_chocolatey(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-22 22:18:48 +03:00
2018-10-17 13:57:30 +03:00
#[cfg(windows)]
report.push_result(execute(
|terminal| windows::run_scoop(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-19 14:45:23 +03:00
#[cfg(unix)]
2018-09-06 14:42:56 +03:00
report.push_result(execute(
|terminal| unix::run_homebrew(terminal, opt.cleanup, opt.dry_run),
&mut execution_context,
)?);
2018-11-12 11:13:43 +02:00
#[cfg(target_os = "freebsd")]
report.push_result(execute(
|terminal| freebsd::upgrade_packages(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-10-21 13:05:49 +03:00
#[cfg(unix)]
report.push_result(execute(
|terminal| unix::run_nix(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-19 14:45:23 +03:00
2018-09-06 15:14:59 +03:00
if !opt.no_emacs {
2018-09-05 11:17:15 +03:00
git_repos.insert(base_dirs.home_dir().join(".emacs.d"));
}
2018-07-07 02:18:19 +03:00
git_repos.insert(base_dirs.home_dir().join(".vim"));
git_repos.insert(base_dirs.home_dir().join(".config/nvim"));
2018-05-30 07:53:19 +03:00
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2018-07-07 02:18:19 +03:00
git_repos.insert(base_dirs.home_dir().join(".zshrc"));
git_repos.insert(base_dirs.home_dir().join(".oh-my-zsh"));
git_repos.insert(base_dirs.home_dir().join(".tmux"));
git_repos.insert(base_dirs.home_dir().join(".config/fish"));
2018-08-27 15:22:44 +03:00
git_repos.insert(base_dirs.config_dir().join("openbox"));
}
2018-05-30 07:53:19 +03:00
2018-08-23 22:08:04 +03:00
#[cfg(windows)]
{
if let Some(profile) = powershell.profile() {
git_repos.insert(profile);
}
}
2018-09-06 15:14:59 +03:00
if !opt.no_git_repos {
if let Some(custom_git_repos) = config.git_repos() {
for git_repo in custom_git_repos {
git_repos.insert(git_repo);
}
2018-05-30 07:53:19 +03:00
}
}
for repo in git_repos.repositories() {
2018-09-06 14:42:56 +03:00
report.push_result(execute(
|terminal| git.pull(&repo, terminal, opt.dry_run),
&mut execution_context,
)?);
}
2018-06-27 23:04:39 +03:00
#[cfg(unix)]
{
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| unix::run_zplug(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-10-07 11:51:12 +03:00
|terminal| unix::run_fisher(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| tmux::run_tpm(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-05-31 16:17:22 +03:00
}
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| generic::run_rustup(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-26 16:12:59 +03:00
report.push_result(execute(
|terminal| generic::run_cargo_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-09-05 11:17:15 +03:00
2018-09-06 15:14:59 +03:00
if !opt.no_emacs {
2018-09-05 11:17:15 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| generic::run_emacs(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-09-05 11:17:15 +03:00
}
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| generic::run_opam_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-11-10 20:22:26 +02:00
report.push_result(execute(
|terminal| generic::run_vcpkg_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-10-31 13:01:57 +02:00
report.push_result(execute(
|terminal| generic::run_pipx_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-10-18 16:05:27 +03:00
report.push_result(execute(
|terminal| generic::run_jetpack(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-09-06 14:42:56 +03:00
report.push_result(execute(
|terminal| vim::upgrade_vim(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| vim::upgrade_neovim(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| node::run_npm_upgrade(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-10-02 13:45:29 +03:00
report.push_result(execute(
|terminal| generic::run_composer_update(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| node::yarn_global_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-10-04 11:26:51 +03:00
2018-10-29 14:32:33 +02:00
#[cfg(not(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| generic::run_apm(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-09-06 16:46:49 +03:00
report.push_result(execute(
|terminal| generic::run_gem(&base_dirs, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "linux")]
{
2018-08-26 16:12:59 +03:00
report.push_result(execute(
|terminal| linux::flatpak_user_update(terminal, opt.dry_run),
&mut execution_context,
)?);
report.push_result(execute(
|terminal| linux::flatpak_global_update(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-09-06 14:42:56 +03:00
report.push_result(execute(
|terminal| linux::run_snap(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-06-14 13:24:52 +03:00
}
if let Some(commands) = config.commands() {
for (name, command) in commands {
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-08-26 16:12:59 +03:00
|terminal| {
Some((
name,
2018-09-06 14:42:56 +03:00
generic::run_custom_command(&name, &command, terminal, opt.dry_run).is_ok(),
2018-08-26 16:12:59 +03:00
))
},
&mut execution_context,
)?);
}
}
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "linux")]
{
2018-08-25 22:19:38 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| linux::run_fwupdmgr(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| linux::run_needrestart(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
2018-05-31 09:19:27 +03:00
}
2018-06-27 23:04:39 +03:00
#[cfg(target_os = "macos")]
{
2018-09-06 15:14:59 +03:00
if !opt.no_system {
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| macos::upgrade_macos(terminal, opt.dry_run),
&mut execution_context,
)?);
}
2018-06-03 18:04:58 +03:00
}
2018-11-12 11:13:43 +02:00
#[cfg(target_os = "freebsd")]
{
if !opt.no_system {
report.push_result(execute(
|terminal| freebsd::upgrade_freebsd(&sudo, terminal, opt.dry_run),
&mut execution_context,
)?);
}
}
2018-08-22 22:18:48 +03:00
#[cfg(windows)]
{
2018-09-06 15:14:59 +03:00
if !opt.no_system {
2018-08-26 16:12:59 +03:00
report.push_result(execute(
2018-09-06 14:42:56 +03:00
|terminal| powershell.windows_update(terminal, opt.dry_run),
&mut execution_context,
)?);
2018-08-22 22:18:48 +03:00
}
}
2018-08-24 21:52:17 +03:00
if !report.data().is_empty() {
execution_context.terminal.print_separator("Summary");
2018-06-03 18:04:58 +03:00
2018-08-24 21:52:17 +03:00
for (key, succeeded) in report.data() {
execution_context.terminal.print_result(key, *succeeded);
2018-06-03 18:04:58 +03:00
}
#[cfg(target_os = "linux")]
{
if let Ok(distribution) = &distribution {
distribution.show_summary();
}
}
2018-11-15 11:37:08 +02:00
#[cfg(target_os = "freebsd")]
2018-11-15 15:54:24 +02:00
freebsd::audit_packages(&sudo).ok();
2018-05-29 23:48:30 +03:00
}
2018-08-24 21:52:17 +03:00
if report.data().iter().all(|(_, succeeded)| *succeeded) {
Ok(())
} else {
Err(StepFailed.into())
}
}
fn main() {
match run() {
Ok(()) => {
exit(0);
}
Err(error) => {
2018-11-07 10:18:18 +02:00
if (error.downcast_ref::<StepFailed>().is_some()) || (error.downcast_ref::<Interrupted>().is_some()) {
} else {
println!("ERROR: {}", error)
}
exit(1);
}
}
2018-05-29 23:48:30 +03:00
}