From 0f0cbc1453336dd43febf0e5fc632a7cb3d6cc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Sch=C3=B6nauer?= <37108907+DottoDev@users.noreply.github.com> Date: Thu, 3 Nov 2022 19:18:45 +0000 Subject: [PATCH 1/5] Update check-and-lint.yaml --- .github/workflows/check-and-lint.yaml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-and-lint.yaml b/.github/workflows/check-and-lint.yaml index 6eb45d48..e115078d 100644 --- a/.github/workflows/check-and-lint.yaml +++ b/.github/workflows/check-and-lint.yaml @@ -10,7 +10,10 @@ name: Check and Lint jobs: check: name: Check - runs-on: ubuntu-latest + strategy: + matrix: + platform: [ ubuntu-latest, macos-latest, windows-latest ] + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -24,7 +27,10 @@ jobs: fmt: name: Rustfmt - runs-on: ubuntu-latest + strategy: + matrix: + platform: [ ubuntu-latest, macos-latest, windows-latest ] + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -40,7 +46,10 @@ jobs: clippy: name: Clippy - runs-on: ubuntu-latest + strategy: + matrix: + platform: [ ubuntu-latest, macos-latest, windows-latest ] + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -51,5 +60,10 @@ jobs: - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + args: --all-targets --locked -- -D warnings name: Clippy Output + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-targets --locked --all-features -- -D warnings + name: Clippy (All features) Output From 2a11df40ee1d3861fd6eedcf4a8c51f4604c1a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Sch=C3=B6nauer?= <37108907+DottoDev@users.noreply.github.com> Date: Thu, 3 Nov 2022 19:40:02 +0000 Subject: [PATCH 2/5] Update check-and-lint.yaml --- .github/workflows/check-and-lint.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-and-lint.yaml b/.github/workflows/check-and-lint.yaml index e115078d..6f2155a9 100644 --- a/.github/workflows/check-and-lint.yaml +++ b/.github/workflows/check-and-lint.yaml @@ -57,13 +57,13 @@ jobs: toolchain: stable components: clippy override: true - - uses: actions-rs/clippy-check@v1 + - uses: actions-rs/cargo@v1.0.1 with: - token: ${{ secrets.GITHUB_TOKEN }} + command: clippy args: --all-targets --locked -- -D warnings name: Clippy Output - - uses: actions-rs/clippy-check@v1 + - uses: actions-rs/cargo@v1.0.1 with: - token: ${{ secrets.GITHUB_TOKEN }} + command: clippy args: --all-targets --locked --all-features -- -D warnings name: Clippy (All features) Output From 60ff0870486c48a6cbd21deb995b02468061ca08 Mon Sep 17 00:00:00 2001 From: 0xMRTT <0xMRTT@proton.me> Date: Fri, 4 Nov 2022 13:08:23 +0100 Subject: [PATCH 3/5] feat: add coc (#140) --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..89a5fc5b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +open an issue on GitHub . +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 8acdfc8d1c3ba7ecd9662c74506fe580b75cd2b6 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Sat, 5 Nov 2022 05:11:31 -0400 Subject: [PATCH 4/5] Bug fixes (#145) --- .github/workflows/crates-publish.yml | 2 +- .github/workflows/release-cross.yml | 2 +- .github/workflows/release.yml | 2 +- Cargo.lock | 87 ++++++++++++++------- Cargo.toml | 61 +++++++-------- src/config.rs | 66 +++++++++------- src/ctrlc/interrupted.rs | 7 +- src/executor.rs | 11 +-- src/main.rs | 8 +- src/steps/generic.rs | 14 ++-- src/steps/node.rs | 109 ++++++++++++++++++++++----- src/steps/os/archlinux.rs | 2 +- src/steps/os/os_release/debian | 11 +-- src/steps/os/windows.rs | 24 +++--- src/steps/powershell.rs | 11 +-- src/steps/remote/ssh.rs | 2 +- src/steps/tmux.rs | 12 ++- 17 files changed, 276 insertions(+), 155 deletions(-) diff --git a/.github/workflows/crates-publish.yml b/.github/workflows/crates-publish.yml index 2d2d0120..4d8c8056 100644 --- a/.github/workflows/crates-publish.yml +++ b/.github/workflows/crates-publish.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2022-08-03 + toolchain: stable override: true publish: diff --git a/.github/workflows/release-cross.yml b/.github/workflows/release-cross.yml index 16554df0..9a2c8eac 100644 --- a/.github/workflows/release-cross.yml +++ b/.github/workflows/release-cross.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.57.0 + toolchain: stable profile: minimal default: true override: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50e4f847..e50032c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.57.0 + toolchain: stable profile: minimal override: true components: rustfmt, clippy diff --git a/Cargo.lock b/Cargo.lock index 077b8794..2c86ccfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,24 +225,26 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.18" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", - "once_cell", + "indexmap", + "lazy_static", "strsim", "termcolor", + "textwrap", ] [[package]] name = "clap_derive" -version = "4.0.18" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -253,9 +255,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] @@ -658,9 +660,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.3.15" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" dependencies = [ "bytes", "fnv", @@ -983,14 +985,24 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", ] [[package]] @@ -1032,6 +1044,15 @@ dependencies = [ "zvariant_derive", ] +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1356,9 +1377,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -1604,6 +1625,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shellexpand" version = "2.1.2" @@ -1753,13 +1780,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", - "fastrand", "libc", + "rand", "redox_syscall", "remove_dir_all", "winapi", @@ -1784,6 +1811,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + [[package]] name = "thiserror" version = "1.0.37" @@ -1861,9 +1894,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "cd3b82e6e823a9ee7d7f64b08f8ac3d5f08ac988f23157194bd32af3f2f92767" dependencies = [ "autocfg", "bytes", @@ -1871,9 +1904,9 @@ dependencies = [ "memchr", "mio", "num_cpus", + "once_cell", "pin-project-lite", "signal-hook-registry", - "socket2", "winapi", ] @@ -1890,16 +1923,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ "bytes", "futures-core", "futures-sink", + "log", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -1935,6 +1968,7 @@ dependencies = [ "self_update", "semver", "serde", + "shell-words", "shellexpand", "strum 0.24.1", "sys-info", @@ -2202,13 +2236,12 @@ dependencies = [ [[package]] name = "which" -version = "4.3.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" dependencies = [ "either", "libc", - "once_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 85a44bae..adff3ba6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,43 +20,44 @@ path = "src/main.rs" ##name = "topgrade_lib" [dependencies] -home = "0.5" -directories = "4.0" -serde = { version = "1.0", features = ["derive"] } +home = "~0.5" +directories = "~4.0" +serde = { version = "~1.0", features = ["derive"] } toml = "0.5" -which_crate = { version = "4.1", package = "which" } -shellexpand = "2.1" -clap = { version = "4.0.18", features = ["cargo", "derive"] } -log = "0.4" -walkdir = "2.3" -console = "0.15" -lazy_static = "1.4" -chrono = "0.4" -pretty_env_logger = "0.4" -glob = "0.3" -strum = { version = "0.24", features = ["derive"] } -thiserror = "1.0" -anyhow = "1.0" -tempfile = "3.2" -cfg-if = "1.0" -tokio = { version = "1.5", features = ["process", "rt-multi-thread"] } -futures = "0.3" -regex = "1.5" -sys-info = "0.9" -semver = "1.0" +which_crate = { version = "~4.1", package = "which" } +shellexpand = "~2.1" +clap = { version = "~3.1", features = ["cargo", "derive"] } +log = "~0.4" +walkdir = "~2.3" +console = "~0.15" +lazy_static = "~1.4" +chrono = "~0.4" +pretty_env_logger = "~0.4" +glob = "~0.3" +strum = { version = "~0.24", features = ["derive"] } +thiserror = "~1.0" +anyhow = "~1.0" +tempfile = "~3.2" +cfg-if = "~1.0" +tokio = { version = "~1.5", features = ["process", "rt-multi-thread"] } +futures = "~0.3" +regex = "~1.5" +sys-info = "~0.9" +semver = "~1.0" +shell-words = "~1.1" [target.'cfg(target_os = "macos")'.dependencies] -notify-rust = "4.5" +notify-rust = "~4.5" [target.'cfg(unix)'.dependencies] -nix = "0.24" -rust-ini = "0.18" -self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } +nix = "~0.24" +rust-ini = "~0.18" +self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } [target.'cfg(windows)'.dependencies] -self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } -winapi = "0.3" -parselnk = "0.1" +self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } +winapi = "~0.3" +parselnk = "~0.1" [profile.release] lto = true diff --git a/src/config.rs b/src/config.rs index a9c893f5..10727019 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,16 +1,16 @@ #![allow(dead_code)] +use anyhow::Context; +use anyhow::Result; +use clap::{ArgEnum, Parser}; +use directories::BaseDirs; +use log::debug; +use regex::Regex; +use serde::Deserialize; use std::collections::BTreeMap; use std::fs::write; use std::path::PathBuf; use std::process::Command; use std::{env, fs}; - -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use directories::BaseDirs; -use log::debug; -use regex::Regex; -use serde::Deserialize; use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator}; use sys_info::hostname; use which_crate::which; @@ -62,7 +62,7 @@ macro_rules! get_deprecated { type Commands = BTreeMap; -#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)] +#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)] #[clap(rename_all = "snake_case")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] @@ -397,78 +397,78 @@ impl ConfigFile { // Command line arguments #[derive(Parser, Debug)] -#[command(name = "Topgrade", version)] +#[clap(name = "Topgrade", version)] pub struct CommandLineArgs { /// Edit the configuration file - #[arg(long = "edit-config")] + #[clap(long = "edit-config")] edit_config: bool, /// Show config reference - #[arg(long = "config-reference")] + #[clap(long = "config-reference")] show_config_reference: bool, /// Run inside tmux - #[arg(short = 't', long = "tmux")] + #[clap(short = 't', long = "tmux")] run_in_tmux: bool, /// Cleanup temporary or old files - #[arg(short = 'c', long = "cleanup")] + #[clap(short = 'c', long = "cleanup")] cleanup: bool, /// Print what would be done - #[arg(short = 'n', long = "dry-run")] + #[clap(short = 'n', long = "dry-run")] dry_run: bool, /// Do not ask to retry failed steps - #[arg(long = "no-retry")] + #[clap(long = "no-retry")] no_retry: bool, /// Do not perform upgrades for the given steps - #[arg(long = "disable", value_name = "STEP", value_enum, num_args = 1..)] + #[clap(long = "disable", arg_enum, multiple_values = true)] disable: Vec, /// Perform only the specified steps (experimental) - #[arg(long = "only", value_name = "STEP", value_enum, num_args = 1..)] + #[clap(long = "only", arg_enum, multiple_values = true)] only: Vec, /// Run only specific custom commands - #[arg(long = "custom-commands", value_name = "NAME", num_args = 1..)] + #[clap(long = "custom-commands")] custom_commands: Vec, /// Set environment variables - #[arg(long = "env", value_name = "NAME=VALUE", num_args = 1..)] + #[clap(long = "env", multiple_values = true)] env: Vec, /// Output logs - #[arg(short = 'v', long = "verbose")] + #[clap(short = 'v', long = "verbose")] pub verbose: bool, /// Prompt for a key before exiting - #[arg(short = 'k', long = "keep")] + #[clap(short = 'k', long = "keep")] keep_at_end: bool, /// Skip sending a notification at the end of a run - #[arg(long = "skip-notify")] + #[clap(long = "skip-notify")] skip_notify: bool, /// Say yes to package manager's prompt - #[arg(short = 'y', long = "yes", value_name = "STEP", value_enum, num_args = 0..)] + #[clap(short = 'y', long = "yes", arg_enum, multiple_values = true, min_values = 0)] yes: Option>, /// Don't pull the predefined git repos - #[arg(long = "disable-predefined-git-repos")] + #[clap(long = "disable-predefined-git-repos")] disable_predefined_git_repos: bool, /// Alternative configuration file - #[arg(long = "config", value_name = "PATH")] + #[clap(long = "config")] config: Option, /// A regular expression for restricting remote host execution - #[arg(long = "remote-host-limit", value_name = "REGEX")] + #[clap(long = "remote-host-limit")] remote_host_limit: Option, /// Show the reason for skipped steps - #[arg(long = "show-skipped")] + #[clap(long = "show-skipped")] show_skipped: bool, } @@ -626,8 +626,16 @@ impl Config { } /// Extra Tmux arguments - pub fn tmux_arguments(&self) -> &Option { - &self.config_file.tmux_arguments + pub fn tmux_arguments(&self) -> anyhow::Result> { + let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default(); + shell_words::split(args) + // The only time the parse failed is in case of a missing close quote. + // The error message looks like this: + // Error: Failed to parse `tmux_arguments`: `'foo` + // + // Caused by: + // missing closing quote + .with_context(|| format!("Failed to parse `tmux_arguments`: `{args}`")) } /// Prompt for a key before exiting diff --git a/src/ctrlc/interrupted.rs b/src/ctrlc/interrupted.rs index 5322d9bb..28886d47 100644 --- a/src/ctrlc/interrupted.rs +++ b/src/ctrlc/interrupted.rs @@ -1,10 +1,7 @@ -use lazy_static::lazy_static; use std::sync::atomic::{AtomicBool, Ordering}; -lazy_static! { - /// A global variable telling whether the application has been interrupted. - static ref INTERRUPTED: AtomicBool = AtomicBool::new(false); -} +/// A global variable telling whether the application has been interrupted. +static INTERRUPTED: AtomicBool = AtomicBool::new(false); /// Tells whether the program has been interrupted pub fn interrupted() -> bool { diff --git a/src/executor.rs b/src/executor.rs index b568225e..b68b30a4 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -194,11 +194,12 @@ impl DryCommand { print!( "Dry running: {} {}", self.program.to_string_lossy(), - self.args - .iter() - .map(|a| String::from(a.to_string_lossy())) - .collect::>() - .join(" ") + shell_words::join( + self.args + .iter() + .map(|a| String::from(a.to_string_lossy())) + .collect::>() + ) ); match &self.directory { Some(dir) => println!(" in {}", dir.to_string_lossy()), diff --git a/src/main.rs b/src/main.rs index 66210df3..d08d207e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ fn run() -> Result<()> { if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() { #[cfg(unix)] { - tmux::run_in_tmux(config.tmux_arguments()); + tmux::run_in_tmux(config.tmux_arguments()?); } } @@ -351,6 +351,7 @@ fn run() -> Result<()> { runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?; runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?; + runner.execute(Step::Node, "pnpm", || node::run_pnpm_upgrade(&ctx))?; runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?; runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; @@ -523,7 +524,10 @@ fn main() { .is_some()); if !skip_print { - println!("Error: {}", error); + // The `Debug` implementation of `anyhow::Result` prints a multi-line + // error message that includes all the 'causes' added with + // `.with_context(...)` calls. + println!("Error: {:?}", error); } exit(1); } diff --git a/src/steps/generic.rs b/src/steps/generic.rs index 22c118f8..7694eada 100644 --- a/src/steps/generic.rs +++ b/src/steps/generic.rs @@ -196,12 +196,16 @@ pub fn run_krew_upgrade(run_type: RunType) -> Result<()> { pub fn run_gcloud_components_update(run_type: RunType) -> Result<()> { let gcloud = utils::require("gcloud")?; - print_separator("gcloud"); + if gcloud.starts_with("/snap") { + Ok(()) + } else { + print_separator("gcloud"); - run_type - .execute(gcloud) - .args(["components", "update", "--quiet"]) - .check_run() + run_type + .execute(gcloud) + .args(["components", "update", "--quiet"]) + .check_run() + } } pub fn run_jetpack(run_type: RunType) -> Result<()> { diff --git a/src/steps/node.rs b/src/steps/node.rs index ea1fbe4c..c702076a 100644 --- a/src/steps/node.rs +++ b/src/steps/node.rs @@ -1,5 +1,6 @@ #![allow(unused_imports)] +use std::fmt::Display; #[cfg(unix)] use std::os::unix::prelude::MetadataExt; use std::path::PathBuf; @@ -17,24 +18,71 @@ use crate::terminal::print_separator; use crate::utils::{require, PathExt}; use crate::{error::SkipStep, execution_context::ExecutionContext}; +enum NPMVariant { + Npm, + Pnpm, +} + +impl NPMVariant { + const fn long_name(&self) -> &str { + match self { + NPMVariant::Npm => "Node Package Manager", + NPMVariant::Pnpm => "PNPM", + } + } + + const fn short_name(&self) -> &str { + match self { + NPMVariant::Npm => "npm", + NPMVariant::Pnpm => "pnpm", + } + } + + const fn is_npm(&self) -> bool { + matches!(self, NPMVariant::Npm) + } +} + +impl Display for NPMVariant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.short_name()) + } +} + #[allow(clippy::upper_case_acronyms)] struct NPM { command: PathBuf, + variant: NPMVariant, } impl NPM { - fn new(command: PathBuf) -> Self { - Self { command } + fn new(command: PathBuf, variant: NPMVariant) -> Self { + Self { command, variant } + } + + /// Is the “NPM” version larger than 8.11.0? + fn is_npm_8(&self) -> bool { + let v = self.version(); + + self.variant.is_npm() && matches!(v, Ok(v) if v >= Version::new(8, 11, 0)) + } + + /// Get the most suitable “global location” argument + /// of this NPM instance. + /// + /// If the “NPM” version is larger than 8.11.0, we use + /// `--location=global`; otherwise, use `-g`. + fn global_location_arg(&self) -> &str { + if self.is_npm_8() { + "--location=global" + } else { + "-g" + } } #[cfg(target_os = "linux")] fn root(&self) -> Result { - let version = self.version()?; - let args = if version < Version::new(8, 11, 0) { - ["root", "-g"] - } else { - ["root", "--location=global"] - }; + let args = ["root", self.global_location_arg()]; Command::new(&self.command) .args(args) .check_output() @@ -50,13 +98,8 @@ impl NPM { } fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { - print_separator("Node Package Manager"); - let version = self.version()?; - let args = if version < Version::new(8, 11, 0) { - ["update", "-g"] - } else { - ["update", "--location=global"] - }; + print_separator(self.variant.long_name()); + let args = ["update", self.global_location_arg()]; if use_sudo { run_type.execute("sudo").args(args).check_run()?; } else { @@ -70,7 +113,7 @@ impl NPM { pub fn should_use_sudo(&self) -> Result { let npm_root = self.root()?; if !npm_root.exists() { - return Err(SkipStep(format!("NPM root at {} doesn't exist", npm_root.display(),)).into()); + return Err(SkipStep(format!("{} root at {} doesn't exist", self.variant, npm_root.display())).into()); } let metadata = std::fs::metadata(&npm_root)?; @@ -93,6 +136,17 @@ impl Yarn { } } + fn has_global_subcmd(&self) -> bool { + // Get the version of Yarn. After Yarn 2.x (berry), + // “yarn global” has been replaced with “yarn dlx”. + // + // As “yarn dlx” don't need to “upgrade”, we + // ignore the whole task if Yarn is 2.x or above. + let version = Command::new(&self.command).args(["--version"]).check_output(); + + matches!(version, Ok(ver) if ver.starts_with('1') || ver.starts_with('0')) + } + #[cfg(target_os = "linux")] fn root(&self) -> Result { let args = ["global", "dir"]; @@ -123,7 +177,7 @@ impl Yarn { pub fn should_use_sudo(&self) -> Result { let yarn_root = self.root()?; if !yarn_root.exists() { - return Err(SkipStep(format!("NPM root at {} doesn't exist", yarn_root.display(),)).into()); + return Err(SkipStep(format!("Yarn root at {} doesn't exist", yarn_root.display(),)).into()); } let metadata = std::fs::metadata(&yarn_root)?; @@ -162,7 +216,7 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result { } pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { - let npm = require("pnpm").or_else(|_| require("npm")).map(NPM::new)?; + let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?; #[cfg(target_os = "linux")] { @@ -175,9 +229,28 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { } } +pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> { + let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?; + + #[cfg(target_os = "linux")] + { + pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?) + } + + #[cfg(not(target_os = "linux"))] + { + pnpm.upgrade(ctx.run_type(), false) + } +} + pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> { let yarn = require("yarn").map(Yarn::new)?; + if !yarn.has_global_subcmd() { + debug!("Yarn is 2.x or above, skipping global upgrade"); + return Ok(()); + } + #[cfg(target_os = "linux")] { yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?) diff --git a/src/steps/os/archlinux.rs b/src/steps/os/archlinux.rs index 74290377..892a8994 100644 --- a/src/steps/os/archlinux.rs +++ b/src/steps/os/archlinux.rs @@ -245,7 +245,7 @@ impl Aura { impl ArchPackageManager for Aura { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { - let sudo = which("sudo").unwrap_or(PathBuf::new()); + let sudo = which("sudo").unwrap_or_else(PathBuf::new); let mut aur_update = ctx.run_type().execute(&sudo); if sudo.ends_with("sudo") { diff --git a/src/steps/os/os_release/debian b/src/steps/os/os_release/debian index 120c51b0..611cf746 100644 --- a/src/steps/os/os_release/debian +++ b/src/steps/os/os_release/debian @@ -1,8 +1,9 @@ -PRETTY_NAME="Debian GNU/Linux 8 (jessie)" +PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" NAME="Debian GNU/Linux" -VERSION_ID="8" -VERSION="8 (jessie)" +VERSION_ID="11" +VERSION="11 (bullseye)" +VERSION_CODENAME=bullseye ID=debian -HOME_URL="http://www.debian.org/" -SUPPORT_URL="http://www.debian.org/support" +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" diff --git a/src/steps/os/windows.rs b/src/steps/os/windows.rs index 86c4515a..14d57595 100644 --- a/src/steps/os/windows.rs +++ b/src/steps/os/windows.rs @@ -26,7 +26,7 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> { args.insert(0, "choco"); } - let mut command = ctx.run_type().execute(&cmd); + let mut command = ctx.run_type().execute(cmd); command.args(&args); @@ -47,7 +47,7 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> { return Err(SkipStep(String::from("Winget is disabled by default")).into()); } - ctx.run_type().execute(&winget).args(&["upgrade", "--all"]).check_run() + ctx.run_type().execute(&winget).args(["upgrade", "--all"]).check_run() } pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { @@ -55,34 +55,34 @@ pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { print_separator("Scoop"); - run_type.execute(&scoop).args(&["update"]).check_run()?; - run_type.execute(&scoop).args(&["update", "*"]).check_run()?; + run_type.execute(&scoop).args(["update"]).check_run()?; + run_type.execute(&scoop).args(["update", "*"]).check_run()?; if cleanup { - run_type.execute(&scoop).args(&["cleanup", "*"]).check_run()?; + run_type.execute(&scoop).args(["cleanup", "*"]).check_run()?; } Ok(()) } fn get_wsl_distributions(wsl: &Path) -> Result> { - let output = Command::new(wsl).args(&["--list", "-q"]).check_output()?; + let output = Command::new(wsl).args(["--list", "-q"]).check_output()?; Ok(output .lines() .filter(|s| !s.is_empty()) - .map(|x| x.replace('\u{0}', "").replace('\r', "")) + .map(|x| x.replace(['\u{0}', '\r'], "")) .collect()) } fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> Result<()> { - let topgrade = Command::new(&wsl) - .args(&["-d", dist, "bash", "-lc", "which topgrade"]) + let topgrade = Command::new(wsl) + .args(["-d", dist, "bash", "-lc", "which topgrade"]) .check_output() .map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?; - let mut command = ctx.run_type().execute(&wsl); + let mut command = ctx.run_type().execute(wsl); command - .args(&["-d", dist, "bash", "-c"]) + .args(["-d", dist, "bash", "-c"]) .arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade)); if ctx.config().yes(Step::Wsl) { @@ -134,7 +134,7 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { } pub fn reboot() { - Command::new("shutdown").args(&["/R", "/T", "0"]).spawn().ok(); + Command::new("shutdown").args(["/R", "/T", "0"]).spawn().ok(); } pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> { diff --git a/src/steps/powershell.rs b/src/steps/powershell.rs index 78145ed1..3d60ea8d 100644 --- a/src/steps/powershell.rs +++ b/src/steps/powershell.rs @@ -46,8 +46,8 @@ impl Powershell { #[cfg(windows)] pub fn has_module(powershell: &Path, command: &str) -> bool { - Command::new(&powershell) - .args(&[ + Command::new(powershell) + .args([ "-NoProfile", "-Command", &format!("Get-Module -ListAvailable {}", command), @@ -79,6 +79,7 @@ impl Powershell { println!("Updating modules..."); ctx.run_type() .execute(powershell) + // This probably doesn't need `shell_words::join`. .args(["-NoProfile", "-Command", &cmd.join(" ")]) .check_run() } @@ -99,14 +100,14 @@ impl Powershell { let mut command = if let Some(sudo) = ctx.sudo() { let mut command = ctx.run_type().execute(sudo); - command.arg(&powershell); + command.arg(powershell); command } else { - ctx.run_type().execute(&powershell) + ctx.run_type().execute(powershell) }; command - .args(&[ + .args([ "-NoProfile", "-Command", &format!( diff --git a/src/steps/remote/ssh.rs b/src/steps/remote/ssh.rs index 8636b895..c94552fb 100644 --- a/src/steps/remote/ssh.rs +++ b/src/steps/remote/ssh.rs @@ -24,7 +24,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> { #[cfg(unix)] { prepare_async_ssh_command(&mut args); - crate::tmux::run_command(ctx, &args.join(" "))?; + crate::tmux::run_command(ctx, &shell_words::join(args))?; Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into()) } diff --git a/src/steps/tmux.rs b/src/steps/tmux.rs index 85082b98..388b65b4 100644 --- a/src/steps/tmux.rs +++ b/src/steps/tmux.rs @@ -29,12 +29,10 @@ struct Tmux { } impl Tmux { - fn new(args: &Option) -> Self { + fn new(args: Vec) -> Self { Self { tmux: which("tmux").expect("Could not find tmux"), - args: args - .as_ref() - .map(|args| args.split_whitespace().map(String::from).collect()), + args: if args.is_empty() { None } else { Some(args) }, } } @@ -75,7 +73,7 @@ impl Tmux { } } -pub fn run_in_tmux(args: &Option) -> ! { +pub fn run_in_tmux(args: Vec) -> ! { let command = { let mut command = vec![ String::from("env"), @@ -83,7 +81,7 @@ pub fn run_in_tmux(args: &Option) -> ! { String::from("TOPGRADE_INSIDE_TMUX=1"), ]; command.extend(env::args()); - command.join(" ") + shell_words::join(command) }; let tmux = Tmux::new(args); @@ -108,7 +106,7 @@ pub fn run_in_tmux(args: &Option) -> ! { } pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> { - Tmux::new(ctx.config().tmux_arguments()) + Tmux::new(ctx.config().tmux_arguments()?) .build() .args(["new-window", "-a", "-t", "topgrade:1", command]) .env_remove("TMUX") From b6e50a38afc35aa34e02b46e1b1684fc20e5657f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Sch=C3=B6nauer?= <37108907+DottoDev@users.noreply.github.com> Date: Sat, 5 Nov 2022 09:15:52 +0000 Subject: [PATCH 5/5] Revert "Bug fixes" (#148) Revert "Bug fixes (#145)" This reverts commit 8acdfc8d1c3ba7ecd9662c74506fe580b75cd2b6. --- .github/workflows/crates-publish.yml | 2 +- .github/workflows/release-cross.yml | 2 +- .github/workflows/release.yml | 2 +- Cargo.lock | 87 +++++++-------------- Cargo.toml | 61 ++++++++------- src/config.rs | 66 +++++++--------- src/ctrlc/interrupted.rs | 7 +- src/executor.rs | 11 ++- src/main.rs | 8 +- src/steps/generic.rs | 14 ++-- src/steps/node.rs | 109 +++++---------------------- src/steps/os/archlinux.rs | 2 +- src/steps/os/os_release/debian | 11 ++- src/steps/os/windows.rs | 24 +++--- src/steps/powershell.rs | 11 ++- src/steps/remote/ssh.rs | 2 +- src/steps/tmux.rs | 12 +-- 17 files changed, 155 insertions(+), 276 deletions(-) diff --git a/.github/workflows/crates-publish.yml b/.github/workflows/crates-publish.yml index 4d8c8056..2d2d0120 100644 --- a/.github/workflows/crates-publish.yml +++ b/.github/workflows/crates-publish.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly-2022-08-03 override: true publish: diff --git a/.github/workflows/release-cross.yml b/.github/workflows/release-cross.yml index 9a2c8eac..16554df0 100644 --- a/.github/workflows/release-cross.yml +++ b/.github/workflows/release-cross.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: 1.57.0 profile: minimal default: true override: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e50032c4..50e4f847 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: 1.57.0 profile: minimal override: true components: rustfmt, clippy diff --git a/Cargo.lock b/Cargo.lock index 2c86ccfb..077b8794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,26 +225,24 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "4.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", - "lazy_static", + "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.1.18" +version = "4.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -255,9 +253,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -660,9 +658,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.3.12" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -985,24 +983,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] @@ -1044,15 +1032,6 @@ dependencies = [ "zvariant_derive", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -1377,9 +1356,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -1625,12 +1604,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - [[package]] name = "shellexpand" version = "2.1.2" @@ -1780,13 +1753,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand", "redox_syscall", "remove_dir_all", "winapi", @@ -1811,12 +1784,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" - [[package]] name = "thiserror" version = "1.0.37" @@ -1894,9 +1861,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.5.1" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3b82e6e823a9ee7d7f64b08f8ac3d5f08ac988f23157194bd32af3f2f92767" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -1904,9 +1871,9 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "winapi", ] @@ -1923,16 +1890,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -1968,7 +1935,6 @@ dependencies = [ "self_update", "semver", "serde", - "shell-words", "shellexpand", "strum 0.24.1", "sys-info", @@ -2236,12 +2202,13 @@ dependencies = [ [[package]] name = "which" -version = "4.1.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", "libc", + "once_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index adff3ba6..85a44bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,44 +20,43 @@ path = "src/main.rs" ##name = "topgrade_lib" [dependencies] -home = "~0.5" -directories = "~4.0" -serde = { version = "~1.0", features = ["derive"] } +home = "0.5" +directories = "4.0" +serde = { version = "1.0", features = ["derive"] } toml = "0.5" -which_crate = { version = "~4.1", package = "which" } -shellexpand = "~2.1" -clap = { version = "~3.1", features = ["cargo", "derive"] } -log = "~0.4" -walkdir = "~2.3" -console = "~0.15" -lazy_static = "~1.4" -chrono = "~0.4" -pretty_env_logger = "~0.4" -glob = "~0.3" -strum = { version = "~0.24", features = ["derive"] } -thiserror = "~1.0" -anyhow = "~1.0" -tempfile = "~3.2" -cfg-if = "~1.0" -tokio = { version = "~1.5", features = ["process", "rt-multi-thread"] } -futures = "~0.3" -regex = "~1.5" -sys-info = "~0.9" -semver = "~1.0" -shell-words = "~1.1" +which_crate = { version = "4.1", package = "which" } +shellexpand = "2.1" +clap = { version = "4.0.18", features = ["cargo", "derive"] } +log = "0.4" +walkdir = "2.3" +console = "0.15" +lazy_static = "1.4" +chrono = "0.4" +pretty_env_logger = "0.4" +glob = "0.3" +strum = { version = "0.24", features = ["derive"] } +thiserror = "1.0" +anyhow = "1.0" +tempfile = "3.2" +cfg-if = "1.0" +tokio = { version = "1.5", features = ["process", "rt-multi-thread"] } +futures = "0.3" +regex = "1.5" +sys-info = "0.9" +semver = "1.0" [target.'cfg(target_os = "macos")'.dependencies] -notify-rust = "~4.5" +notify-rust = "4.5" [target.'cfg(unix)'.dependencies] -nix = "~0.24" -rust-ini = "~0.18" -self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } +nix = "0.24" +rust-ini = "0.18" +self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } [target.'cfg(windows)'.dependencies] -self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } -winapi = "~0.3" -parselnk = "~0.1" +self_update_crate = { version = "0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } +winapi = "0.3" +parselnk = "0.1" [profile.release] lto = true diff --git a/src/config.rs b/src/config.rs index 10727019..a9c893f5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,16 +1,16 @@ #![allow(dead_code)] -use anyhow::Context; -use anyhow::Result; -use clap::{ArgEnum, Parser}; -use directories::BaseDirs; -use log::debug; -use regex::Regex; -use serde::Deserialize; use std::collections::BTreeMap; use std::fs::write; use std::path::PathBuf; use std::process::Command; use std::{env, fs}; + +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use directories::BaseDirs; +use log::debug; +use regex::Regex; +use serde::Deserialize; use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator}; use sys_info::hostname; use which_crate::which; @@ -62,7 +62,7 @@ macro_rules! get_deprecated { type Commands = BTreeMap; -#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)] +#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)] #[clap(rename_all = "snake_case")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] @@ -397,78 +397,78 @@ impl ConfigFile { // Command line arguments #[derive(Parser, Debug)] -#[clap(name = "Topgrade", version)] +#[command(name = "Topgrade", version)] pub struct CommandLineArgs { /// Edit the configuration file - #[clap(long = "edit-config")] + #[arg(long = "edit-config")] edit_config: bool, /// Show config reference - #[clap(long = "config-reference")] + #[arg(long = "config-reference")] show_config_reference: bool, /// Run inside tmux - #[clap(short = 't', long = "tmux")] + #[arg(short = 't', long = "tmux")] run_in_tmux: bool, /// Cleanup temporary or old files - #[clap(short = 'c', long = "cleanup")] + #[arg(short = 'c', long = "cleanup")] cleanup: bool, /// Print what would be done - #[clap(short = 'n', long = "dry-run")] + #[arg(short = 'n', long = "dry-run")] dry_run: bool, /// Do not ask to retry failed steps - #[clap(long = "no-retry")] + #[arg(long = "no-retry")] no_retry: bool, /// Do not perform upgrades for the given steps - #[clap(long = "disable", arg_enum, multiple_values = true)] + #[arg(long = "disable", value_name = "STEP", value_enum, num_args = 1..)] disable: Vec, /// Perform only the specified steps (experimental) - #[clap(long = "only", arg_enum, multiple_values = true)] + #[arg(long = "only", value_name = "STEP", value_enum, num_args = 1..)] only: Vec, /// Run only specific custom commands - #[clap(long = "custom-commands")] + #[arg(long = "custom-commands", value_name = "NAME", num_args = 1..)] custom_commands: Vec, /// Set environment variables - #[clap(long = "env", multiple_values = true)] + #[arg(long = "env", value_name = "NAME=VALUE", num_args = 1..)] env: Vec, /// Output logs - #[clap(short = 'v', long = "verbose")] + #[arg(short = 'v', long = "verbose")] pub verbose: bool, /// Prompt for a key before exiting - #[clap(short = 'k', long = "keep")] + #[arg(short = 'k', long = "keep")] keep_at_end: bool, /// Skip sending a notification at the end of a run - #[clap(long = "skip-notify")] + #[arg(long = "skip-notify")] skip_notify: bool, /// Say yes to package manager's prompt - #[clap(short = 'y', long = "yes", arg_enum, multiple_values = true, min_values = 0)] + #[arg(short = 'y', long = "yes", value_name = "STEP", value_enum, num_args = 0..)] yes: Option>, /// Don't pull the predefined git repos - #[clap(long = "disable-predefined-git-repos")] + #[arg(long = "disable-predefined-git-repos")] disable_predefined_git_repos: bool, /// Alternative configuration file - #[clap(long = "config")] + #[arg(long = "config", value_name = "PATH")] config: Option, /// A regular expression for restricting remote host execution - #[clap(long = "remote-host-limit")] + #[arg(long = "remote-host-limit", value_name = "REGEX")] remote_host_limit: Option, /// Show the reason for skipped steps - #[clap(long = "show-skipped")] + #[arg(long = "show-skipped")] show_skipped: bool, } @@ -626,16 +626,8 @@ impl Config { } /// Extra Tmux arguments - pub fn tmux_arguments(&self) -> anyhow::Result> { - let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default(); - shell_words::split(args) - // The only time the parse failed is in case of a missing close quote. - // The error message looks like this: - // Error: Failed to parse `tmux_arguments`: `'foo` - // - // Caused by: - // missing closing quote - .with_context(|| format!("Failed to parse `tmux_arguments`: `{args}`")) + pub fn tmux_arguments(&self) -> &Option { + &self.config_file.tmux_arguments } /// Prompt for a key before exiting diff --git a/src/ctrlc/interrupted.rs b/src/ctrlc/interrupted.rs index 28886d47..5322d9bb 100644 --- a/src/ctrlc/interrupted.rs +++ b/src/ctrlc/interrupted.rs @@ -1,7 +1,10 @@ +use lazy_static::lazy_static; use std::sync::atomic::{AtomicBool, Ordering}; -/// A global variable telling whether the application has been interrupted. -static INTERRUPTED: AtomicBool = AtomicBool::new(false); +lazy_static! { + /// A global variable telling whether the application has been interrupted. + static ref INTERRUPTED: AtomicBool = AtomicBool::new(false); +} /// Tells whether the program has been interrupted pub fn interrupted() -> bool { diff --git a/src/executor.rs b/src/executor.rs index b68b30a4..b568225e 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -194,12 +194,11 @@ impl DryCommand { print!( "Dry running: {} {}", self.program.to_string_lossy(), - shell_words::join( - self.args - .iter() - .map(|a| String::from(a.to_string_lossy())) - .collect::>() - ) + self.args + .iter() + .map(|a| String::from(a.to_string_lossy())) + .collect::>() + .join(" ") ); match &self.directory { Some(dir) => println!(" in {}", dir.to_string_lossy()), diff --git a/src/main.rs b/src/main.rs index d08d207e..66210df3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ fn run() -> Result<()> { if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() { #[cfg(unix)] { - tmux::run_in_tmux(config.tmux_arguments()?); + tmux::run_in_tmux(config.tmux_arguments()); } } @@ -351,7 +351,6 @@ fn run() -> Result<()> { runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?; runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?; - runner.execute(Step::Node, "pnpm", || node::run_pnpm_upgrade(&ctx))?; runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?; runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; @@ -524,10 +523,7 @@ fn main() { .is_some()); if !skip_print { - // The `Debug` implementation of `anyhow::Result` prints a multi-line - // error message that includes all the 'causes' added with - // `.with_context(...)` calls. - println!("Error: {:?}", error); + println!("Error: {}", error); } exit(1); } diff --git a/src/steps/generic.rs b/src/steps/generic.rs index 7694eada..22c118f8 100644 --- a/src/steps/generic.rs +++ b/src/steps/generic.rs @@ -196,16 +196,12 @@ pub fn run_krew_upgrade(run_type: RunType) -> Result<()> { pub fn run_gcloud_components_update(run_type: RunType) -> Result<()> { let gcloud = utils::require("gcloud")?; - if gcloud.starts_with("/snap") { - Ok(()) - } else { - print_separator("gcloud"); + print_separator("gcloud"); - run_type - .execute(gcloud) - .args(["components", "update", "--quiet"]) - .check_run() - } + run_type + .execute(gcloud) + .args(["components", "update", "--quiet"]) + .check_run() } pub fn run_jetpack(run_type: RunType) -> Result<()> { diff --git a/src/steps/node.rs b/src/steps/node.rs index c702076a..ea1fbe4c 100644 --- a/src/steps/node.rs +++ b/src/steps/node.rs @@ -1,6 +1,5 @@ #![allow(unused_imports)] -use std::fmt::Display; #[cfg(unix)] use std::os::unix::prelude::MetadataExt; use std::path::PathBuf; @@ -18,71 +17,24 @@ use crate::terminal::print_separator; use crate::utils::{require, PathExt}; use crate::{error::SkipStep, execution_context::ExecutionContext}; -enum NPMVariant { - Npm, - Pnpm, -} - -impl NPMVariant { - const fn long_name(&self) -> &str { - match self { - NPMVariant::Npm => "Node Package Manager", - NPMVariant::Pnpm => "PNPM", - } - } - - const fn short_name(&self) -> &str { - match self { - NPMVariant::Npm => "npm", - NPMVariant::Pnpm => "pnpm", - } - } - - const fn is_npm(&self) -> bool { - matches!(self, NPMVariant::Npm) - } -} - -impl Display for NPMVariant { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.short_name()) - } -} - #[allow(clippy::upper_case_acronyms)] struct NPM { command: PathBuf, - variant: NPMVariant, } impl NPM { - fn new(command: PathBuf, variant: NPMVariant) -> Self { - Self { command, variant } - } - - /// Is the “NPM” version larger than 8.11.0? - fn is_npm_8(&self) -> bool { - let v = self.version(); - - self.variant.is_npm() && matches!(v, Ok(v) if v >= Version::new(8, 11, 0)) - } - - /// Get the most suitable “global location” argument - /// of this NPM instance. - /// - /// If the “NPM” version is larger than 8.11.0, we use - /// `--location=global`; otherwise, use `-g`. - fn global_location_arg(&self) -> &str { - if self.is_npm_8() { - "--location=global" - } else { - "-g" - } + fn new(command: PathBuf) -> Self { + Self { command } } #[cfg(target_os = "linux")] fn root(&self) -> Result { - let args = ["root", self.global_location_arg()]; + let version = self.version()?; + let args = if version < Version::new(8, 11, 0) { + ["root", "-g"] + } else { + ["root", "--location=global"] + }; Command::new(&self.command) .args(args) .check_output() @@ -98,8 +50,13 @@ impl NPM { } fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { - print_separator(self.variant.long_name()); - let args = ["update", self.global_location_arg()]; + print_separator("Node Package Manager"); + let version = self.version()?; + let args = if version < Version::new(8, 11, 0) { + ["update", "-g"] + } else { + ["update", "--location=global"] + }; if use_sudo { run_type.execute("sudo").args(args).check_run()?; } else { @@ -113,7 +70,7 @@ impl NPM { pub fn should_use_sudo(&self) -> Result { let npm_root = self.root()?; if !npm_root.exists() { - return Err(SkipStep(format!("{} root at {} doesn't exist", self.variant, npm_root.display())).into()); + return Err(SkipStep(format!("NPM root at {} doesn't exist", npm_root.display(),)).into()); } let metadata = std::fs::metadata(&npm_root)?; @@ -136,17 +93,6 @@ impl Yarn { } } - fn has_global_subcmd(&self) -> bool { - // Get the version of Yarn. After Yarn 2.x (berry), - // “yarn global” has been replaced with “yarn dlx”. - // - // As “yarn dlx” don't need to “upgrade”, we - // ignore the whole task if Yarn is 2.x or above. - let version = Command::new(&self.command).args(["--version"]).check_output(); - - matches!(version, Ok(ver) if ver.starts_with('1') || ver.starts_with('0')) - } - #[cfg(target_os = "linux")] fn root(&self) -> Result { let args = ["global", "dir"]; @@ -177,7 +123,7 @@ impl Yarn { pub fn should_use_sudo(&self) -> Result { let yarn_root = self.root()?; if !yarn_root.exists() { - return Err(SkipStep(format!("Yarn root at {} doesn't exist", yarn_root.display(),)).into()); + return Err(SkipStep(format!("NPM root at {} doesn't exist", yarn_root.display(),)).into()); } let metadata = std::fs::metadata(&yarn_root)?; @@ -216,7 +162,7 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result { } pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { - let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?; + let npm = require("pnpm").or_else(|_| require("npm")).map(NPM::new)?; #[cfg(target_os = "linux")] { @@ -229,28 +175,9 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { } } -pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> { - let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?; - - #[cfg(target_os = "linux")] - { - pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?) - } - - #[cfg(not(target_os = "linux"))] - { - pnpm.upgrade(ctx.run_type(), false) - } -} - pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> { let yarn = require("yarn").map(Yarn::new)?; - if !yarn.has_global_subcmd() { - debug!("Yarn is 2.x or above, skipping global upgrade"); - return Ok(()); - } - #[cfg(target_os = "linux")] { yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?) diff --git a/src/steps/os/archlinux.rs b/src/steps/os/archlinux.rs index 892a8994..74290377 100644 --- a/src/steps/os/archlinux.rs +++ b/src/steps/os/archlinux.rs @@ -245,7 +245,7 @@ impl Aura { impl ArchPackageManager for Aura { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { - let sudo = which("sudo").unwrap_or_else(PathBuf::new); + let sudo = which("sudo").unwrap_or(PathBuf::new()); let mut aur_update = ctx.run_type().execute(&sudo); if sudo.ends_with("sudo") { diff --git a/src/steps/os/os_release/debian b/src/steps/os/os_release/debian index 611cf746..120c51b0 100644 --- a/src/steps/os/os_release/debian +++ b/src/steps/os/os_release/debian @@ -1,9 +1,8 @@ -PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" +PRETTY_NAME="Debian GNU/Linux 8 (jessie)" NAME="Debian GNU/Linux" -VERSION_ID="11" -VERSION="11 (bullseye)" -VERSION_CODENAME=bullseye +VERSION_ID="8" +VERSION="8 (jessie)" ID=debian -HOME_URL="https://www.debian.org/" -SUPPORT_URL="https://www.debian.org/support" +HOME_URL="http://www.debian.org/" +SUPPORT_URL="http://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" diff --git a/src/steps/os/windows.rs b/src/steps/os/windows.rs index 14d57595..86c4515a 100644 --- a/src/steps/os/windows.rs +++ b/src/steps/os/windows.rs @@ -26,7 +26,7 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> { args.insert(0, "choco"); } - let mut command = ctx.run_type().execute(cmd); + let mut command = ctx.run_type().execute(&cmd); command.args(&args); @@ -47,7 +47,7 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> { return Err(SkipStep(String::from("Winget is disabled by default")).into()); } - ctx.run_type().execute(&winget).args(["upgrade", "--all"]).check_run() + ctx.run_type().execute(&winget).args(&["upgrade", "--all"]).check_run() } pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { @@ -55,34 +55,34 @@ pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { print_separator("Scoop"); - run_type.execute(&scoop).args(["update"]).check_run()?; - run_type.execute(&scoop).args(["update", "*"]).check_run()?; + run_type.execute(&scoop).args(&["update"]).check_run()?; + run_type.execute(&scoop).args(&["update", "*"]).check_run()?; if cleanup { - run_type.execute(&scoop).args(["cleanup", "*"]).check_run()?; + run_type.execute(&scoop).args(&["cleanup", "*"]).check_run()?; } Ok(()) } fn get_wsl_distributions(wsl: &Path) -> Result> { - let output = Command::new(wsl).args(["--list", "-q"]).check_output()?; + let output = Command::new(wsl).args(&["--list", "-q"]).check_output()?; Ok(output .lines() .filter(|s| !s.is_empty()) - .map(|x| x.replace(['\u{0}', '\r'], "")) + .map(|x| x.replace('\u{0}', "").replace('\r', "")) .collect()) } fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> Result<()> { - let topgrade = Command::new(wsl) - .args(["-d", dist, "bash", "-lc", "which topgrade"]) + let topgrade = Command::new(&wsl) + .args(&["-d", dist, "bash", "-lc", "which topgrade"]) .check_output() .map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?; - let mut command = ctx.run_type().execute(wsl); + let mut command = ctx.run_type().execute(&wsl); command - .args(["-d", dist, "bash", "-c"]) + .args(&["-d", dist, "bash", "-c"]) .arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade)); if ctx.config().yes(Step::Wsl) { @@ -134,7 +134,7 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { } pub fn reboot() { - Command::new("shutdown").args(["/R", "/T", "0"]).spawn().ok(); + Command::new("shutdown").args(&["/R", "/T", "0"]).spawn().ok(); } pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> { diff --git a/src/steps/powershell.rs b/src/steps/powershell.rs index 3d60ea8d..78145ed1 100644 --- a/src/steps/powershell.rs +++ b/src/steps/powershell.rs @@ -46,8 +46,8 @@ impl Powershell { #[cfg(windows)] pub fn has_module(powershell: &Path, command: &str) -> bool { - Command::new(powershell) - .args([ + Command::new(&powershell) + .args(&[ "-NoProfile", "-Command", &format!("Get-Module -ListAvailable {}", command), @@ -79,7 +79,6 @@ impl Powershell { println!("Updating modules..."); ctx.run_type() .execute(powershell) - // This probably doesn't need `shell_words::join`. .args(["-NoProfile", "-Command", &cmd.join(" ")]) .check_run() } @@ -100,14 +99,14 @@ impl Powershell { let mut command = if let Some(sudo) = ctx.sudo() { let mut command = ctx.run_type().execute(sudo); - command.arg(powershell); + command.arg(&powershell); command } else { - ctx.run_type().execute(powershell) + ctx.run_type().execute(&powershell) }; command - .args([ + .args(&[ "-NoProfile", "-Command", &format!( diff --git a/src/steps/remote/ssh.rs b/src/steps/remote/ssh.rs index c94552fb..8636b895 100644 --- a/src/steps/remote/ssh.rs +++ b/src/steps/remote/ssh.rs @@ -24,7 +24,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> { #[cfg(unix)] { prepare_async_ssh_command(&mut args); - crate::tmux::run_command(ctx, &shell_words::join(args))?; + crate::tmux::run_command(ctx, &args.join(" "))?; Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into()) } diff --git a/src/steps/tmux.rs b/src/steps/tmux.rs index 388b65b4..85082b98 100644 --- a/src/steps/tmux.rs +++ b/src/steps/tmux.rs @@ -29,10 +29,12 @@ struct Tmux { } impl Tmux { - fn new(args: Vec) -> Self { + fn new(args: &Option) -> Self { Self { tmux: which("tmux").expect("Could not find tmux"), - args: if args.is_empty() { None } else { Some(args) }, + args: args + .as_ref() + .map(|args| args.split_whitespace().map(String::from).collect()), } } @@ -73,7 +75,7 @@ impl Tmux { } } -pub fn run_in_tmux(args: Vec) -> ! { +pub fn run_in_tmux(args: &Option) -> ! { let command = { let mut command = vec![ String::from("env"), @@ -81,7 +83,7 @@ pub fn run_in_tmux(args: Vec) -> ! { String::from("TOPGRADE_INSIDE_TMUX=1"), ]; command.extend(env::args()); - shell_words::join(command) + command.join(" ") }; let tmux = Tmux::new(args); @@ -106,7 +108,7 @@ pub fn run_in_tmux(args: Vec) -> ! { } pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> { - Tmux::new(ctx.config().tmux_arguments()?) + Tmux::new(ctx.config().tmux_arguments()) .build() .args(["new-window", "-a", "-t", "topgrade:1", command]) .env_remove("TMUX")