Compare commits
1 Commits
v16.0.1
...
last-topgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e694d8e13 |
83
.github/ISSUE_TEMPLATE/bug_report.md
vendored
83
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,91 +2,32 @@
|
||||
name: Bug report
|
||||
about: Topgrade is misbehaving
|
||||
title: ''
|
||||
labels: 'C-bug'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Please make sure to
|
||||
[search for existing issues](https://github.com/topgrade-rs/topgrade/issues)
|
||||
before filing a new one!
|
||||
<!-- If you're here to report about a "No asset found" error, please make sure that an hour has been passed since the last release was made. -->
|
||||
|
||||
Questions labeled with `Optional` can be skipped.
|
||||
-->
|
||||
## What did you expect to happen?
|
||||
|
||||
<!--
|
||||
If you're here to report about a "No asset found" error, please make sure that
|
||||
an hour has been passed since the last release was made.
|
||||
-->
|
||||
|
||||
## Erroneous Behavior
|
||||
<!--
|
||||
What actually happened?
|
||||
-->
|
||||
## What actually happened?
|
||||
|
||||
## Expected Behavior
|
||||
<!--
|
||||
Describe the expected behavior
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
<!--
|
||||
A minimal example to reproduce the issue
|
||||
-->
|
||||
|
||||
## Possible Cause (Optional)
|
||||
<!--
|
||||
If you know the possible cause of the issue, please tell us.
|
||||
-->
|
||||
|
||||
## Problem persists without calling from topgrade
|
||||
<!--
|
||||
Execute the erroneous command directly to see if the problem persists
|
||||
-->
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Did you run topgrade through `Remote Execution`
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
If yes, does the issue still occur when you run topgrade directlly in your
|
||||
remote host
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Configuration file (Optional)
|
||||
<!--
|
||||
Paste your configuration file inside the code block if you think this issue is
|
||||
related to configuration.
|
||||
-->
|
||||
|
||||
```toml
|
||||
|
||||
```
|
||||
|
||||
## Additional Details
|
||||
- Operation System/Version
|
||||
<!-- For example, Fedora Linux 38 -->
|
||||
- Which operating system or Linux distribution are you using?
|
||||
- How did you install Topgrade?
|
||||
- Which version are you running? <!-- Check with `topgrade -V` -->
|
||||
|
||||
- Installation
|
||||
<!--
|
||||
How did you install topgrade: build from repo / crates.io (cargo install topgrade)
|
||||
/ package manager (which one) / other (describe)
|
||||
-->
|
||||
|
||||
- Topgrade version (`topgrade -V`)
|
||||
|
||||
## Verbose Output (`topgrade -v`)
|
||||
<!--
|
||||
Paste the verbose output into the pre-tags
|
||||
<!--
|
||||
Run `topgrade --dry-run` to see which commands Topgrade is running.
|
||||
If the command seems wrong and you know why please tell us so.
|
||||
If the command seems fine try to run it yourself and tell us if you got a different result from Topgrade.
|
||||
-->
|
||||
|
||||
<details>
|
||||
<!-- Paste the output of the problematic command with `-v` into the pre-tags -->
|
||||
<pre>
|
||||
|
||||
</pre>
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/topgrade-rs/topgrade/discussions
|
||||
about: Please ask and answer questions here.
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,20 +2,16 @@
|
||||
name: Feature request
|
||||
about: Can you please support...?
|
||||
title: ''
|
||||
labels: 'C-feature request'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## I want to suggest a new step
|
||||
|
||||
* Which tool is this about? Where is its repository?
|
||||
* Which operating systems are supported by this tool?
|
||||
* What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
* Which exact commands should Topgrade run?
|
||||
* Does it have a `--dry-run` option? i.e., print what should be done and exit
|
||||
* Does it need the user to confirm the execution? And does it provide a `--yes`
|
||||
option to skip this step?
|
||||
### Which tool is this about? Where is its repository?
|
||||
### Which operating systems are supported by this tool?
|
||||
### What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
### Which exact commands should Topgrade run?
|
||||
|
||||
## I want to suggest some general feature
|
||||
Topgrade should...
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,18 +1,12 @@
|
||||
## What does this PR do
|
||||
|
||||
|
||||
## Standards checklist
|
||||
## Standards checklist:
|
||||
|
||||
- [ ] The PR title is descriptive.
|
||||
- [ ] I have read `CONTRIBUTING.md`
|
||||
- [ ] The code compiles (`cargo build`)
|
||||
- [ ] The code passes rustfmt (`cargo fmt`)
|
||||
- [ ] The code passes clippy (`cargo clippy`)
|
||||
- [ ] The code passes tests (`cargo test`)
|
||||
- [ ] *Optional:* I have tested the code myself
|
||||
|
||||
## For new steps
|
||||
|
||||
- [ ] *Optional:* Topgrade skips this step where needed
|
||||
- [ ] *Optional:* The `--dry-run` option works with this step
|
||||
- [ ] *Optional:* The `--yes` option works with this step if it is supported by
|
||||
the underlying command
|
||||
- [ ] I also tested that Topgrade skips the step where needed
|
||||
|
||||
If you developed a feature or a bug fix for someone else and you do not have the
|
||||
means to test it, please tag this person here.
|
||||
|
||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
||||
# Set update schedule for GitHub Actions
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: "weekly"
|
||||
24
.github/workflows/build-and-test.yml
vendored
Normal file
24
.github/workflows/build-and-test.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Cargo Build & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Rust project - latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
|
||||
- run: cargo build --verbose
|
||||
- run: cargo test --verbose
|
||||
55
.github/workflows/check-and-lint.yaml
vendored
Normal file
55
.github/workflows/check-and-lint.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
name: Check and Lint
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
name: Clippy Output
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Check config file creation if not exists
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
||||
jobs:
|
||||
TestConfig:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
TOPGRADE_SKIP_BRKC_NOTIFY=true ./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
22
.github/workflows/check_i18n.yml
vendored
22
.github/workflows/check_i18n.yml
vendored
@@ -1,22 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Check i18n
|
||||
|
||||
jobs:
|
||||
check_locale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install checker
|
||||
# Build it with the dev profile as this is faster and the checker still works
|
||||
run: |
|
||||
cargo install --git https://github.com/topgrade-rs/topgrade_i18n_locale_checker --profile dev
|
||||
|
||||
- name: Run the checker
|
||||
run: topgrade_i18n_locale_checker --locale-file ./locales/app.yml --rust-src-to-check ./src
|
||||
@@ -1,32 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Check Security Vulnerability
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: DevSkim
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
87
.github/workflows/ci.yml
vendored
87
.github/workflows/ci.yml
vendored
@@ -1,87 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
CROSS_VER: '0.2.5'
|
||||
CARGO_NET_RETRY: 3
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run cargo fmt
|
||||
env:
|
||||
TERM: xterm-256color
|
||||
run: |
|
||||
cargo fmt --all -- --check
|
||||
|
||||
main:
|
||||
needs: fmt
|
||||
name: ${{ matrix.target_name }} (check, clippy)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-linux-android
|
||||
target_name: Android
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-freebsd
|
||||
target_name: FreeBSD
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
target_name: Linux
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-apple-darwin
|
||||
target_name: macOS-x86_64
|
||||
os: macos-13
|
||||
|
||||
- target: aarch64-apple-darwin
|
||||
target_name: macOS-aarch64
|
||||
os: macos-latest
|
||||
|
||||
- target: x86_64-unknown-netbsd
|
||||
target_name: NetBSD
|
||||
use_cross: true
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-pc-windows-msvc
|
||||
target_name: Windows
|
||||
os: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: ${{ matrix.target }}
|
||||
|
||||
- name: Setup cross
|
||||
if: matrix.use_cross == true
|
||||
run: curl -fL --retry 3 https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VER }}/cross-x86_64-unknown-linux-musl.tar.gz | tar vxz -C /usr/local/bin
|
||||
|
||||
- name: Run cargo/cross check
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Run cargo/cross clippy
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||
|
||||
- name: Run cargo test
|
||||
# ONLY run test with cargo
|
||||
if: matrix.use_cross == false
|
||||
run: cargo test --locked --target ${{ matrix.target }}
|
||||
@@ -2,18 +2,16 @@ on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
name: Check SemVer compliance
|
||||
name: Check SemVer compliance and publish on release
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2022-08-03
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
semver:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,3 +23,12 @@ jobs:
|
||||
- run: eval "current_version=$(grep -e '^version = .*$' Cargo.toml | cut -d ' ' -f 3)"
|
||||
- run: cargo semver | tee semver_out
|
||||
- run: (head -n 1 semver_out | grep "\-> $current_version") || (echo "versioning mismatch" && return 1)
|
||||
|
||||
publish:
|
||||
steps:
|
||||
- uses: katyo/publish-crates@v1
|
||||
with:
|
||||
dry-run: true
|
||||
check-repo: ${{ github.event_name == 'push' }}
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: true
|
||||
88
.github/workflows/create_release_assets.yml
vendored
88
.github/workflows/create_release_assets.yml
vendored
@@ -1,88 +0,0 @@
|
||||
name: Publish release files for CD native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Check format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all-targets --locked -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cargo clippy --all-targets --locked --all-features -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cargo build --release --all-features
|
||||
|
||||
- name: Rename Release (Unix)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
if: ${{ matrix.platform != 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Build Debian-based system binary and create package
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf target/release
|
||||
cargo build --release
|
||||
cargo deb --no-build --no-strip
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/debian/*.deb assets
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Rename Release (Windows)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade.exe assets/topgrade.exe
|
||||
cd assets
|
||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||
rm topgrade.exe
|
||||
ls .
|
||||
if: ${{ matrix.platform == 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Publish release files for non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-deb cross compilation dependencies
|
||||
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: install cross
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Check format
|
||||
run: cross fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target ${{matrix.target}}
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target ${{matrix.target}}
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir -p assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
- name: Build Debian-based system package without autoupdate feature
|
||||
# First remove the binary built by previous steps
|
||||
# because we don't want the auto-update feature,
|
||||
# then build the new binary without auto-updating.
|
||||
run: |
|
||||
rm -rf target/${{matrix.target}}
|
||||
cross build --release --target ${{matrix.target}}
|
||||
cargo deb --target=${{matrix.target}} --no-build --no-strip
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Move Debian-based system package
|
||||
run: |
|
||||
mkdir -p assets
|
||||
mv target/${{matrix.target}}/debian/*.deb assets
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
28
.github/workflows/release-packaging.yaml
vendored
Normal file
28
.github/workflows/release-packaging.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Release Packaging
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release Packaging
|
||||
env:
|
||||
PROJECT_NAME_UNDERSCORE: topgrade-rs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Release Build
|
||||
run: cargo build --release
|
||||
- name: 'Upload Artifact'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME_UNDERSCORE }}
|
||||
path: target/release/${{ env.PROJECT_NAME_UNDERSCORE }}
|
||||
23
.github/workflows/release_to_aur.yml
vendored
23
.github/workflows/release_to_aur.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Publish to AUR
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
aur-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish AUR package
|
||||
uses: aksh1618/update-aur-package@v1.0.5
|
||||
with:
|
||||
tag_version_prefix: v
|
||||
package_name: topgrade
|
||||
commit_username: "Thomas Schönauer"
|
||||
commit_email: t.schoenauer@hgs-wt.at
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
29
.github/workflows/release_to_crates_io.yml
vendored
29
.github/workflows/release_to_crates_io.yml
vendored
@@ -1,29 +0,0 @@
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
name: Publish to crates.io on release
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
dry-run: true
|
||||
check-repo: ${{ github.event_name == 'push' }}
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: true
|
||||
39
.github/workflows/release_to_homebrew.yml
vendored
39
.github/workflows/release_to_homebrew.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Publish to Homebrew
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
homebrew-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Cache Homebrew Bundler RubyGems
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
||||
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
||||
restore-keys: ${{ runner.os }}-rubygems-
|
||||
|
||||
- name: Install Homebrew Bundler RubyGems
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: brew install-bundler-gems
|
||||
- name: Bump formulae
|
||||
uses: Homebrew/actions/bump-packages@master
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Custom GitHub access token with only the 'public_repo' scope enabled
|
||||
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
||||
# Bump only these formulae if outdated
|
||||
formulae: |
|
||||
topgrade
|
||||
99
.github/workflows/release_to_pypi.yml
vendored
99
.github/workflows/release_to_pypi.yml
vendored
@@ -1,99 +0,0 @@
|
||||
name: Update PyPi
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --skip-existing *
|
||||
13
.github/workflows/release_to_winget.yml
vendored
13
.github/workflows/release_to_winget.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Publish to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@main
|
||||
with:
|
||||
identifier: topgrade-rs.topgrade
|
||||
max-versions-to-keep: 5 # keep only latest 5 versions
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
59
.github/workflows/release_win_osx.yml
vendored
Normal file
59
.github/workflows/release_win_osx.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: CD Win/MacOS Native
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ macos-latest, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run tests
|
||||
with:
|
||||
command: test
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Build
|
||||
with:
|
||||
command: build
|
||||
args: --release --all-features
|
||||
- name: Rename Release (Unix)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
if: ${{ matrix.platform != 'windows-latest' }}
|
||||
shell: bash
|
||||
- name: Rename Release (Windows)
|
||||
run: |
|
||||
cargo install default-target
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
||||
mv target/release/topgrade.exe assets/topgrade.exe
|
||||
cd assets
|
||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||
rm topgrade.exe
|
||||
ls .
|
||||
if: ${{ matrix.platform == 'windows-latest' }}
|
||||
shell: bash
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: assets/*
|
||||
24
.github/workflows/rust-ubuntu.yml
vendored
Normal file
24
.github/workflows/rust-ubuntu.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "dev" ]
|
||||
pull_request:
|
||||
branches: [ "master", "dev" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Fmt
|
||||
run: cargo fmt --check --all
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
57
.github/workflows/test.yaml
vendored
Normal file
57
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Test with Code Coverage
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
env:
|
||||
PROJECT_NAME_UNDERSCORE: topgrade
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort
|
||||
RUSTDOCFLAGS: -Cpanic=abort
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-dependencies
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Cargo.lock') }}
|
||||
- name: Generate test result and coverage report
|
||||
run: |
|
||||
cargo install cargo2junit grcov;
|
||||
cargo test $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > results.xml;
|
||||
zip -0 ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print`;
|
||||
grcov ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info;
|
||||
- name: Upload test results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||
with:
|
||||
check_name: Test Results
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: results.xml
|
||||
- name: Upload to CodeCov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
# required for private repositories:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./lcov.info
|
||||
fail_ci_if_error: true
|
||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,20 +1,4 @@
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Generic build outputs
|
||||
/build
|
||||
|
||||
# Specific for some languages like Rust
|
||||
/target
|
||||
|
||||
# LLVM profiling output
|
||||
*.profraw
|
||||
|
||||
# Backup files for any .rs files in the project
|
||||
/build
|
||||
**/*.rs.bk
|
||||
|
||||
38
.vscode/launch.json
vendored
Normal file
38
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Topgrade",
|
||||
"console": "integratedTerminal",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=topgrade",
|
||||
"--package=topgrade"
|
||||
],
|
||||
"filter": {
|
||||
"name": "topgrade",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"--only",
|
||||
"${input:step}",
|
||||
"-v"
|
||||
],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "promptString",
|
||||
"id": "step",
|
||||
"description": "step name",
|
||||
}
|
||||
]
|
||||
}
|
||||
14
.vscode/tasks.json
vendored
Normal file
14
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "clippy",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test",
|
||||
"label": "rust: cargo clippy"
|
||||
}
|
||||
]
|
||||
}
|
||||
50
.vscode/topgrade.code-snippets
vendored
Normal file
50
.vscode/topgrade.code-snippets
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
// Place your topgrade workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Skip Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "skipstep",
|
||||
"body": [
|
||||
"return Err(SkipStep(format!(\"$1\")).into());"
|
||||
]
|
||||
},
|
||||
"Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "step",
|
||||
"body": [
|
||||
"pub fn $1(ctx: &ExecutionContext) -> Result<()> {",
|
||||
" $0",
|
||||
" Ok(())",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"Require Binary": {
|
||||
"scope": "rust",
|
||||
"prefix": "req",
|
||||
"description": "Require a binary to be installed",
|
||||
"body": [
|
||||
"let ${1:binary} = require(\"${1:binary}\")?;"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"scope": "rust",
|
||||
"prefix": "macos",
|
||||
"body": [
|
||||
"#[cfg(target_os = \"macos\")]"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# Containers step
|
||||
|
||||
* New default behavior: In the previous versions, if you have both Docker and
|
||||
Podman installed, Podman will be used by Topgrade. Now the default option
|
||||
has been changed to Docker. This can be overridden by setting the
|
||||
`containers.runtime` option in the configuration TOML to "podman".
|
||||
@@ -1,128 +0,0 @@
|
||||
# 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.
|
||||
152
CONTRIBUTING.md
152
CONTRIBUTING.md
@@ -1,152 +0,0 @@
|
||||
## Contributing to `topgrade`
|
||||
|
||||
Thank you for your interest in contributing to `topgrade`!
|
||||
We welcome and encourage contributions of all kinds, such as:
|
||||
|
||||
1. Issue reports or feature requests
|
||||
2. Documentation improvements
|
||||
3. Code (PR or PR Review)
|
||||
|
||||
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
||||
for commit messages.
|
||||
|
||||
## Adding a new `step`
|
||||
|
||||
In `topgrade`'s term, package manager is called `step`.
|
||||
To add a new `step` to `topgrade`:
|
||||
|
||||
1. Add a new variant to
|
||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
|
||||
|
||||
```rust
|
||||
pub enum Step {
|
||||
// Existed steps
|
||||
// ...
|
||||
|
||||
// Your new step here!
|
||||
// You may want it to be sorted alphabetically because that looks great:)
|
||||
Xxx,
|
||||
}
|
||||
```
|
||||
|
||||
2. Implement the update function
|
||||
|
||||
You need to find the appropriate location where this update function goes, it should be
|
||||
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/master/src/steps),
|
||||
the file names are self-explanatory, for example, `step`s related to `zsh` are
|
||||
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/steps/zsh.rs).
|
||||
|
||||
Then you implement the update function, and put it in the file where it belongs.
|
||||
|
||||
```rust
|
||||
pub fn run_xxx(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Check if this step is installed, if not, then this update will be skipped.
|
||||
let xxx = require("xxx")?;
|
||||
|
||||
// Print the separator
|
||||
print_separator("xxx");
|
||||
|
||||
// Invoke the new step to get things updated!
|
||||
ctx.run_type()
|
||||
.execute(xxx)
|
||||
.arg(/* args required by this step */)
|
||||
.status_checked()
|
||||
}
|
||||
```
|
||||
|
||||
Such a update function would be conventionally named `run_xxx()`, where `xxx`
|
||||
is the name of the new step, and it should take a argument of type
|
||||
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
|
||||
needed (You can find some examples where extra arguments are needed
|
||||
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
|
||||
|
||||
Update function would usually do 3 things:
|
||||
1. Check if the step is installed
|
||||
2. Output the Separator
|
||||
3. Invoke the step
|
||||
|
||||
Still, this is sufficient for most tools, but you may need some extra stuff
|
||||
with complicated `step`.
|
||||
|
||||
3. Finally, invoke that update function in `main.rs`
|
||||
|
||||
```rust
|
||||
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
||||
```
|
||||
|
||||
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
||||
to separate the steps, for example, for steps that are Linux-only, it goes
|
||||
like this:
|
||||
|
||||
```
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Xxx is Linux-only
|
||||
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
||||
}
|
||||
```
|
||||
|
||||
Congrats, you just added a new `step`:)
|
||||
|
||||
## Modification to the configuration entries
|
||||
|
||||
If your PR has the configuration options
|
||||
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/config.rs))
|
||||
modified:
|
||||
|
||||
1. Adding new options
|
||||
2. Changing the existing options
|
||||
|
||||
Be sure to apply your changes to
|
||||
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
|
||||
and have some basic documentations guiding user how to use these options.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
|
||||
it should be written in Markdown and wrapped at 80, for example:
|
||||
|
||||
```md
|
||||
1. The configuration location has been updated to x.
|
||||
|
||||
2. The step x has been removed.
|
||||
|
||||
3. ...
|
||||
```
|
||||
|
||||
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
|
||||
## Before you submit your PR
|
||||
|
||||
Make sure your patch passes the following tests on your host:
|
||||
|
||||
```shell
|
||||
$ cargo build
|
||||
$ cargo fmt
|
||||
$ cargo clippy
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
Don't worry about other platforms, we have most of them covered in our CI.
|
||||
|
||||
## Some tips
|
||||
|
||||
1. Locale
|
||||
|
||||
Some `step` respects locale, which means their output can be in language other
|
||||
than English, we should not do check on it.
|
||||
|
||||
For example, one may want to check if a tool works by doing this:
|
||||
|
||||
```rust
|
||||
let output = Command::new("xxx").arg("--help").output().unwrap();
|
||||
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
||||
|
||||
if stdout.contains("help") {
|
||||
// xxx works
|
||||
}
|
||||
```
|
||||
|
||||
If `xxx` respects locale, then the above code should work on English system,
|
||||
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
||||
translated to `"帮助"`, and the above code won't work.
|
||||
3013
Cargo.lock
generated
3013
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
101
Cargo.toml
101
Cargo.toml
@@ -1,14 +1,13 @@
|
||||
[package]
|
||||
name = "topgrade"
|
||||
description = "Upgrade all the things"
|
||||
name = "topgrade-rs"
|
||||
description = "Upgrade all the things, successor of topgrade"
|
||||
categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
rust-version = "1.76.0"
|
||||
version = "16.0.1"
|
||||
version = "10.0.1"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||
exclude = ["doc/screenshot.gif"]
|
||||
edition = "2021"
|
||||
|
||||
readme = "README.md"
|
||||
@@ -17,70 +16,44 @@ readme = "README.md"
|
||||
name = "topgrade"
|
||||
path = "src/main.rs"
|
||||
|
||||
##[lib]
|
||||
##name = "topgrade_lib"
|
||||
|
||||
[dependencies]
|
||||
home = "~0.5"
|
||||
etcetera = "~0.8"
|
||||
once_cell = "~1.19"
|
||||
serde = { version = "~1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
which_crate = { version = "~6.0", package = "which" }
|
||||
shellexpand = "~3.1"
|
||||
clap = { version = "~4.5", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.5"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.5"
|
||||
console = "~0.15"
|
||||
lazy_static = "~1.4"
|
||||
chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.26", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.10"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.38", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
regex = "~1.10"
|
||||
semver = "~1.0"
|
||||
shell-words = "~1.1"
|
||||
color-eyre = "~0.6"
|
||||
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
||||
merge = "~0.1"
|
||||
regex-split = "~0.1"
|
||||
notify-rust = "~4.11"
|
||||
wildmatch = "2.3.0"
|
||||
rust-i18n = "3.0.1"
|
||||
sys-locale = "0.3.1"
|
||||
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"
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
|
||||
[package.metadata.generate-rpm.requires]
|
||||
git = "*"
|
||||
|
||||
[package.metadata.deb]
|
||||
name = "topgrade"
|
||||
maintainer = "Chris Gelatt <kreeblah@gmail.com>"
|
||||
copyright = "2024, Topgrade Team"
|
||||
license-file = ["LICENSE", "0"]
|
||||
depends = "$auto"
|
||||
extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them."
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
default-features = true
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
notify-rust = "4.5"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.21"
|
||||
self_update_crate = { version = "~0.40", 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.40", 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
|
||||
|
||||
118
README.md
118
README.md
@@ -1,111 +1,59 @@
|
||||
<div align="center">
|
||||
<h1>
|
||||
<img alt="Topgrade" src="doc/topgrade_transparent.png" width="850px">
|
||||
</h1>
|
||||
|
||||
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a>
|
||||
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||

|
||||
<!---
|
||||

|
||||
[](https://crates.io/crates/topgrade)
|
||||
[](https://aur.archlinux.org/packages/topgrade/)
|
||||
 -->
|
||||
--->
|
||||
|
||||
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||
</div>
|
||||

|
||||
|
||||
## Fork
|
||||
This is a fork of [topgrade by r-darwish](https://github.com/r-darwish/topgrade) to keep it maintained.
|
||||
|
||||
## Introduction
|
||||
|
||||
> **Note**
|
||||
> This is a fork of [topgrade by r-darwish](https://github.com/r-darwish/topgrade) to keep it maintained.
|
||||
|
||||
Keeping your system up to date usually involves invoking multiple package managers.
|
||||
This results in big, non-portable shell one-liners saved in your shell.
|
||||
To remedy this, **Topgrade** detects which tools you use and runs the appropriate commands to update them.
|
||||
To remedy this, _topgrade_ detects which tools you use and runs the appropriate commands to update them.
|
||||
|
||||
## Installation
|
||||
- Arch Linux: [AUR](https://aur.archlinux.org/packages/topgrade/) package.
|
||||
- NixOS: _topgrade_ package in `nixpkgs`.
|
||||
- macOS: [Homebrew](https://brew.sh/) or [MacPorts](https://www.macports.org/install.php).
|
||||
|
||||
[](https://repology.org/project/topgrade/versions)
|
||||
|
||||
- Arch Linux: [AUR](https://aur.archlinux.org/packages/topgrade)
|
||||
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
|
||||
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
|
||||
- Windows: [Chocolatey][choco], [Scoop][scoop] or [Winget][winget]
|
||||
- PyPi: [pip](https://pypi.org/project/topgrade/)
|
||||
|
||||
[choco]: https://community.chocolatey.org/packages/topgrade
|
||||
[scoop]: https://scoop.sh/#/apps?q=topgrade
|
||||
[winget]: https://winstall.app/apps/topgrade-rs.topgrade
|
||||
|
||||
Other systems users can either use `cargo install` or the compiled binaries from the release page.
|
||||
Other systems users can either use `cargo install` or use the compiled binaries from the release page.
|
||||
The compiled binaries contain a self-upgrading feature.
|
||||
|
||||
Topgrade requires Rust 1.51 or above.
|
||||
|
||||
## Documentation[WIP]
|
||||
You can visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) .
|
||||
|
||||
## Usage
|
||||
|
||||
Just run `topgrade`.
|
||||
See [the wiki](https://github.com/r-darwish/topgrade/wiki/Step-list) for the list of things Topgrade supports.
|
||||
|
||||
## Configuration
|
||||
|
||||
## Customization
|
||||
See `config.example.toml` for an example configuration file.
|
||||
|
||||
## Migration and Breaking Changes
|
||||
### Configuration path
|
||||
|
||||
Whenever there is a **breaking change**, the major version number will be bumped,
|
||||
and we will document these changes in the release note, please take a look at
|
||||
it when updated to a major release.
|
||||
The configuration should be placed in the following paths depending by the operating system:
|
||||
|
||||
> Got a question? Feel free to open an issue or discussion!
|
||||
|
||||
### Configuration Path
|
||||
|
||||
#### `CONFIG_DIR` on each platform
|
||||
- **Windows**: `%APPDATA%`
|
||||
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
|
||||
|
||||
`topgrade` will look for the configuration file in the following places, in order of priority:
|
||||
|
||||
1. `CONFIG_DIR/topgrade.toml`
|
||||
2. `CONFIG_DIR/topgrade/topgrade.toml`
|
||||
|
||||
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be ignored.
|
||||
|
||||
On the first run(no configuration file exists), `topgrade` will create a configuration file at `CONFIG_DIR/topgrade.toml` for you.
|
||||
|
||||
### Custom Commands
|
||||
|
||||
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as required.
|
||||
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
||||
|
||||
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you can add `-i` at the start of your custom command.
|
||||
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
|
||||
|
||||
## Remote Execution
|
||||
|
||||
You can specify a key called `remote_topgrades` in the configuration file.
|
||||
This key should contain a list of hostnames that have Topgrade installed on them.
|
||||
Topgrade will use `ssh` to run `topgrade` on remote hosts before acting locally.
|
||||
To limit the execution only to specific hosts use the `--remote-host-limit` parameter.
|
||||
* **Windows** - `%APPDATA%/topgrade.toml`
|
||||
* **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml`
|
||||
|
||||
## Contribution
|
||||
|
||||
### Problems or missing features?
|
||||
|
||||
Open a new issue describing your problem and if possible provide a solution.
|
||||
|
||||
Open a new Issue describing your problem and if possible with a possible solution.
|
||||
### Missing a feature or found an unsupported tool/distro?
|
||||
|
||||
Just let us now what you are missing by opening an issue.
|
||||
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of its usage.
|
||||
|
||||
For tools please open an Issue describing the tool, which platforms it supports and if possible, give us an example of its usage.
|
||||
### Want to contribute to the code?
|
||||
|
||||
Just fork the repository and start coding.
|
||||
|
||||
### Contribution Guidelines
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Add a proper testing framework to the code base.
|
||||
- [ ] Add unit tests for package managers.
|
||||
- [ ] Split up code into more maintainable parts, eg. putting every linux package manager in a own submodule of linux.rs.
|
||||
## Remote execution
|
||||
You can specify a key called `remote_topgrades` in the configuration file.
|
||||
This key should contain a list of hostnames that have topgrade installed on them.
|
||||
Topgrade will use `ssh` to run `topgrade` on remote hosts before acting locally.
|
||||
To limit the execution only to specific hosts use the `--remote-host-limit` parameter.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
> This document lists the steps that lead to a successful release of Topgrade.
|
||||
|
||||
1. Open a PR that:
|
||||
|
||||
> Here is an [Example PR](https://github.com/topgrade-rs/topgrade/pull/652)
|
||||
> that you can refer to.
|
||||
|
||||
1. bumps the version number.
|
||||
|
||||
> If there are breaking changes, the major version number should be increased.
|
||||
|
||||
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||
|
||||
```sh'
|
||||
$ cd topgrade
|
||||
$ mv BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
|
||||
$ touch BREAKINGCHANGES_dev.md
|
||||
```
|
||||
|
||||
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
[breaking_changes]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES.md
|
||||
|
||||
2. Check and merge that PR.
|
||||
|
||||
3. Go to the [release](https://github.com/topgrade-rs/topgrade/releases) page
|
||||
and click the [Draft a new release button](https://github.com/topgrade-rs/topgrade/releases/new)
|
||||
|
||||
4. Write the release notes
|
||||
|
||||
We usually use GitHub's [Automatically generated release notes][auto_gen_release_notes]
|
||||
functionality to generate release notes, but you write your own one instead.
|
||||
|
||||
[auto_gen_release_notes]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
|
||||
|
||||
5. Attaching binaries
|
||||
|
||||
You don't need to do this as our CI will automatically do it for you,
|
||||
binaries for Linux, macOS and Windows will be created and attached.
|
||||
|
||||
And the CI will publish the new binary to:
|
||||
|
||||
1. AUR
|
||||
2. PyPi
|
||||
3. Homebrew (seems that this is not working correctly)
|
||||
4. Winget
|
||||
|
||||
6. Manually release it to Crates.io
|
||||
|
||||
> Yeah, this is unfortunate, our CI won't do this for us. We should probably add one.
|
||||
|
||||
1. `cd` to the Topgrade directory, make sure that it is the latest version
|
||||
(i.e., including the PR that bumps the version number).
|
||||
2. Set up your token with `cargo login`.
|
||||
3. Dry-run the publish `cargo publish --dry-run`.
|
||||
4. If step 3 works, then do the final release `cargo publish`.
|
||||
|
||||
> You can also take a look at the official tutorial [Publishing on crates.io][doc]
|
||||
>
|
||||
> [doc]: https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
11
SECURITY.md
11
SECURITY.md
@@ -1,11 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We only support the latest major version and each subversion.
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| 15.0.x | :white_check_mark: |
|
||||
| < 15.0 | :x: |
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
disallowed-methods = [
|
||||
{ path = "std::process::Command::output", reason = "Use `output_checked[_with][_utf8]`" },
|
||||
{ path = "std::process::Command::spawn", reason = "Use `spawn_checked`" },
|
||||
{ path = "std::process::Command::status", reason = "Use `status_checked`" },
|
||||
]
|
||||
@@ -1,267 +1,109 @@
|
||||
# Include any additional configuration file(s)
|
||||
# [include] sections are processed in the order you write them
|
||||
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
||||
[include]
|
||||
# paths = ["/etc/topgrade.toml"]
|
||||
|
||||
|
||||
[misc]
|
||||
# Run `sudo -v` to cache credentials at the start of the run
|
||||
# This avoids a blocking password prompt in the middle of an unattended run
|
||||
# (default: false)
|
||||
# pre_sudo = false
|
||||
|
||||
# Sudo command to be used
|
||||
# sudo_command = "sudo"
|
||||
# Don't ask for confirmations
|
||||
#assume_yes = true
|
||||
|
||||
# Disable specific steps - same options as the command line flag
|
||||
# disable = ["system", "emacs"]
|
||||
#disable = ["system", "emacs"]
|
||||
|
||||
# Ignore failures for these steps
|
||||
# ignore_failures = ["powershell"]
|
||||
|
||||
# List of remote machines with Topgrade installed on them
|
||||
# remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Path to Topgrade executable on remote machines
|
||||
# remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
|
||||
# Arguments to pass to SSH when upgrading remote systems
|
||||
# ssh_arguments = "-o ConnectTimeout=2"
|
||||
|
||||
# Arguments to pass tmux when pulling Repositories
|
||||
# tmux_arguments = "-S /var/tmux.sock"
|
||||
|
||||
# Do not set the terminal title (default: true)
|
||||
# set_title = true
|
||||
|
||||
# Display the time in step titles (default: true)
|
||||
# display_time = true
|
||||
|
||||
# Don't ask for confirmations (no default value)
|
||||
# assume_yes = true
|
||||
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
# no_retry = true
|
||||
|
||||
# Run inside tmux (default: false)
|
||||
# run_in_tmux = true
|
||||
|
||||
# Changes the way topgrade interacts with
|
||||
# the tmux session, creating the session
|
||||
# and only attaching to it if not inside tmux
|
||||
# (default: "attach_if_not_in_session", allowed values: "attach_if_not_in_session", "attach_always")
|
||||
# tmux_session_mode = "attach_if_not_in_session"
|
||||
|
||||
# Cleanup temporary or old files (default: false)
|
||||
# cleanup = true
|
||||
|
||||
# Send a notification for every step (default: false)
|
||||
# notify_each_step = false
|
||||
|
||||
# Skip sending a notification at the end of a run (default: false)
|
||||
# skip_notify = true
|
||||
|
||||
# The Bash-it branch to update (default: "stable")
|
||||
# bashit_branch = "stable"
|
||||
#ignore_failures = ["powershell"]
|
||||
|
||||
# Run specific steps - same options as the command line flag
|
||||
# only = ["system", "emacs"]
|
||||
#only = ["system", "emacs"]
|
||||
|
||||
# Whether to self update
|
||||
#
|
||||
# this will be ignored if the binary is built without self update support
|
||||
#
|
||||
# available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||
# no_self_update = true
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
#no_retry = true
|
||||
|
||||
# Extra tracing filter directives
|
||||
# These are prepended to the `--log-filter` argument
|
||||
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
# log_filters = ["topgrade::command=debug", "warn"]
|
||||
# Run inside tmux
|
||||
#run_in_tmux = true
|
||||
|
||||
# List of remote machines with Topgrade installed on them
|
||||
#remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Arguments to pass SSH when upgrading remote systems
|
||||
#ssh_arguments = "-o ConnectTimeout=2"
|
||||
|
||||
# Path to Topgrade executable on remote machines
|
||||
#remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
|
||||
# Arguments to pass tmux when pulling Repositories
|
||||
#tmux_arguments = "-S /var/tmux.sock"
|
||||
|
||||
# Do not set the terminal title
|
||||
#set_title = false
|
||||
|
||||
# Display the time in step titles
|
||||
# display_time = true
|
||||
|
||||
# Cleanup temporary or old files
|
||||
#cleanup = true
|
||||
|
||||
# Skip sending a notification at the end of a run
|
||||
#skip_notify = true
|
||||
|
||||
[git]
|
||||
#max_concurrency = 5
|
||||
# Additional git repositories to pull
|
||||
#repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
#]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
#pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
#arguments = "--rebase --autostash"
|
||||
|
||||
[composer]
|
||||
#self_update = true
|
||||
|
||||
# Commands to run before anything
|
||||
[pre_commands]
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
|
||||
# Commands to run after anything
|
||||
[post_commands]
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
# Custom commands
|
||||
[commands]
|
||||
# "Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
# "Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||
|
||||
|
||||
[python]
|
||||
# enable_pip_review = true ###disabled by default
|
||||
# enable_pip_review_local = true ###disabled by default
|
||||
# enable_pipupgrade = true ###disabled by default
|
||||
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
|
||||
|
||||
[composer]
|
||||
# self_update = true
|
||||
|
||||
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
|
||||
[brew]
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` exists, then use the `-a` option.
|
||||
# Otherwise, use the `--greedy` option.
|
||||
# greedy_cask = true
|
||||
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_latest` option.
|
||||
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
|
||||
# both of them, they won't clash with each other.
|
||||
# greedy_latest = true
|
||||
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_auto_updates` option.
|
||||
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
|
||||
# both of them, they won't clash with each other.
|
||||
# greedy_auto_updates = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Execute `brew autoremove` after the step.
|
||||
# autoremove = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Upgrade formulae built from the HEAD branch; `brew upgrade --fetch-HEAD`
|
||||
# fetch_head = true
|
||||
|
||||
#greedy_cask = true
|
||||
#autoremove = true
|
||||
|
||||
[linux]
|
||||
# Arch Package Manager to use.
|
||||
# Allowed values:
|
||||
# autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay
|
||||
# arch_package_manager = "pacman"
|
||||
|
||||
# Arch Package Manager to use. Allowed values: autodetect, trizen, aura, paru, yay, pikaur, pacman, pamac.
|
||||
#arch_package_manager = "pacman"
|
||||
# Arguments to pass yay (or paru) when updating packages
|
||||
# yay_arguments = "--nodevel"
|
||||
|
||||
# Arguments to pass dnf when updating packages
|
||||
# dnf_arguments = "--refresh"
|
||||
|
||||
# aura_aur_arguments = "-kx"
|
||||
|
||||
# aura_pacman_arguments = ""
|
||||
# garuda_update_arguments = ""
|
||||
|
||||
# show_arch_news = true
|
||||
|
||||
# trizen_arguments = "--devel"
|
||||
|
||||
# pikaur_arguments = ""
|
||||
|
||||
# pamac_arguments = "--no-devel"
|
||||
|
||||
# enable_tlmgr = true
|
||||
|
||||
# emerge_sync_flags = "-q"
|
||||
|
||||
# emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
|
||||
# redhat_distro_sync = false
|
||||
|
||||
# suse_dup = false
|
||||
|
||||
# rpm_ostree = false
|
||||
|
||||
# nix_arguments = "--flake"
|
||||
|
||||
# nix_env_arguments = "--prebuilt-only"
|
||||
|
||||
# Extra Home Manager arguments
|
||||
# home_manager_arguments = ["--flake", "file"]
|
||||
|
||||
|
||||
[git]
|
||||
# How many repos to pull at max in parallel
|
||||
# max_concurrency = 5
|
||||
|
||||
# Additional git repositories to pull
|
||||
# repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
# ]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
# pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
# arguments = "--rebase --autostash"
|
||||
|
||||
#yay_arguments = "--nodevel"
|
||||
#aura_aur_arguments = "-kx"
|
||||
#aura_pacman_arguments = ""
|
||||
#show_arch_news = true
|
||||
#trizen_arguments = "--devel"
|
||||
#pikaur_arguments = ""
|
||||
#pamac_arguments = "--no-devel"
|
||||
#enable_tlmgr = true
|
||||
#emerge_sync_flags = "-q"
|
||||
#emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
#redhat_distro_sync = false
|
||||
#rpm_ostree = false
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
# accept_all_updates = false
|
||||
|
||||
# open_remotes_in_new_terminal = true
|
||||
|
||||
# wsl_update_pre_release = true
|
||||
|
||||
# wsl_update_use_web_download = true
|
||||
#accept_all_updates = false
|
||||
#open_remotes_in_new_terminal = true
|
||||
|
||||
# Causes Topgrade to rename itself during the run to allow package managers
|
||||
# to upgrade it. Use this only if you installed Topgrade by using a package
|
||||
# manager such as Scoop or Cargo
|
||||
# self_rename = true
|
||||
|
||||
#self_rename = true
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[yarn]
|
||||
# Run `yarn global upgrade` with `sudo`
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[vim]
|
||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||
# force_plug_update = true
|
||||
|
||||
#use_sudo = true
|
||||
|
||||
[firmware]
|
||||
# Offer to update firmware; if false just check for and display available updates
|
||||
# upgrade = true
|
||||
|
||||
|
||||
[vagrant]
|
||||
# Vagrant directories
|
||||
# directories = []
|
||||
|
||||
# power on vagrant boxes if needed
|
||||
# power_on = true
|
||||
|
||||
# Always suspend vagrant boxes instead of powering off
|
||||
# always_suspend = true
|
||||
|
||||
#upgrade = true
|
||||
|
||||
[flatpak]
|
||||
# Use sudo for updating the system-wide installation
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[distrobox]
|
||||
# use_root = false
|
||||
|
||||
# containers = ["archlinux-latest"]
|
||||
[containers]
|
||||
# Specify the containers to ignore while updating (Wildcard supported)
|
||||
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"]
|
||||
# Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman")
|
||||
# runtime = "podman"
|
||||
|
||||
[lensfun]
|
||||
# If disabled, Topgrade invokes `lensfun‑update‑data` without root priviledge,
|
||||
# then the update will be only available to you. Otherwise, `sudo` is required,
|
||||
# and the update will be installed system-wide, i.e., available to all users.
|
||||
# (default: false)
|
||||
# use_sudo = false
|
||||
#use_sudo = true
|
||||
|
||||
BIN
doc/screenshot.gif
Normal file
BIN
doc/screenshot.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 718 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
464
locales/app.yml
464
locales/app.yml
@@ -1,464 +0,0 @@
|
||||
_version: 2
|
||||
|
||||
"Current system locale is {system_locale}":
|
||||
en: "Current system locale is %{system_locale}"
|
||||
zh_TW: "目前語言為 %{system_locale}"
|
||||
"Dry running: {program_name} {arguments}":
|
||||
en: "Dry running: %{program_name} %{arguments}"
|
||||
zh_TW: "正在模擬 %{program_name} %{arguments} 的執行過程"
|
||||
"in {directory}":
|
||||
en: "in %{directory}"
|
||||
zh_TW: "在 %{directory}"
|
||||
"Rebooting...":
|
||||
en: "Rebooting..."
|
||||
zh_TW: "正在重新啟動..."
|
||||
"Plugins upgraded":
|
||||
en: "Plugins upgraded"
|
||||
zh_TW: "已更新所有擴充功能"
|
||||
"Would self-update":
|
||||
en: "Would self-update"
|
||||
zh_TW: "將自行更新"
|
||||
"Pulling":
|
||||
en: "Pulling"
|
||||
zh_TW: "正在拉取"
|
||||
"No Breaking changes":
|
||||
en: "No Breaking changes"
|
||||
zh_TW: "無重大更改"
|
||||
"Dropping you to shell. Fix what you need and then exit the shell.":
|
||||
en: "Dropping you to shell. Fix what you need and then exit the shell."
|
||||
zh_TW: "已切換到終端殼層。修復完畢後請退出殼層以繼續。"
|
||||
"Topgrade launched in a new tmux session":
|
||||
en: "Topgrade launched in a new tmux session"
|
||||
zh_TW: "Topgrade 已啟動新 tmux 程序"
|
||||
'Topgrade upgraded to {version}:\n':
|
||||
en: 'Topgrade upgraded to %{version}:\n'
|
||||
zh_TW: '已將 Topgrade 更新至 %{version}:\n'
|
||||
"Topgrade is up-to-date":
|
||||
en: "Topgrade is up-to-date"
|
||||
zh_TW: "Topgrade 為最新版本"
|
||||
"Updating modules...":
|
||||
en: "Updating modules..."
|
||||
zh_TW: "正在更新模組..."
|
||||
"Powershell Modules Update":
|
||||
en: "Powershell Modules Update"
|
||||
zh_TW: "Powershell 模組更新"
|
||||
"Powershell is not installed":
|
||||
en: "Powershell is not installed"
|
||||
zh_TW: "未安裝 Powershell"
|
||||
"Error detecting current distribution: {error}":
|
||||
en: "Error detecting current distribution: %{error}"
|
||||
zh_TW: "無法偵測作業系統:%{error}"
|
||||
"Error: {error}":
|
||||
en: "Error: %{error}"
|
||||
zh_TW: "錯誤:%{error}"
|
||||
"Failed":
|
||||
en: "Failed"
|
||||
zh_TW: "失敗"
|
||||
"pulling":
|
||||
en: "pulling"
|
||||
zh_TW: "正在拉取"
|
||||
"Changed":
|
||||
en: "Changed"
|
||||
zh_TW: "已更改"
|
||||
"Up-to-date":
|
||||
en: "Up-to-date"
|
||||
zh_TW: "已為最新版本"
|
||||
"Self update":
|
||||
en: "Self update"
|
||||
zh_TW: "自行更新"
|
||||
|
||||
# The following 2 strings are used in the same sentence
|
||||
# i.e. *Only* updated repositories will be shown...
|
||||
# Note that the text *Only* is highlighted in green
|
||||
|
||||
"Only":
|
||||
en: "Only"
|
||||
zh_TW: "將僅"
|
||||
"updated repositories will be shown...":
|
||||
en: "updated repositories will be shown..."
|
||||
zh_TW: "顯示被更新的 git 來源..."
|
||||
|
||||
"because it has no remotes":
|
||||
en: "because it has no remotes"
|
||||
zh_TW: "因為其沒有遠端來源"
|
||||
"Skipping":
|
||||
en: "Skipping"
|
||||
zh_TW: "正在略過"
|
||||
"Aura(<0.4.6) requires sudo installed to work with AUR packages":
|
||||
en: "Aura(<0.4.6) requires sudo installed to work with AUR packages"
|
||||
zh_TW: "Aura(<0.4.6)依賴 sudo 安裝 AUR 套件"
|
||||
"Pacman backup configuration files found:":
|
||||
en: "Pacman backup configuration files found:"
|
||||
zh_TW: "找到 Pacman 設定備份檔:"
|
||||
"The package audit was successful, but vulnerable packages still remain on the system":
|
||||
en: "The package audit was successful, but vulnerable packages still remain on the system"
|
||||
zh_TW: "雖然套件檢測成功,但系統仍然包含危險套件"
|
||||
"Syncing portage":
|
||||
en: "Syncing portage"
|
||||
zh_TW: "正在同步 portage"
|
||||
"Finding available software":
|
||||
en: "Finding available software"
|
||||
zh_TW: "正在尋找軟體"
|
||||
"A system update is available. Do you wish to install it?":
|
||||
en: "A system update is available. Do you wish to install it?"
|
||||
zh_TW: "系統更新已就緒。是否現在安裝?"
|
||||
"No new software available.":
|
||||
en: "No new software available."
|
||||
zh_TW: "沒有新軟體。"
|
||||
"No Xcode releases installed.":
|
||||
en: "No Xcode releases installed."
|
||||
zh_TW: "尚未安裝 Xcode 發行版。"
|
||||
"Would you like to move the former Xcode release to the trash?":
|
||||
en: "Would you like to move the former Xcode release to the trash?"
|
||||
zh_TW: "是否將舊版 Xcode 移至垃圾桶?"
|
||||
"New Xcode release detected:":
|
||||
en: "New Xcode release detected:"
|
||||
zh_TW: "有新的 Xcode 版本:"
|
||||
"Would you like to install it?":
|
||||
en: "Would you like to install it?"
|
||||
zh_TW: "是否現在安裝?"
|
||||
"No global packages installed":
|
||||
en: "No global packages installed"
|
||||
zh_TW: "尚未安裝全域套件"
|
||||
"Remote Topgrade launched in Tmux":
|
||||
en: "Remote Topgrade launched in Tmux"
|
||||
zh_TW: "已在 Tmux 中啟動遠端 Topgrade 程序"
|
||||
"Remote Topgrade launched in an external terminal":
|
||||
en: "Remote Topgrade launched in an external terminal"
|
||||
zh_TW: "已在新終端機視窗中啟動遠端 Topgrade"
|
||||
"Collecting Vagrant boxes":
|
||||
en: "Collecting Vagrant boxes"
|
||||
zh_TW: "正在蒐集 Vagrant 容器"
|
||||
"No Vagrant directories were specified in the configuration file":
|
||||
en: "No Vagrant directories were specified in the configuration file"
|
||||
zh_TW: "尚未在設定檔中指定 Vagrant 資料夾"
|
||||
"Vagrant boxes":
|
||||
en: "Vagrant boxes"
|
||||
zh_TW: "Vagrant 容器"
|
||||
"No outdated boxes":
|
||||
en: "No outdated boxes"
|
||||
zh_TW: "未有需要更新的容器"
|
||||
"Summary":
|
||||
en: "Summary"
|
||||
zh_TW: "結果"
|
||||
"Topgrade finished with errors":
|
||||
en: "Topgrade finished with errors"
|
||||
zh_TW: "Topgrade 執行部分成功"
|
||||
"Topgrade finished successfully":
|
||||
en: "Topgrade finished successfully"
|
||||
zh_TW: "Topgrade 執行成功"
|
||||
"Topgrade {version_str} Breaking Changes":
|
||||
en: "Topgrade %{version_str} Breaking Changes"
|
||||
zh_TW: "Topgrade %{version_str} 重大更改"
|
||||
"Path {path} expanded to {expanded}":
|
||||
en: "Path %{path} expanded to %{expanded}"
|
||||
zh_TW: "已擴展 %{path} 至 %{expanded}"
|
||||
"Path {path} doesn't exist":
|
||||
en: "Path %{path} doesn't exist"
|
||||
zh_TW: "路徑 %{path} 不存在"
|
||||
"Cannot find {binary_name} in PATH":
|
||||
en: "Cannot find %{binary_name} in PATH"
|
||||
zh_TW: "在 $PATH 中找不到 %{binary_name} 執行檔"
|
||||
"Failed to get a UTF-8 encoded hostname":
|
||||
en: "Failed to get a UTF-8 encoded hostname"
|
||||
zh_TW: "無法取得 UTF-8 編碼的主機名稱"
|
||||
"Failed to get hostname: {err}":
|
||||
en: "Failed to get hostname: %{err}"
|
||||
zh_TW: "無法取得主機名稱:%{err}"
|
||||
"{python} is a Python 2, skip.":
|
||||
en: "%{python} is a Python 2, skip."
|
||||
zh_TW: "%{python} 是 Python 2,略過。"
|
||||
"{python} is a Python shim, skip.":
|
||||
en: "%{python} is a Python shim, skip."
|
||||
zh_TW: "%{python} 是 Python shim,略過。"
|
||||
"{key} failed:":
|
||||
en: "%{key} failed:"
|
||||
zh_TW: "%{key} 失敗:"
|
||||
"{step_name} failed":
|
||||
en: "%{step_name} failed"
|
||||
zh_TW: "%{step_name} 失敗"
|
||||
"DragonFly BSD Packages":
|
||||
en: "DragonFly BSD Packages"
|
||||
zh_TW: "DragonFly BSD 套件"
|
||||
"DragonFly BSD Audit":
|
||||
en: "DragonFly BSD Audit"
|
||||
zh_TW: "DragonFly BSD 紀錄"
|
||||
"FreeBSD Update":
|
||||
en: "FreeBSD Update"
|
||||
zh_TW: "FreeBSD 更新"
|
||||
"FreeBSD Packages":
|
||||
en: "FreeBSD Packages"
|
||||
zh_TW: "FreeBSD 套件"
|
||||
"FreeBSD Audit":
|
||||
en: "FreeBSD Audit"
|
||||
zh_TW: "FreeBSD 紀錄"
|
||||
"System update":
|
||||
en: "System update"
|
||||
zh_TW: "系統更新"
|
||||
"needrestart will be ran by the package manager":
|
||||
en: "needrestart will be ran by the package manager"
|
||||
zh_TW: "needrestart 將被套件管理員執行"
|
||||
"Check for needed restarts":
|
||||
en: "Check for needed restarts"
|
||||
zh_TW: "正在檢查是否需要重新啟動系統"
|
||||
"Should not run in WSL":
|
||||
en: "Should not run in WSL"
|
||||
zh_TW: "不該在 WSL 中執行"
|
||||
"Firmware upgrades":
|
||||
en: "Firmware upgrades"
|
||||
zh_TW: "韌體更新"
|
||||
"Flatpak System Packages":
|
||||
en: "Flatpak System Packages"
|
||||
zh_TW: "Flatpak 系統套件"
|
||||
"Snapd socket does not exist":
|
||||
en: "Snapd socket does not exist"
|
||||
zh_TW: "找不到 Snapd 程序"
|
||||
"You need to specify at least one container":
|
||||
en: "You need to specify at least one container"
|
||||
zh_TW: "必須指定至少一個容器"
|
||||
"Skipped in --yes":
|
||||
en: "Skipped in --yes"
|
||||
zh_TW: "指定 --yes,略過"
|
||||
"Configuration update":
|
||||
en: "Configuration update"
|
||||
zh_TW: "設定更新"
|
||||
"Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?":
|
||||
en: "Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?"
|
||||
zh_TW: "將略過 `waydroid upgrade`,並且「停止」執行容器。是否繼續?"
|
||||
"Skip the Waydroid step because the user don't want to proceed":
|
||||
en: "Skip the Waydroid step because the user don't want to proceed"
|
||||
zh_TW: "使用者指定略過 Waydroid 程序"
|
||||
"macOS App Store":
|
||||
en: "macOS App Store"
|
||||
zh_TW: "macOS App Store"
|
||||
"macOS system update":
|
||||
en: "macOS system update"
|
||||
zh_TW: "macOS 系統更新"
|
||||
"OpenBSD Update":
|
||||
en: "OpenBSD Update"
|
||||
zh_TW: "OpenBSD 更新"
|
||||
"OpenBSD Packages":
|
||||
en: "OpenBSD Packages"
|
||||
zh_TW: "OpenBSD 套件"
|
||||
"`fisher` is not defined in `fish`":
|
||||
en: "`fisher` is not defined in `fish`"
|
||||
zh_TW: "`fisher` 未在 `fish` 中指定"
|
||||
"`fish_plugins` path doesn't exist: {err}":
|
||||
en: "`fish_plugins` path doesn't exist: %{err}"
|
||||
zh_TW: "不存在 `fish_plugins` 路徑:%{err}"
|
||||
"`fish_update_completions` is not available":
|
||||
en: "`fish_update_completions` is not available"
|
||||
zh_TW: "無法使用 `fish_update_completions`"
|
||||
"Desktop doest not appear to be gnome":
|
||||
en: "Desktop doest not appear to be gnome"
|
||||
zh_TW: "桌面環境不是 Gnome"
|
||||
"Gnome shell extensions are unregistered in DBus":
|
||||
en: "Gnome shell extensions are unregistered in DBus"
|
||||
zh_TW: "Gnome Shell 擴充功能在 DBus 中未被註冊"
|
||||
"Gnome Shell extensions":
|
||||
en: "Gnome Shell extensions"
|
||||
zh_TW: "Gnome Shell 擴充功能"
|
||||
"Not a custom brew for macOS":
|
||||
en: "Not a custom brew for macOS"
|
||||
zh_TW: "不是專門的 macOS brew"
|
||||
"Guix Pull Failed, Skipping":
|
||||
en: "Guix Pull Failed, Skipping"
|
||||
zh_TW: "Guix 拉取失敗,略過"
|
||||
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch":
|
||||
en: "Nix-darwin on macOS must be upgraded via darwin-rebuild switch"
|
||||
zh_TW: "Nix-darwin 在 macOS 上必須使用 darwin-rebuild 更新"
|
||||
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux":
|
||||
en: "`nix upgrade-nix` can only be used on macOS or non-NixOS Linux"
|
||||
zh_TW: "`nix upgrade-nix` 僅能在 macOS 或非 NixOS 的 Linux 上使用"
|
||||
"`nix upgrade-nix` cannot be run when Nix is installed in a profile":
|
||||
en: "`nix upgrade-nix` cannot be run when Nix is installed in a profile"
|
||||
zh_TW: "`nix upgrade-nix` 無法在已安裝 Nix 使用者環境的系統上使用"
|
||||
"Nix (self-upgrade)":
|
||||
en: "Nix (self-upgrade)"
|
||||
zh_TW: "Nix(自行更新)"
|
||||
"Pyenv is installed, but $PYENV_ROOT is not set correctly":
|
||||
en: "Pyenv is installed, but $PYENV_ROOT is not set correctly"
|
||||
zh_TW: "已安裝 Pyenv 但尚未正確設定 $PYENV_ROOT"
|
||||
"pyenv is not a git repository":
|
||||
en: "pyenv is not a git repository"
|
||||
zh_TW: "pyenv 不是 git 來源"
|
||||
"Bun Packages":
|
||||
en: "Bun Packages"
|
||||
zh_TW: "Bun 套件"
|
||||
"WSL not installed":
|
||||
en: "WSL not installed"
|
||||
zh_TW: "未安裝 WSL"
|
||||
"Update WSL":
|
||||
en: "Update WSL"
|
||||
zh_TW: "更新 WSL"
|
||||
"Could not find Topgrade installed in WSL":
|
||||
en: "Could not find Topgrade installed in WSL"
|
||||
zh_TW: "尚未在 WSL 內安裝 Topgrade"
|
||||
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.":
|
||||
en: "Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
|
||||
zh_TW: "目前不支援使用 USOClient 管理 Windows 更新。建議安裝 PSWindowsUpdate。"
|
||||
"USOClient not supported.":
|
||||
en: "USOClient not supported."
|
||||
zh_TW: "不支援 USOClient。"
|
||||
"Connecting to {hostname}...":
|
||||
en: "Connecting to %{hostname}..."
|
||||
zh_TW: "正在連接 %{hostname}..."
|
||||
"Skipping powered off box {vagrant_box}":
|
||||
en: "Skipping powered off box %{vagrant_box}"
|
||||
zh_TW: "正在略過已關機的容器 %{vagrant_box}"
|
||||
"`{repo_tag}` for `{platform}`":
|
||||
en: "`%{repo_tag}` for `%{platform}`"
|
||||
zh_TW: "`%{repo_tag}` 給 `%{platform}`"
|
||||
"Containers":
|
||||
en: "Containers"
|
||||
zh_TW: "容器"
|
||||
"Emacs directory does not exist":
|
||||
en: "Emacs directory does not exist"
|
||||
zh_TW: "找不到 Emacs 資料夾"
|
||||
"Error getting the composer directory: {error}":
|
||||
en: "Error getting the composer directory: %{error}"
|
||||
zh_TW: "無法取得 composer 資料夾:%{error}"
|
||||
"Composer directory {composer_home} isn't a descendant of the user's home directory":
|
||||
en: "Composer directory %{composer_home} isn't a descendant of the user's home directory"
|
||||
zh_TW: "Composer 資料夾 %{composer_home} 不在家目錄下"
|
||||
"Composer":
|
||||
en: "Composer"
|
||||
zh_TW: "Composer"
|
||||
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.":
|
||||
en: "Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK."
|
||||
zh_TW: "執行 `dotnet tool list` 失敗。已安裝 dotnet 執行環境但未安裝 SDK"
|
||||
"No dotnet global tools installed":
|
||||
en: "No dotnet global tools installed"
|
||||
zh_TW: "尚未安裝全域 dotnet 工具"
|
||||
"Racket Package Manager":
|
||||
en: "Racket Package Manager"
|
||||
zh_TW: "Racket 套件管理員"
|
||||
"GH failed":
|
||||
en: "GH failed"
|
||||
zh_TW: "GH 失敗"
|
||||
"GitHub CLI Extensions":
|
||||
en: "GitHub CLI Extensions"
|
||||
zh_TW: "GitHub CLI 擴充功能"
|
||||
"Julia Packages":
|
||||
en: "Julia Packages"
|
||||
zh_TW: "Julia 套件"
|
||||
"Update ClamAV Database(FreshClam)":
|
||||
en: "Update ClamAV Database(FreshClam)"
|
||||
zh_TW: "更新 ClamAV 資料庫(FreshClam)"
|
||||
"Path {pattern} did not contain any git repositories":
|
||||
en: "Path %{pattern} did not contain any git repositories"
|
||||
zh_TW: "路徑 %{pattern} 中沒有任何 git 來源"
|
||||
"No repositories to pull":
|
||||
en: "No repositories to pull"
|
||||
zh_TW: "沒有來源可以拉取"
|
||||
"Git repositories":
|
||||
en: "Git repositories"
|
||||
zh_TW: "Git 來源"
|
||||
"Would pull {repo}":
|
||||
en: "Would pull %{repo}"
|
||||
zh_TW: "拉取 %{repo}"
|
||||
|
||||
# aka npm
|
||||
"Node Package Manager":
|
||||
en: "Node Package Manager"
|
||||
zh_TW: "Node 套件管理員(npm)"
|
||||
# aka pnpm
|
||||
"Performant Node Package Manager":
|
||||
en: "Performant Node Package Manager"
|
||||
zh_TW: "效能 Node 套件管理員(pnpm)"
|
||||
"Yarn Package Manager":
|
||||
en: "Yarn Package Manager"
|
||||
zh_TW: "Yarn 套件管理員"
|
||||
"Deno installed outside of .deno directory":
|
||||
en: "Deno installed outside of .deno directory"
|
||||
zh_TW: "Deno 安裝在 .deno 資料夾外"
|
||||
"The Ultimate vimrc":
|
||||
en: "The Ultimate vimrc"
|
||||
zh_TW: "終極 vimrc(The Ultimate vimrc)"
|
||||
"vim binary might be actually nvim":
|
||||
en: "vim binary might be actually nvim"
|
||||
zh_TW: "vim 執行檔可能為 nvim"
|
||||
"`{process}` failed: {exit_status}":
|
||||
en: "`%{process}` failed: %{exit_status}"
|
||||
zh_TW: "`%{process}` 失敗:%{exit_status}"
|
||||
"`{process}` failed: {exit_status} with {output}":
|
||||
en: "`%{process}` failed: %{exit_status} with %{output}"
|
||||
zh_TW: "`%{process}` 失敗:%{exit_status} 伴隨 %{output}"
|
||||
"Unknown Linux Distribution":
|
||||
en: "Unknown Linux Distribution"
|
||||
zh_TW: "未知 Linux"
|
||||
'File "/etc/os-release" does not exist or is empty':
|
||||
en: 'File "/etc/os-release" does not exist or is empty'
|
||||
zh_TW: '「/etc/os-release」不存在或為空'
|
||||
"Failed getting the system package manager":
|
||||
en: "Failed getting the system package manager"
|
||||
zh_TW: "偵測系統套件管理員失敗"
|
||||
"A step failed":
|
||||
en: "A step failed"
|
||||
zh_TW: "某步驟執行失敗"
|
||||
"Dry running":
|
||||
en: "Dry running"
|
||||
zh_TW: "模擬執行"
|
||||
"Topgrade Upgraded":
|
||||
en: "Topgrade Upgraded"
|
||||
zh_TW: "已更新 Topgrade"
|
||||
|
||||
# Summary texts
|
||||
"OK":
|
||||
en: "OK"
|
||||
zh_TW: "成功"
|
||||
"FAILED":
|
||||
en: "FAILED"
|
||||
zh_TW: "失敗"
|
||||
"IGNORED":
|
||||
en: "IGNORED"
|
||||
zh_TW: "忽略"
|
||||
"SKIPPED":
|
||||
en: "SKIPPED"
|
||||
zh_TW: "略過"
|
||||
|
||||
# 'Y' and 'N' have to stay the same characters. Eg for German the translation
|
||||
# would look sth like "(Y) Ja / (N) Nein"
|
||||
"(Y)es/(N)o":
|
||||
en: "(Y)es/(N)o"
|
||||
zh_TW: "(Y)是/(N)否"
|
||||
|
||||
# 'y', 'N', 's', 'q' have to stay the same throughout all translations.
|
||||
# Eg German would look like "(y) Wiederholen / (N) Nein / (s) Konsole / (q) Beenden"
|
||||
"Retry? (y)es/(N)o/(s)hell/(q)uit":
|
||||
en: "Retry? (y)es/(N)o/(s)hell/(q)uit"
|
||||
zh_TW: "再試一次? (y)是/(N)否/(s)殼層/(q)退出"
|
||||
# 'R', 'S', 'Q' have to stay the same throughout all translations. Eg German would look like "\n(R) Neustarten\n(S) Konsole\n(Q) Beenden"
|
||||
'\n(R)eboot\n(S)hell\n(Q)uit':
|
||||
en: '\n(R)eboot\n(S)hell\n(Q)uit'
|
||||
zh_TW: '\n(R)重新啟動\n(S)殼層\n(Q)退出'
|
||||
"Require sudo or counterpart but not found, skip":
|
||||
en: "Require sudo or counterpart but not found, skip"
|
||||
zh_TW: "找不到權限管理程式(sudo 等),略過"
|
||||
"sudo as user '{user}'":
|
||||
en: "sudo as user '%{user}'"
|
||||
zh_TW: "sudo 以使用者 '%{user}'"
|
||||
"Updating aqua ...":
|
||||
en: "Updating aqua ..."
|
||||
zh_TW: "正在更新 aqua..."
|
||||
"Updating aqua installed cli tools ...":
|
||||
en: "Updating aqua installed cli tools ..."
|
||||
zh_TW: "正在更新 aqua 安裝的命令行介面工具..."
|
||||
"Updating Volta packages...":
|
||||
en: "Updating Volta packages..."
|
||||
zh_TW: "正在更新 Volta 套件..."
|
||||
"No packages installed with Volta":
|
||||
en: "No packages installed with Volta"
|
||||
zh_TW: "沒有任何 Volta 套件"
|
||||
"pyenv-update plugin is not installed":
|
||||
en: "pyenv-update plugin is not installed"
|
||||
zh_TW: "尚未安裝 pyenv-update 擴充功能"
|
||||
"Respawning...":
|
||||
en: "Respawning..."
|
||||
zh_TW: "正在重新生成..."
|
||||
"Could not find Topgrade in any WSL disribution":
|
||||
en: "Could not find Topgrade in any WSL disribution"
|
||||
zh_TW: "在所有 WSL 中找不到 Topgrade"
|
||||
"Windows Update":
|
||||
en: "Windows Update"
|
||||
zh_TW: "Windows 更新"
|
||||
@@ -1,16 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "topgrade"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "1.76.0"
|
||||
@@ -1,171 +0,0 @@
|
||||
//! Inform the users of the breaking changes introduced in this major release.
|
||||
//!
|
||||
//! Print the breaking changes and possibly a migration guide when:
|
||||
//! 1. The Topgrade being executed is a new major release
|
||||
//! 2. This is the first launch of that major release
|
||||
|
||||
use crate::terminal::print_separator;
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::{read_to_string, OpenOptions},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Version string x.y.z
|
||||
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Version info
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Version {
|
||||
_major: u64,
|
||||
minor: u64,
|
||||
patch: u64,
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
const NOT_SEMVER: &str = "Topgrade version is not semantic";
|
||||
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
|
||||
|
||||
let mut iter = s.split('.').take(3);
|
||||
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
|
||||
// They cannot be all 0s
|
||||
assert!(
|
||||
!(major == 0 && minor == 0 && patch == 0),
|
||||
"Version numbers cannot be all 0s"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
_major: major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// True if this version is a new major release.
|
||||
pub(crate) fn is_new_major_release(&self) -> bool {
|
||||
// We have already checked that they cannot all be zeros, so `self.major`
|
||||
// is guaranteed to be non-zero.
|
||||
self.minor == 0 && self.patch == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Topgrade's breaking changes
|
||||
///
|
||||
/// We store them in the compiled binary.
|
||||
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
|
||||
|
||||
/// Return platform's data directory.
|
||||
fn data_dir() -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
return XDG_DIRS.data_dir();
|
||||
|
||||
#[cfg(windows)]
|
||||
return WINDOWS_DIRS.data_dir();
|
||||
}
|
||||
|
||||
/// Return Topgrade's keep file path.
|
||||
///
|
||||
/// keep file is a file under the data directory containing a major version
|
||||
/// number, it will be created on first run and is used to check if an execution
|
||||
/// of Topgrade is the first run of a major release, for more details, see
|
||||
/// `first_run_of_major_release()`.
|
||||
fn keep_file_path() -> PathBuf {
|
||||
let keep_file = "topgrade_keep";
|
||||
data_dir().join(keep_file)
|
||||
}
|
||||
|
||||
/// If environment variable `TOPGRADE_SKIP_BRKC_NOTIFY` is set to `true`, then
|
||||
/// we won't notify the user of the breaking changes.
|
||||
pub(crate) fn should_skip() -> bool {
|
||||
if let Ok(var) = var("TOPGRADE_SKIP_BRKC_NOTIFY") {
|
||||
return var.as_str() == "true";
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// True if this is the first execution of a major release.
|
||||
pub(crate) fn first_run_of_major_release() -> Result<bool> {
|
||||
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
// disable this lint here as the current code has better readability
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if version.is_new_major_release() {
|
||||
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Print breaking changes to the user.
|
||||
pub(crate) fn print_breaking_changes() {
|
||||
let header = format!(
|
||||
"{}",
|
||||
t!("Topgrade {version_str} Breaking Changes", version_str = VERSION_STR)
|
||||
);
|
||||
print_separator(header);
|
||||
let contents = if BREAKINGCHANGES.is_empty() {
|
||||
t!("No Breaking changes").to_string()
|
||||
} else {
|
||||
BREAKINGCHANGES.to_string()
|
||||
};
|
||||
println!("{contents}\n");
|
||||
}
|
||||
|
||||
/// This function will be ONLY executed when the user has confirmed the breaking
|
||||
/// changes, once confirmed, we write the keep file, which means the first run
|
||||
/// of this major release is finished.
|
||||
pub(crate) fn write_keep_file() -> Result<()> {
|
||||
std::fs::create_dir_all(data_dir())?;
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(keep_file)?;
|
||||
let _ = file.write(VERSION_STR.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_new_major_release_works() {
|
||||
let first_major_release: Version = "1.0.0".parse().unwrap();
|
||||
let under_dev: Version = "0.1.0".parse().unwrap();
|
||||
|
||||
assert!(first_major_release.is_new_major_release());
|
||||
assert!(!under_dev.is_new_major_release());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Version numbers cannot be all 0s")]
|
||||
fn invalid_version() {
|
||||
let all_0 = "0.0.0";
|
||||
all_0.parse::<Version>().unwrap();
|
||||
}
|
||||
}
|
||||
246
src/command.rs
246
src/command.rs
@@ -1,246 +0,0 @@
|
||||
//! Utilities for running commands and providing user-friendly error messages.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::process::Child;
|
||||
use std::process::{Command, ExitStatus, Output};
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
|
||||
use crate::error::TopgradeError;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
/// Like [`Output`], but UTF-8 decoded.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Utf8Output {
|
||||
pub status: ExitStatus,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
impl TryFrom<Output> for Utf8Output {
|
||||
type Error = eyre::Error;
|
||||
|
||||
fn try_from(Output { status, stdout, stderr }: Output) -> Result<Self, Self::Error> {
|
||||
let stdout = String::from_utf8(stdout).map_err(|err| {
|
||||
eyre!(
|
||||
"Stdout contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let stderr = String::from_utf8(stderr).map_err(|err| {
|
||||
eyre!(
|
||||
"Stderr contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Utf8Output { status, stdout, stderr })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Output> for Utf8Output {
|
||||
type Error = eyre::Error;
|
||||
|
||||
fn try_from(Output { status, stdout, stderr }: &Output) -> Result<Self, Self::Error> {
|
||||
let stdout = String::from_utf8(stdout.to_vec()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stdout contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let stderr = String::from_utf8(stderr.to_vec()).map_err(|err| {
|
||||
eyre!(
|
||||
"Stderr contained invalid UTF-8: {}",
|
||||
String::from_utf8_lossy(err.as_bytes())
|
||||
)
|
||||
})?;
|
||||
let status = *status;
|
||||
|
||||
Ok(Utf8Output { status, stdout, stderr })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Utf8Output {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.stdout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [`Command`], adding helpers to gather output while checking the exit
|
||||
/// status.
|
||||
///
|
||||
/// These also give us significantly better error messages, which include:
|
||||
///
|
||||
/// 1. The command and arguments that were executed, escaped with familiar `sh` syntax.
|
||||
/// 2. The exit status of the command or the signal that killed it.
|
||||
/// 3. If we were capturing the output of the command, rather than forwarding it to the user's
|
||||
/// stdout/stderr, the error message includes the command's stdout and stderr output.
|
||||
///
|
||||
/// Additionally, executing commands with these methods will log the command at debug-level,
|
||||
/// useful when gathering error reports.
|
||||
pub trait CommandExt {
|
||||
type Child;
|
||||
|
||||
/// Like [`Command::output`], but checks the exit status and provides nice error messages.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute or returned a non-zero exit code.
|
||||
#[track_caller]
|
||||
fn output_checked(&mut self) -> eyre::Result<Output> {
|
||||
self.output_checked_with(|output: &Output| if output.status.success() { Ok(()) } else { Err(()) })
|
||||
}
|
||||
|
||||
/// Like [`output_checked`], but also decodes Stdout and Stderr as UTF-8.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute, returned a non-zero exit code, or if the
|
||||
/// output contains invalid UTF-8.
|
||||
#[track_caller]
|
||||
fn output_checked_utf8(&mut self) -> eyre::Result<Utf8Output> {
|
||||
let output = self.output_checked()?;
|
||||
output.try_into()
|
||||
}
|
||||
|
||||
/// Like [`output_checked`] but a closure determines if the command failed instead of
|
||||
/// [`ExitStatus::success`].
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute or if `succeeded` returns an `Err`.
|
||||
/// (This lets the caller substitute their own notion of "success" instead of assuming
|
||||
/// non-zero exit codes indicate success.)
|
||||
#[track_caller]
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> eyre::Result<Output>;
|
||||
|
||||
/// Like [`output_checked_with`], but also decodes Stdout and Stderr as UTF-8.
|
||||
///
|
||||
/// Returns an `Err` if the command failed to execute, if `succeeded` returns an `Err`, or if
|
||||
/// the output contains invalid UTF-8.
|
||||
#[track_caller]
|
||||
fn output_checked_with_utf8(
|
||||
&mut self,
|
||||
succeeded: impl Fn(&Utf8Output) -> Result<(), ()>,
|
||||
) -> eyre::Result<Utf8Output> {
|
||||
// This decodes the Stdout and Stderr as UTF-8 twice...
|
||||
let output =
|
||||
self.output_checked_with(|output| output.try_into().map_err(|_| ()).and_then(|o| succeeded(&o)))?;
|
||||
output.try_into()
|
||||
}
|
||||
|
||||
/// Like [`Command::status`], but gives a nice error message if the status is unsuccessful
|
||||
/// rather than returning the [`ExitStatus`].
|
||||
///
|
||||
/// Returns `Ok` if the command executes successfully, returns `Err` if the command fails to
|
||||
/// execute or returns a non-zero exit code.
|
||||
#[track_caller]
|
||||
fn status_checked(&mut self) -> eyre::Result<()> {
|
||||
self.status_checked_with(|status| if status.success() { Ok(()) } else { Err(()) })
|
||||
}
|
||||
|
||||
/// Like [`status_checked`], but gives a nice error message if the status is unsuccessful
|
||||
/// rather than returning the [`ExitStatus`].
|
||||
///
|
||||
/// Returns `Ok` if the command executes successfully, returns `Err` if the command fails to
|
||||
/// execute or if `succeeded` returns an `Err`.
|
||||
/// (This lets the caller substitute their own notion of "success" instead of assuming
|
||||
/// non-zero exit codes indicate success.)
|
||||
#[track_caller]
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> eyre::Result<()>;
|
||||
|
||||
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
|
||||
/// execute.
|
||||
#[track_caller]
|
||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
type Child = Child;
|
||||
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> eyre::Result<Output> {
|
||||
let command = log(self);
|
||||
|
||||
// This is where we implement `output_checked`, which is what we prefer to use instead of
|
||||
// `output`, so we allow `Command::output` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let output = self
|
||||
.output()
|
||||
.with_context(|| format!("Failed to execute `{command}`"))?;
|
||||
|
||||
if succeeded(&output).is_ok() {
|
||||
Ok(output)
|
||||
} else {
|
||||
let mut message = format!("Command failed: `{command}`");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let stdout_trimmed = stdout.trim();
|
||||
if !stdout_trimmed.is_empty() {
|
||||
message.push_str(&format!("\n\nStdout:\n{stdout_trimmed}"));
|
||||
}
|
||||
let stderr_trimmed = stderr.trim();
|
||||
if !stderr_trimmed.is_empty() {
|
||||
message.push_str(&format!("\n\nStderr:\n{stderr_trimmed}"));
|
||||
}
|
||||
|
||||
let (program, _) = get_program_and_args(self);
|
||||
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
|
||||
|
||||
let ret = Err(err).with_context(|| message);
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> eyre::Result<()> {
|
||||
let command = log(self);
|
||||
let message = format!("Failed to execute `{command}`");
|
||||
|
||||
// This is where we implement `status_checked`, which is what we prefer to use instead of
|
||||
// `status`, so we allow `Command::status` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = self.status().with_context(|| message.clone())?;
|
||||
|
||||
if succeeded(status).is_ok() {
|
||||
Ok(())
|
||||
} else {
|
||||
let (program, _) = get_program_and_args(self);
|
||||
let err = TopgradeError::ProcessFailed(program, status);
|
||||
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child> {
|
||||
let command = log(self);
|
||||
let message = format!("Failed to execute `{command}`");
|
||||
|
||||
// This is where we implement `spawn_checked`, which is what we prefer to use instead of
|
||||
// `spawn`, so we allow `Command::spawn` here.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
{
|
||||
self.spawn().with_context(|| message.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_program_and_args(cmd: &Command) -> (String, String) {
|
||||
// We're not doing anything weird with commands that are invalid UTF-8 so this is fine.
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let args = shell_words::join(cmd.get_args().map(|arg| arg.to_string_lossy()));
|
||||
(program, args)
|
||||
}
|
||||
|
||||
fn format_program_and_args(cmd: &Command) -> String {
|
||||
let (program, args) = get_program_and_args(cmd);
|
||||
if args.is_empty() {
|
||||
program
|
||||
} else {
|
||||
format!("{program} {args}")
|
||||
}
|
||||
}
|
||||
|
||||
fn log(cmd: &Command) -> String {
|
||||
let command = format_program_and_args(cmd);
|
||||
debug!("Executing command `{command}`");
|
||||
command
|
||||
}
|
||||
1117
src/config.rs
1117
src/config.rs
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! SIGINT handling in Unix systems.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
|
||||
use nix::sys::signal;
|
||||
|
||||
/// Handle SIGINT. Set the interruption flag.
|
||||
extern "C" fn handle_sigint(_: i32) {
|
||||
@@ -10,8 +10,12 @@ extern "C" fn handle_sigint(_: i32) {
|
||||
/// Set the necessary signal handlers.
|
||||
/// The function panics on failure.
|
||||
pub fn set_handler() {
|
||||
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
|
||||
let sig_action = signal::SigAction::new(
|
||||
signal::SigHandler::Handler(handle_sigint),
|
||||
signal::SaFlags::empty(),
|
||||
signal::SigSet::empty(),
|
||||
);
|
||||
unsafe {
|
||||
sigaction(Signal::SIGINT, &sig_action).unwrap();
|
||||
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! A stub for Ctrl + C handling.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use tracing::error;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
||||
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
||||
use winapi::um::wincon::CTRL_C_EVENT;
|
||||
@@ -17,6 +16,6 @@ extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
|
||||
|
||||
pub fn set_handler() {
|
||||
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
||||
error!("Cannot set a control C handler")
|
||||
log::error!("Cannot set a control C handler")
|
||||
}
|
||||
}
|
||||
|
||||
87
src/error.rs
87
src/error.rs
@@ -1,98 +1,41 @@
|
||||
use std::{fmt::Display, process::ExitStatus};
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use rust_i18n::t;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum TopgradeError {
|
||||
ProcessFailed(String, ExitStatus),
|
||||
#[error("{0}")]
|
||||
ProcessFailed(ExitStatus),
|
||||
|
||||
ProcessFailedWithOutput(String, ExitStatus, String),
|
||||
#[error("{0}: {1}")]
|
||||
ProcessFailedWithOutput(ExitStatus, String),
|
||||
|
||||
#[error("Sudo is required for this step")]
|
||||
#[allow(dead_code)]
|
||||
SudoRequired,
|
||||
|
||||
#[error("Unknown Linux Distribution")]
|
||||
#[cfg(target_os = "linux")]
|
||||
UnknownLinuxDistribution,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
EmptyOSReleaseFile,
|
||||
|
||||
#[error("Failed getting the system package manager")]
|
||||
#[cfg(target_os = "linux")]
|
||||
FailedGettingPackageManager,
|
||||
}
|
||||
|
||||
impl Display for TopgradeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TopgradeError::ProcessFailed(process, exit_status) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_status}",
|
||||
process = process,
|
||||
exit_status = exit_status
|
||||
)
|
||||
)
|
||||
}
|
||||
TopgradeError::ProcessFailedWithOutput(process, exit_status, output) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{process}` failed: {exit_status} with {output}",
|
||||
process = process,
|
||||
exit_status = exit_status,
|
||||
output = output
|
||||
)
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::UnknownLinuxDistribution => write!(f, "{}", t!("Unknown Linux Distribution")),
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::EmptyOSReleaseFile => {
|
||||
write!(f, "{}", t!("File \"/etc/os-release\" does not exist or is empty"))
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
TopgradeError::FailedGettingPackageManager => {
|
||||
write!(f, "{}", t!("Failed getting the system package manager"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("A step failed")]
|
||||
pub struct StepFailed;
|
||||
|
||||
impl Display for StepFailed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("A step failed"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Dry running")]
|
||||
pub struct DryRun();
|
||||
|
||||
impl Display for DryRun {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("Dry running"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0}")]
|
||||
pub struct SkipStep(pub String);
|
||||
|
||||
impl Display for SkipStep {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Topgrade Upgraded")]
|
||||
pub struct Upgraded(pub ExitStatus);
|
||||
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
impl Display for Upgraded {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", t!("Topgrade Upgraded"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::executor::RunType;
|
||||
use crate::sudo::Sudo;
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use crate::git::Git;
|
||||
use crate::utils::require_option;
|
||||
use crate::{config::Config, executor::Executor};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::env::var;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct ExecutionContext<'a> {
|
||||
run_type: RunType,
|
||||
sudo: Option<Sudo>,
|
||||
sudo: &'a Option<PathBuf>,
|
||||
git: &'a Git,
|
||||
config: &'a Config,
|
||||
/// Name of a tmux session to execute commands in, if any.
|
||||
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
|
||||
/// tmux window for each remote.
|
||||
tmux_session: Mutex<Option<String>>,
|
||||
/// True if topgrade is running under ssh.
|
||||
under_ssh: bool,
|
||||
base_dirs: &'a BaseDirs,
|
||||
}
|
||||
|
||||
impl<'a> ExecutionContext<'a> {
|
||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, config: &'a Config) -> Self {
|
||||
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
||||
Self {
|
||||
pub fn new(
|
||||
run_type: RunType,
|
||||
sudo: &'a Option<PathBuf>,
|
||||
git: &'a Git,
|
||||
config: &'a Config,
|
||||
base_dirs: &'a BaseDirs,
|
||||
) -> ExecutionContext<'a> {
|
||||
ExecutionContext {
|
||||
run_type,
|
||||
sudo,
|
||||
git,
|
||||
config,
|
||||
tmux_session: Mutex::new(None),
|
||||
under_ssh,
|
||||
base_dirs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
|
||||
let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
|
||||
Ok(sudo.execute_elevated(self, command, interactive))
|
||||
let sudo = require_option(self.sudo.clone(), "Sudo is required for this operation".into())?;
|
||||
let mut cmd = self.run_type.execute(&sudo);
|
||||
|
||||
if sudo.ends_with("sudo") {
|
||||
cmd.arg("--preserve-env=DIFFPROG");
|
||||
}
|
||||
|
||||
if interactive {
|
||||
cmd.arg("-i");
|
||||
}
|
||||
|
||||
cmd.arg(command);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
pub fn run_type(&self) -> RunType {
|
||||
self.run_type
|
||||
}
|
||||
|
||||
pub fn sudo(&self) -> &Option<Sudo> {
|
||||
&self.sudo
|
||||
pub fn git(&self) -> &Git {
|
||||
self.git
|
||||
}
|
||||
|
||||
pub fn sudo(&self) -> &Option<PathBuf> {
|
||||
self.sudo
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Config {
|
||||
self.config
|
||||
}
|
||||
|
||||
pub fn under_ssh(&self) -> bool {
|
||||
self.under_ssh
|
||||
}
|
||||
|
||||
pub fn set_tmux_session(&self, session_name: String) {
|
||||
self.tmux_session.lock().unwrap().replace(session_name);
|
||||
}
|
||||
|
||||
pub fn get_tmux_session(&self) -> Option<String> {
|
||||
self.tmux_session.lock().unwrap().clone()
|
||||
pub fn base_dirs(&self) -> &BaseDirs {
|
||||
self.base_dirs
|
||||
}
|
||||
}
|
||||
|
||||
174
src/executor.rs
174
src/executor.rs
@@ -1,14 +1,11 @@
|
||||
//! Utilities for command execution
|
||||
use crate::error::{DryRun, TopgradeError};
|
||||
use crate::utils::{Check, CheckWithCodes};
|
||||
use anyhow::Result;
|
||||
use log::{debug, trace};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, ExitStatus, Output};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::DryRun;
|
||||
use std::process::{Child, Command, ExitStatus};
|
||||
|
||||
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -59,16 +56,6 @@ pub enum Executor {
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Get the name of the program being run.
|
||||
///
|
||||
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
||||
pub fn get_program(&self) -> String {
|
||||
match self {
|
||||
Executor::Wet(c) => c.get_program().to_string_lossy().into_owned(),
|
||||
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// See `std::process::Command::arg`
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
||||
match self {
|
||||
@@ -152,7 +139,7 @@ impl Executor {
|
||||
let result = match self {
|
||||
Executor::Wet(c) => {
|
||||
debug!("Running {:?}", c);
|
||||
c.spawn_checked().map(ExecutorChild::Wet)?
|
||||
c.spawn().map(ExecutorChild::Wet)?
|
||||
}
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
@@ -166,7 +153,7 @@ impl Executor {
|
||||
/// See `std::process::Command::output`
|
||||
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
||||
match self {
|
||||
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output_checked()?)),
|
||||
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output()?)),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(ExecutorOutput::Dry)
|
||||
@@ -174,28 +161,23 @@ impl Executor {
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension of `status_checked` that allows you to set a sequence of codes
|
||||
/// A convinence method for `spawn().wait().check()`.
|
||||
/// Returns an error if something went wrong during the execution or if the
|
||||
/// process exited with failure.
|
||||
pub fn check_run(&mut self) -> Result<()> {
|
||||
self.spawn()?.wait()?.check()
|
||||
}
|
||||
|
||||
/// An extension of `check_run` that allows you to set a sequence of codes
|
||||
/// that can indicate success of a script
|
||||
#[allow(dead_code)]
|
||||
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
match self {
|
||||
Executor::Wet(c) => c.status_checked_with(|status| {
|
||||
if status.success() || status.code().as_ref().map(|c| codes.contains(c)).unwrap_or(false) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub fn check_run_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||
self.spawn()?.wait()?.check_with_codes(codes)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ExecutorOutput {
|
||||
Wet(Output),
|
||||
Wet(std::process::Output),
|
||||
Dry,
|
||||
}
|
||||
|
||||
@@ -210,20 +192,16 @@ pub struct DryCommand {
|
||||
impl DryCommand {
|
||||
fn dry_run(&self) {
|
||||
print!(
|
||||
"{}",
|
||||
t!(
|
||||
"Dry running: {program_name} {arguments}",
|
||||
program_name = self.program.to_string_lossy(),
|
||||
arguments = shell_words::join(
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
)
|
||||
"Dry running: {} {}",
|
||||
self.program.to_string_lossy(),
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
);
|
||||
match &self.directory {
|
||||
Some(dir) => println!(" {}", t!("in {directory}", directory = dir.to_string_lossy())),
|
||||
Some(dir) => println!(" in {}", dir.to_string_lossy()),
|
||||
None => println!(),
|
||||
};
|
||||
}
|
||||
@@ -231,38 +209,82 @@ impl DryCommand {
|
||||
|
||||
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
||||
pub enum ExecutorChild {
|
||||
#[allow(unused)] // this type has not been used
|
||||
Wet(Child),
|
||||
Dry,
|
||||
}
|
||||
|
||||
impl CommandExt for Executor {
|
||||
type Child = ExecutorChild;
|
||||
impl ExecutorChild {
|
||||
/// See `std::process::Child::wait`
|
||||
pub fn wait(&mut self) -> Result<ExecutorExitStatus> {
|
||||
let result = match self {
|
||||
ExecutorChild::Wet(c) => c.wait().map(ExecutorExitStatus::Wet)?,
|
||||
ExecutorChild::Dry => ExecutorExitStatus::Dry,
|
||||
};
|
||||
|
||||
// TODO: It might be nice to make `output_checked_with` return something that has a
|
||||
// variant for wet/dry runs.
|
||||
|
||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
|
||||
match self {
|
||||
Executor::Wet(c) => c.output_checked_with(succeeded),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Err(DryRun().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
|
||||
match self {
|
||||
Executor::Wet(c) => c.status_checked_with(succeeded),
|
||||
Executor::Dry(c) => {
|
||||
c.dry_run();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_checked(&mut self) -> Result<Self::Child> {
|
||||
self.spawn()
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Result of wait. Contains an actual `std::process::ExitStatus` if executed by a wet command.
|
||||
pub enum ExecutorExitStatus {
|
||||
Wet(ExitStatus),
|
||||
Dry,
|
||||
}
|
||||
|
||||
impl CheckWithCodes for ExecutorExitStatus {
|
||||
fn check_with_codes(self, codes: &[i32]) -> Result<()> {
|
||||
match self {
|
||||
ExecutorExitStatus::Wet(e) => e.check_with_codes(codes),
|
||||
ExecutorExitStatus::Dry => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for `std::process::Command`
|
||||
pub trait CommandExt {
|
||||
/// Run the command, wait for it to complete, check the return code and decode the output as UTF-8.
|
||||
fn check_output(&mut self) -> Result<String>;
|
||||
fn string_output(&mut self) -> Result<String>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn check_output(&mut self) -> Result<String> {
|
||||
let output = self.output()?;
|
||||
trace!("Output of {:?}: {:?}", self, output);
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
|
||||
return Err(TopgradeError::ProcessFailedWithOutput(status, stderr).into());
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
|
||||
fn string_output(&mut self) -> Result<String> {
|
||||
let output = self.output()?;
|
||||
trace!("Output of {:?}: {:?}", self, output);
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandExt for Executor {
|
||||
fn check_output(&mut self) -> Result<String> {
|
||||
let output = match self.output()? {
|
||||
ExecutorOutput::Wet(output) => output,
|
||||
ExecutorOutput::Dry => return Err(DryRun().into()),
|
||||
};
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
|
||||
return Err(TopgradeError::ProcessFailedWithOutput(status, stderr).into());
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
|
||||
fn string_output(&mut self) -> Result<String> {
|
||||
let output = match self.output()? {
|
||||
ExecutorOutput::Wet(output) => output,
|
||||
ExecutorOutput::Dry => return Err(DryRun().into()),
|
||||
};
|
||||
Ok(String::from_utf8(output.stdout)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
//pub mod steps;
|
||||
//pub mod utils;
|
||||
524
src/main.rs
524
src/main.rs
@@ -2,24 +2,14 @@
|
||||
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
|
||||
use clap::CommandFactory;
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{crate_version, Parser};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use console::Key;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
#[cfg(windows)]
|
||||
use etcetera::base_strategy::Windows;
|
||||
#[cfg(unix)]
|
||||
use etcetera::base_strategy::Xdg;
|
||||
use once_cell::sync::Lazy;
|
||||
use rust_i18n::{i18n, t};
|
||||
use tracing::debug;
|
||||
use log::debug;
|
||||
use log::LevelFilter;
|
||||
use pretty_env_logger::formatted_timed_builder;
|
||||
|
||||
use self::config::{CommandLineArgs, Config, Step};
|
||||
use self::error::StepFailed;
|
||||
@@ -28,10 +18,6 @@ use self::error::Upgraded;
|
||||
use self::steps::{remote::*, *};
|
||||
use self::terminal::*;
|
||||
|
||||
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
|
||||
|
||||
mod breaking_changes;
|
||||
mod command;
|
||||
mod config;
|
||||
mod ctrlc;
|
||||
mod error;
|
||||
@@ -44,54 +30,15 @@ mod self_renamer;
|
||||
#[cfg(feature = "self-update")]
|
||||
mod self_update;
|
||||
mod steps;
|
||||
mod sudo;
|
||||
mod terminal;
|
||||
mod utils;
|
||||
|
||||
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
||||
#[cfg(unix)]
|
||||
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
||||
|
||||
// Init and load the i18n files
|
||||
i18n!("locales", fallback = "en");
|
||||
|
||||
fn run() -> Result<()> {
|
||||
install_color_eyre()?;
|
||||
ctrlc::set_handler();
|
||||
|
||||
let base_dirs = directories::BaseDirs::new().ok_or_else(|| anyhow!("No base directories"))?;
|
||||
|
||||
let opt = CommandLineArgs::parse();
|
||||
// Set up the logger with the filter directives from:
|
||||
// 1. CLI option `--log-filter`
|
||||
// 2. `debug` if the `--verbose` option is present
|
||||
// We do this because we need our logger to work while loading the
|
||||
// configuration file.
|
||||
//
|
||||
// When the configuration file is loaded, update the logger with the full
|
||||
// filter directives.
|
||||
//
|
||||
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
|
||||
// and `Config::tracing_filter_directives()`.
|
||||
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
|
||||
|
||||
// Get current system locale and set it as the default locale
|
||||
let system_locale = sys_locale::get_locale().unwrap_or("en".to_string());
|
||||
rust_i18n::set_locale(&system_locale);
|
||||
debug!("Current system locale is {system_locale}");
|
||||
|
||||
if let Some(shell) = opt.gen_completion {
|
||||
let cmd = &mut CommandLineArgs::command();
|
||||
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if opt.gen_manpage {
|
||||
let man = clap_mangen::Man::new(CommandLineArgs::command());
|
||||
man.render(&mut io::stdout())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for env in opt.env_variables() {
|
||||
let mut splitted = env.split('=');
|
||||
@@ -100,74 +47,66 @@ fn run() -> Result<()> {
|
||||
env::set_var(var, value);
|
||||
}
|
||||
|
||||
let mut builder = formatted_timed_builder();
|
||||
|
||||
if opt.verbose {
|
||||
builder.filter(Some("topgrade"), LevelFilter::Trace);
|
||||
}
|
||||
|
||||
builder.init();
|
||||
|
||||
if opt.edit_config() {
|
||||
Config::edit()?;
|
||||
Config::edit(&base_dirs)?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if opt.show_config_reference() {
|
||||
print!("{}", config::EXAMPLE_CONFIG);
|
||||
print!("{}", crate::config::EXAMPLE_CONFIG);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config = Config::load(opt)?;
|
||||
// Update the logger with the full filter directives.
|
||||
update_tracing(&reload_handle, &config.tracing_filter_directives())?;
|
||||
set_title(config.set_title());
|
||||
display_time(config.display_time());
|
||||
set_desktop_notifications(config.notify_each_step());
|
||||
let config = Config::load(&base_dirs, opt)?;
|
||||
terminal::set_title(config.set_title());
|
||||
terminal::display_time(config.display_time());
|
||||
terminal::set_desktop_notifications(config.notify_each_step());
|
||||
|
||||
debug!("Version: {}", crate_version!());
|
||||
debug!("OS: {}", env!("TARGET"));
|
||||
debug!("{:?}", std::env::args());
|
||||
debug!("Binary path: {:?}", std::env::current_exe());
|
||||
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("Configuration: {:?}", config);
|
||||
debug!("Self Update: {:?}", cfg!(feature = "self-update"));
|
||||
|
||||
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tmux::run_in_tmux(config.tmux_config()?)?;
|
||||
return Ok(());
|
||||
tmux::run_in_tmux(config.tmux_arguments());
|
||||
}
|
||||
}
|
||||
|
||||
let powershell = powershell::Powershell::new();
|
||||
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
|
||||
let emacs = emacs::Emacs::new();
|
||||
#[cfg(target_os = "linux")]
|
||||
let distribution = linux::Distribution::detect();
|
||||
let git = git::Git::new();
|
||||
let mut git_repos = git::Repositories::new(&git);
|
||||
|
||||
let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
|
||||
let sudo = utils::sudo();
|
||||
let run_type = executor::RunType::new(config.dry_run());
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &config);
|
||||
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, &sudo, &git, &config, &base_dirs);
|
||||
|
||||
let mut runner = runner::Runner::new(&ctx);
|
||||
|
||||
// If
|
||||
//
|
||||
// 1. the breaking changes notification shouldnot be skipped
|
||||
// 2. this is the first execution of a major release
|
||||
//
|
||||
// inform user of breaking changes
|
||||
if !should_skip() && first_run_of_major_release()? {
|
||||
print_breaking_changes();
|
||||
|
||||
if prompt_yesno("Confirmed?")? {
|
||||
write_keep_file()?;
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
||||
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() {
|
||||
let result = self_update::self_update();
|
||||
|
||||
if should_self_update {
|
||||
runner.execute(Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
|
||||
if let Err(e) = &result {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if e.downcast_ref::<Upgraded>().is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
print_warning(format!("Self update error: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,67 +123,45 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if config.pre_sudo() {
|
||||
if let Some(sudo) = ctx.sudo() {
|
||||
sudo.elevate(&ctx)?;
|
||||
}
|
||||
}
|
||||
let powershell = powershell::Powershell::new();
|
||||
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
|
||||
|
||||
#[cfg(windows)]
|
||||
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
|
||||
|
||||
if let Some(topgrades) = config.remote_topgrades() {
|
||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(hostname(), t)) {
|
||||
runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
|
||||
ssh::ssh_step(&ctx, remote_topgrade)
|
||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
|
||||
runner.execute(Step::Remotes, format!("Remote ({})", remote_topgrade), || {
|
||||
remote::ssh::ssh_step(&ctx, remote_topgrade)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
|
||||
runner.execute(Step::WslUpdate, "WSL", || windows::update_wsl(&ctx))?;
|
||||
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
|
||||
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(&ctx))?;
|
||||
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
|
||||
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// NOTE: Due to breaking `nu` updates, `packer.nu` needs to be updated before `nu` get updated
|
||||
// by other package managers.
|
||||
runner.execute(Step::Shell, "packer.nu", || linux::run_packer_nu(&ctx))?;
|
||||
let distribution = linux::Distribution::detect();
|
||||
|
||||
#[cfg(target_os = r#"linux"#)]
|
||||
{
|
||||
match &distribution {
|
||||
Ok(distribution) => {
|
||||
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", t!("Error detecting current distribution: {error}", error = e));
|
||||
println!("Error detecting current distribution: {}", e);
|
||||
}
|
||||
}
|
||||
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
||||
|
||||
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
|
||||
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
|
||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
||||
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
|
||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
||||
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
|
||||
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
|
||||
runner.execute(Step::DkpPacman, "dkp-pacman", || linux::run_dkp_pacman_update(&ctx))?;
|
||||
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
|
||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
||||
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
|
||||
|
||||
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
||||
runner.execute(Step::BrewFormula, "Brew", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
||||
runner.execute(Step::Waydroid, "Waydroid", || linux::run_waydroid(&ctx))?;
|
||||
runner.execute(Step::AutoCpufreq, "auto-cpufreq", || linux::run_auto_cpufreq(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
|
||||
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(config.cleanup(), run_type))?;
|
||||
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -268,76 +185,122 @@ fn run() -> Result<()> {
|
||||
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
|
||||
runner.execute(Step::Xcodes, "Xcodes", || macos::update_xcodes(&ctx))?;
|
||||
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
|
||||
runner.execute(Step::Mas, "App Store", || macos::run_mas(&ctx))?;
|
||||
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
{
|
||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
||||
dragonfly::upgrade_packages(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Audit, "DragonFly Audit", || dragonfly::audit_packages(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
|
||||
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
|
||||
runner.execute(Step::Audit, "FreeBSD Audit", || freebsd::audit_packages(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
{
|
||||
runner.execute(Step::Pkg, "OpenBSD Packages", || openbsd::upgrade_packages(&ctx))?;
|
||||
runner.execute(Step::System, "OpenBSD Upgrade", || openbsd::upgrade_openbsd(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
||||
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
||||
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
|
||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
|
||||
runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?;
|
||||
|
||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(run_type))?;
|
||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(run_type))?;
|
||||
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
|
||||
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
|
||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
|
||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
|
||||
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
|
||||
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&ctx))?;
|
||||
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&ctx))?;
|
||||
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&ctx))?;
|
||||
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&ctx))?;
|
||||
runner.execute(Step::Shell, "zi", || zsh::run_zi(&ctx))?;
|
||||
runner.execute(Step::Shell, "zim", || zsh::run_zim(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
||||
dragonfly::upgrade_packages(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(Step::Pkg, "FreeBSD Packages", || {
|
||||
freebsd::upgrade_packages(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
|
||||
|
||||
let emacs = emacs::Emacs::new(&base_dirs);
|
||||
if config.use_predefined_git_repos() {
|
||||
if config.should_run(Step::Emacs) {
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
git_repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".vim"));
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/nvim"));
|
||||
}
|
||||
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".ideavimrc"));
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".intellimacs"));
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
git_repos.insert_if_repo(zsh::zshrc(&base_dirs));
|
||||
if config.should_run(Step::Tmux) {
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".tmux"));
|
||||
}
|
||||
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/fish"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("openbox"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("bspwm"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("i3"));
|
||||
git_repos.insert_if_repo(base_dirs.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
git_repos.insert_if_repo(
|
||||
base_dirs
|
||||
.data_local_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
windows::insert_startup_scripts(&ctx, &mut git_repos).ok();
|
||||
|
||||
if let Some(profile) = powershell.profile() {
|
||||
git_repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if config.should_run(Step::GitRepos) {
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
git_repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
runner.execute(Step::GitRepos, "Git repositories", || {
|
||||
git.multi_pull_step(&git_repos, &ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
if should_run_powershell {
|
||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||
powershell.update_modules(&ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(run_type))?;
|
||||
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zi", || zsh::run_zi(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "zim", || zsh::run_zim(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
|
||||
runner.execute(Step::Shell, "oh-my-bash", || unix::run_oh_my_bash(&ctx))?;
|
||||
runner.execute(Step::Shell, "fisher", || unix::run_fisher(&ctx))?;
|
||||
runner.execute(Step::Shell, "fisher", || unix::run_fisher(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&ctx))?;
|
||||
runner.execute(Step::Shell, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
|
||||
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
|
||||
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
|
||||
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&ctx))?;
|
||||
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(&ctx))?;
|
||||
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(&ctx))?;
|
||||
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(run_type))?;
|
||||
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(run_type))?;
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android")))]
|
||||
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
|
||||
unix::upgrade_gnome_extensions(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Pyenv, "pyenv", || unix::run_pyenv(&ctx))?;
|
||||
runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(&ctx))?;
|
||||
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
|
||||
runner.execute(Step::Maza, "maza", || unix::run_maza(&ctx))?;
|
||||
runner.execute(Step::Sdkman, "SDKMAN!", || {
|
||||
unix::run_sdkman(&base_dirs, config.cleanup(), run_type)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
@@ -346,97 +309,65 @@ fn run() -> Result<()> {
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
runner.execute(Step::Atom, "apm", || generic::run_apm(&ctx))?;
|
||||
}
|
||||
|
||||
// The following update function should be executed on all OSes.
|
||||
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(&ctx))?;
|
||||
runner.execute(Step::Elan, "elan", || generic::run_elan(&ctx))?;
|
||||
runner.execute(Step::Rye, "rye", || generic::run_rye(&ctx))?;
|
||||
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
|
||||
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&ctx))?;
|
||||
runner.execute(Step::Atom, "apm", || generic::run_apm(run_type))?;
|
||||
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(run_type))?;
|
||||
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
|
||||
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
|
||||
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
|
||||
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(&ctx))?;
|
||||
runner.execute(Step::Go, "go-global-update", || go::run_go_global_update(&ctx))?;
|
||||
runner.execute(Step::Go, "gup", || go::run_go_gup(&ctx))?;
|
||||
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(run_type))?;
|
||||
runner.execute(Step::Go, "Go", || generic::run_go(run_type))?;
|
||||
runner.execute(Step::Emacs, "Emacs", || emacs.upgrade(&ctx))?;
|
||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
||||
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
|
||||
generic::run_vscode_extensions_update(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(run_type))?;
|
||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(run_type))?;
|
||||
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
|
||||
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
|
||||
runner.execute(Step::Pixi, "pixi", || generic::run_pixi_update(&ctx))?;
|
||||
runner.execute(Step::Miktex, "miktex", || generic::run_miktex_packages_update(&ctx))?;
|
||||
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(&ctx))?;
|
||||
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
|
||||
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
|
||||
generic::run_pip_review_local_update(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
|
||||
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(&ctx))?;
|
||||
runner.execute(Step::Stack, "stack", || generic::run_stack_update(&ctx))?;
|
||||
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(run_type))?;
|
||||
runner.execute(Step::Stack, "stack", || generic::run_stack_update(run_type))?;
|
||||
runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
|
||||
runner.execute(Step::Myrepos, "myrepos", || generic::run_myrepos_update(&ctx))?;
|
||||
runner.execute(Step::Chezmoi, "chezmoi", || generic::run_chezmoi_update(&ctx))?;
|
||||
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(&ctx))?;
|
||||
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&ctx))?;
|
||||
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&ctx))?;
|
||||
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
|
||||
runner.execute(Step::Vim, "voom", || vim::run_voom(&ctx))?;
|
||||
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
|
||||
runner.execute(Step::Helix, "helix", || generic::run_helix_grammars(&ctx))?;
|
||||
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
|
||||
runner.execute(Step::Yarn, "yarn", || node::run_yarn_upgrade(&ctx))?;
|
||||
runner.execute(Step::Pnpm, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
|
||||
runner.execute(Step::VoltaPackages, "volta packages", || {
|
||||
node::run_volta_packages_upgrade(&ctx)
|
||||
runner.execute(Step::Myrepos, "myrepos", || {
|
||||
generic::run_myrepos_update(&base_dirs, run_type)
|
||||
})?;
|
||||
runner.execute(Step::Chezmoi, "chezmoi", || {
|
||||
generic::run_chezmoi_update(&base_dirs, run_type)
|
||||
})?;
|
||||
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(run_type))?;
|
||||
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&base_dirs, &ctx))?;
|
||||
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&base_dirs, &ctx))?;
|
||||
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
|
||||
runner.execute(Step::Vim, "voom", || vim::run_voom(&base_dirs, run_type))?;
|
||||
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::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))?;
|
||||
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(&ctx))?;
|
||||
runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(&ctx))?;
|
||||
runner.execute(Step::Gem, "gem", || generic::run_gem(&ctx))?;
|
||||
runner.execute(Step::RubyGems, "rubygems", || generic::run_rubygems(&ctx))?;
|
||||
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(run_type))?;
|
||||
runner.execute(Step::Gem, "gem", || generic::run_gem(&base_dirs, run_type))?;
|
||||
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
|
||||
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
|
||||
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
|
||||
runner.execute(Step::Stew, "stew", || generic::run_stew(&ctx))?;
|
||||
runner.execute(Step::Rtcl, "rtcl", || generic::run_rtcl(&ctx))?;
|
||||
runner.execute(Step::Bin, "bin", || generic::bin_update(&ctx))?;
|
||||
runner.execute(Step::Gcloud, "gcloud", || generic::run_gcloud_components_update(&ctx))?;
|
||||
runner.execute(Step::Micro, "micro", || generic::run_micro(&ctx))?;
|
||||
runner.execute(Step::Raco, "raco", || generic::run_raco_update(&ctx))?;
|
||||
runner.execute(Step::Gcloud, "gcloud", || {
|
||||
generic::run_gcloud_components_update(run_type)
|
||||
})?;
|
||||
runner.execute(Step::Micro, "micro", || generic::run_micro(run_type))?;
|
||||
runner.execute(Step::Raco, "raco", || generic::run_raco_update(run_type))?;
|
||||
runner.execute(Step::Spicetify, "spicetify", || generic::spicetify_upgrade(&ctx))?;
|
||||
runner.execute(Step::GithubCliExtensions, "GitHub CLI Extensions", || {
|
||||
generic::run_ghcli_extensions_upgrade(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Bob, "Bob", || generic::run_bob(&ctx))?;
|
||||
runner.execute(Step::Certbot, "Certbot", || generic::run_certbot(&ctx))?;
|
||||
runner.execute(Step::GitRepos, "Git Repositories", || git::run_git_pull(&ctx))?;
|
||||
runner.execute(Step::ClamAvDb, "ClamAV Databases", || generic::run_freshclam(&ctx))?;
|
||||
runner.execute(Step::PlatformioCore, "PlatformIO Core", || {
|
||||
generic::run_platform_io(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Lensfun, "Lensfun's database update", || {
|
||||
generic::run_lensfun_update_data(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Poetry, "Poetry", || generic::run_poetry(&ctx))?;
|
||||
runner.execute(Step::Uv, "uv", || generic::run_uv(&ctx))?;
|
||||
runner.execute(Step::Zvm, "ZVM", || generic::run_zvm(&ctx))?;
|
||||
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
|
||||
|
||||
if should_run_powershell {
|
||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||
powershell.update_modules(&ctx)
|
||||
})?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
||||
runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?;
|
||||
runner.execute(Step::Snap, "snap", || linux::run_snap(sudo.as_ref(), run_type))?;
|
||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
||||
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
|
||||
}
|
||||
|
||||
if let Some(commands) = config.commands() {
|
||||
@@ -449,6 +380,32 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
runner.execute(Step::System, "pihole", || {
|
||||
linux::run_pihole_update(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
||||
runner.execute(Step::Restarts, "Restarts", || {
|
||||
linux::run_needrestart(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
|
||||
runner.execute(Step::Mas, "App Store", || macos::run_mas(run_type))?;
|
||||
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
runner.execute(Step::System, "FreeBSD Upgrade", || {
|
||||
freebsd::upgrade_freebsd(sudo.as_ref(), run_type)
|
||||
})?;
|
||||
|
||||
#[cfg(windows)]
|
||||
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
|
||||
|
||||
if config.should_run(Step::Vagrant) {
|
||||
if let Ok(boxes) = vagrant::collect_boxes(&ctx) {
|
||||
for vagrant_box in boxes {
|
||||
@@ -461,7 +418,7 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?;
|
||||
|
||||
if !runner.report().data().is_empty() {
|
||||
print_separator(t!("Summary"));
|
||||
print_separator("Summary");
|
||||
|
||||
for (key, result) in runner.report().data() {
|
||||
print_result(key, result);
|
||||
@@ -473,6 +430,12 @@ fn run() -> Result<()> {
|
||||
distribution.show_summary();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
freebsd::audit_packages(&sudo).ok();
|
||||
|
||||
#[cfg(target_os = "dragonfly")]
|
||||
dragonfly::audit_packages(&sudo).ok();
|
||||
}
|
||||
|
||||
let mut post_command_failed = false;
|
||||
@@ -485,14 +448,14 @@ fn run() -> Result<()> {
|
||||
}
|
||||
|
||||
if config.keep_at_end() {
|
||||
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
|
||||
print_info("\n(R)eboot\n(S)hell\n(Q)uit");
|
||||
loop {
|
||||
match get_key() {
|
||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
||||
run_shell().context("Failed to execute shell")?;
|
||||
run_shell();
|
||||
}
|
||||
Ok(Key::Char('r')) | Ok(Key::Char('R')) => {
|
||||
reboot().context("Failed to reboot")?;
|
||||
reboot();
|
||||
}
|
||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => (),
|
||||
_ => {
|
||||
@@ -506,14 +469,13 @@ fn run() -> Result<()> {
|
||||
let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed());
|
||||
|
||||
if !config.skip_notify() {
|
||||
notify_desktop(
|
||||
if failed {
|
||||
t!("Topgrade finished with errors")
|
||||
} else {
|
||||
t!("Topgrade finished successfully")
|
||||
},
|
||||
Some(Duration::from_secs(10)),
|
||||
)
|
||||
terminal::notify_desktop(
|
||||
format!(
|
||||
"Topgrade finished {}",
|
||||
if failed { "with errors" } else { "successfully" }
|
||||
),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
if failed {
|
||||
@@ -524,6 +486,9 @@ fn run() -> Result<()> {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_info("Due to r-darwish giving this project access to the original topgrade crate, this crate will no longer receive any updates beyond the 10.0.1 update. To install the supported version, please run the following command: \n");
|
||||
println!("cargo uninstall topgrade-rs");
|
||||
println!("cargo install topgrade");
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
exit(0);
|
||||
@@ -543,10 +508,7 @@ fn main() {
|
||||
.is_some());
|
||||
|
||||
if !skip_print {
|
||||
// The `Debug` implementation of `eyre::Result` prints a multi-line
|
||||
// error message that includes all the 'causes' added with
|
||||
// `.with_context(...)` calls.
|
||||
println!("{}", t!("Error: {error}", error = format!("{:?}", error)));
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl<'a> Report<'a> {
|
||||
if let Some((key, success)) = result {
|
||||
let key = key.into();
|
||||
|
||||
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{key} already reported");
|
||||
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{} already reported", key);
|
||||
self.data.push((key, success));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ use crate::ctrlc;
|
||||
use crate::error::{DryRun, SkipStep};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::report::{Report, StepResult};
|
||||
use crate::terminal::print_error;
|
||||
use crate::{config::Step, terminal::should_retry};
|
||||
use color_eyre::eyre::Result;
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use tracing::debug;
|
||||
|
||||
pub struct Runner<'a> {
|
||||
ctx: &'a ExecutionContext<'a>,
|
||||
@@ -34,14 +33,6 @@ impl<'a> Runner<'a> {
|
||||
let key = key.into();
|
||||
debug!("Step {:?}", key);
|
||||
|
||||
// alter the `func` to put it in a span
|
||||
let func = || {
|
||||
let span =
|
||||
tracing::span!(parent: tracing::Span::none(), tracing::Level::TRACE, "step", step = ?step, key = %key);
|
||||
let _guard = span.enter();
|
||||
func()
|
||||
};
|
||||
|
||||
loop {
|
||||
match func() {
|
||||
Ok(()) => {
|
||||
@@ -64,12 +55,7 @@ impl<'a> Runner<'a> {
|
||||
|
||||
let ignore_failure = self.ctx.config().ignore_failure(step);
|
||||
let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure);
|
||||
let should_retry = if should_ask {
|
||||
print_error(&key, format!("{e:?}"));
|
||||
should_retry(interrupted, key.as_ref())?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let should_retry = should_ask && should_retry(interrupted, key.as_ref())?;
|
||||
|
||||
if !should_retry {
|
||||
self.report.push_result(Some((
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use color_eyre::eyre::Result;
|
||||
#![cfg(windows)]
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, error};
|
||||
use std::{env::current_exe, fs, path::PathBuf};
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub struct SelfRenamer {
|
||||
exe_path: PathBuf,
|
||||
@@ -29,7 +31,7 @@ impl Drop for SelfRenamer {
|
||||
}
|
||||
|
||||
match fs::rename(&self.temp_path, &self.exe_path) {
|
||||
Ok(_) => debug!("Moved Topgrade back from {:?} to {:?}", self.temp_path, self.exe_path),
|
||||
Ok(_) => debug!("Moved topgrade back from {:?} to {:?}", self.temp_path, self.exe_path),
|
||||
Err(e) => error!(
|
||||
"Could not move Topgrade from {} back to {}: {}",
|
||||
self.temp_path.display(),
|
||||
|
||||
@@ -1,73 +1,59 @@
|
||||
use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::config::Step;
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use rust_i18n::t;
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
|
||||
use super::terminal::*;
|
||||
#[cfg(windows)]
|
||||
use crate::error::Upgraded;
|
||||
use anyhow::{bail, Result};
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
use std::env;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::execution_context::ExecutionContext;
|
||||
pub fn self_update() -> Result<()> {
|
||||
print_separator("Self update");
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("Self update"));
|
||||
let target = self_update_crate::get_target();
|
||||
let result = 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_crate::cargo_crate_version!())
|
||||
.no_confirm(true)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
println!("{}", t!("Would self-update"));
|
||||
Ok(())
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("\nTopgrade upgraded to {}:\n", release.version);
|
||||
if let Some(body) = &release.body {
|
||||
println!("{}", body);
|
||||
}
|
||||
} else {
|
||||
let assume_yes = ctx.config().yes(Step::SelfUpdate);
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
let target = self_update_crate::get_target();
|
||||
let result = Update::configure()
|
||||
.repo_owner("topgrade-rs")
|
||||
.repo_name("topgrade")
|
||||
.target(target)
|
||||
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
||||
.show_output(true)
|
||||
.show_download_progress(true)
|
||||
.current_version(self_update_crate::cargo_crate_version!())
|
||||
.no_confirm(assume_yes)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("{}", t!("Topgrade upgraded to {version}:\n", version = release.version));
|
||||
if let Some(body) = &release.body {
|
||||
println!("{body}");
|
||||
}
|
||||
} else {
|
||||
println!("{}", t!("Topgrade is up-to-date"));
|
||||
}
|
||||
|
||||
{
|
||||
if result.updated() {
|
||||
print_info(t!("Respawning..."));
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = command.status()?;
|
||||
bail!(Upgraded(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
println!("Topgrade is up-to-date");
|
||||
}
|
||||
|
||||
{
|
||||
if result.updated() {
|
||||
print_warning("Respawning...");
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let status = command.spawn().and_then(|mut c| c.wait())?;
|
||||
bail!(Upgraded(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,196 +1,109 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use tracing::{debug, error, warn};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{self, TopgradeError};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use rust_i18n::t;
|
||||
|
||||
// A string found in the output of docker for containers that weren't found in
|
||||
// the docker registry. We use this to gracefully handle and skip containers
|
||||
// that cannot be pulled, likely because they don't exist in the registry in
|
||||
// the first place. This happens e.g. when the user tags an image locally
|
||||
// themselves or when using docker-compose.
|
||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||
|
||||
/// Uniquely identifies a `Container`.
|
||||
#[derive(Debug)]
|
||||
struct Container {
|
||||
/// `Repository` and `Tag`
|
||||
///
|
||||
/// format: `Repository:Tag`, e.g., `nixos/nix:latest`.
|
||||
repo_tag: String,
|
||||
/// Platform
|
||||
///
|
||||
/// format: `OS/Architecture`, e.g., `linux/amd64`.
|
||||
platform: String,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
/// Construct a new `Container`.
|
||||
fn new(repo_tag: String, platform: String) -> Self {
|
||||
Self { repo_tag, platform }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Container {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// e.g., "`fedora:latest` for `linux/amd64`"
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
t!(
|
||||
"`{repo_tag}` for `{platform}`",
|
||||
repo_tag = self.repo_tag,
|
||||
platform = self.platform
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Vector of all containers, with Strings in the format
|
||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||
///
|
||||
/// Containers specified in `ignored_containers` will be filtered out.
|
||||
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
|
||||
let ignored_containers = ignored_containers.map(|patterns| {
|
||||
patterns
|
||||
.iter()
|
||||
.map(|pattern| WildMatch::new(pattern))
|
||||
.collect::<Vec<WildMatch>>()
|
||||
});
|
||||
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
||||
crt.display()
|
||||
);
|
||||
let output = Command::new(crt)
|
||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
|
||||
.output_checked_with_utf8(|_| Ok(()))?;
|
||||
|
||||
let mut retval = vec![];
|
||||
for line in output.stdout.lines() {
|
||||
if line.starts_with("localhost") {
|
||||
// Don't know how to update self-built containers
|
||||
debug!("Skipping self-built container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.contains("<none>") {
|
||||
// Bogus/dangling container or intermediate layer
|
||||
debug!("Skipping bogus container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("vsc-") {
|
||||
debug!("Skipping visual studio code dev container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Using container '{}'", line);
|
||||
|
||||
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
|
||||
let split_res = line.split(' ').collect::<Vec<&str>>();
|
||||
assert_eq!(split_res.len(), 2);
|
||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||
|
||||
if let Some(ref ignored_containers) = ignored_containers {
|
||||
if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
|
||||
debug!("Skipping ignored container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
|
||||
crt.display(),
|
||||
image_id
|
||||
);
|
||||
let inspect_output = Command::new(crt)
|
||||
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
|
||||
.output_checked_with_utf8(|_| Ok(()))?;
|
||||
let mut platform = inspect_output.stdout;
|
||||
// truncate the tailing new line character
|
||||
platform.truncate(platform.len() - 1);
|
||||
assert!(platform.contains('/'));
|
||||
|
||||
retval.push(Container::new(repo_tag.to_string(), platform));
|
||||
}
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
|
||||
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Check what runtime is specified in the config
|
||||
let container_runtime = ctx.config().containers_runtime().to_string();
|
||||
let crt = require(container_runtime)?;
|
||||
debug!("Using container runtime '{}'", crt.display());
|
||||
|
||||
print_separator(t!("Containers"));
|
||||
let mut success = true;
|
||||
let containers =
|
||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in containers.iter() {
|
||||
debug!("Pulling container '{}'", container);
|
||||
let args = vec![
|
||||
"pull",
|
||||
container.repo_tag.as_str(),
|
||||
"--platform",
|
||||
container.platform.as_str(),
|
||||
];
|
||||
let mut exec = ctx.run_type().execute(&crt);
|
||||
|
||||
if let Err(e) = exec.args(&args).status_checked() {
|
||||
error!("Pulling container '{}' failed: {}", container, e);
|
||||
|
||||
// Find out if this is 'skippable'
|
||||
// This is necessary e.g. for docker, because unlike podman docker doesn't tell from
|
||||
// which repository a container originates (such as `docker.io`). This has the
|
||||
// practical consequence that all containers, whether self-built, created by
|
||||
// docker-compose or pulled from the docker hub, look exactly the same to us. We can
|
||||
// only find out what went wrong by manually parsing the output of the command...
|
||||
if match exec.output_checked_utf8() {
|
||||
Ok(s) => s.stdout.contains(NONEXISTENT_REPO) || s.stderr.contains(NONEXISTENT_REPO),
|
||||
Err(e) => match e.downcast_ref::<TopgradeError>() {
|
||||
Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(NONEXISTENT_REPO),
|
||||
_ => false,
|
||||
},
|
||||
} {
|
||||
warn!("Skipping unknown container '{}'", container);
|
||||
continue;
|
||||
}
|
||||
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
// Remove dangling images
|
||||
debug!("Removing dangling images");
|
||||
if let Err(e) = ctx
|
||||
.run_type()
|
||||
.execute(&crt)
|
||||
.args(["image", "prune", "-f"])
|
||||
.status_checked()
|
||||
{
|
||||
error!("Removing dangling images failed: {}", e);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre!(error::StepFailed))
|
||||
}
|
||||
}
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::error::{self, TopgradeError};
|
||||
use crate::executor::CommandExt;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use log::{debug, error, warn};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// A string found in the output of docker for containers that weren't found in
|
||||
// the docker registry. We use this to gracefully handle and skip containers
|
||||
// that cannot be pulled, likely because they don't exist in the registry in
|
||||
// the first place. This happens e.g. when the user tags an image locally
|
||||
// themselves or when using docker-compose.
|
||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||
|
||||
/// Returns a Vector of all containers, with Strings in the format
|
||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||
fn list_containers(crt: &Path) -> Result<Vec<String>> {
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers",
|
||||
crt.display()
|
||||
);
|
||||
let output = Command::new(crt)
|
||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"])
|
||||
.output()?;
|
||||
let output_str = String::from_utf8(output.stdout)?;
|
||||
|
||||
let mut retval = vec![];
|
||||
for line in output_str.lines() {
|
||||
if line.starts_with("localhost") {
|
||||
// Don't know how to update self-built containers
|
||||
debug!("Skipping self-built container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.contains("<none>") {
|
||||
// Bogus/dangling container or intermediate layer
|
||||
debug!("Skipping bogus container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("vsc-") {
|
||||
debug!("Skipping visual studio code dev container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Using container '{}'", line);
|
||||
retval.push(String::from(line));
|
||||
}
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
|
||||
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Prefer podman, fall back to docker if not present
|
||||
let crt = require("podman").or_else(|_| require("docker"))?;
|
||||
debug!("Using container runtime '{}'", crt.display());
|
||||
|
||||
print_separator("Containers");
|
||||
let mut success = true;
|
||||
let containers = list_containers(&crt)?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in containers.iter() {
|
||||
debug!("Pulling container '{}'", container);
|
||||
let args = vec!["pull", &container[..]];
|
||||
let mut exec = ctx.run_type().execute(&crt);
|
||||
|
||||
if let Err(e) = exec.args(&args).check_run() {
|
||||
error!("Pulling container '{}' failed: {}", container, e);
|
||||
|
||||
// Find out if this is 'skippable'
|
||||
// This is necessary e.g. for docker, because unlike podman docker doesn't tell from
|
||||
// which repository a container originates (such as `docker.io`). This has the
|
||||
// practical consequence that all containers, whether self-built, created by
|
||||
// docker-compose or pulled from the docker hub, look exactly the same to us. We can
|
||||
// only find out what went wrong by manually parsing the output of the command...
|
||||
if match exec.check_output() {
|
||||
Ok(s) => s.contains(NONEXISTENT_REPO),
|
||||
Err(e) => match e.downcast_ref::<TopgradeError>() {
|
||||
Some(TopgradeError::ProcessFailedWithOutput(_, stderr)) => stderr.contains(NONEXISTENT_REPO),
|
||||
_ => false,
|
||||
},
|
||||
} {
|
||||
warn!("Skipping unknown container '{}'", container);
|
||||
continue;
|
||||
}
|
||||
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
// Remove dangling images
|
||||
debug!("Removing dangling images");
|
||||
if let Err(e) = ctx.run_type().execute(&crt).args(["image", "prune", "-f"]).check_run() {
|
||||
error!("Removing dangling images failed: {}", e);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(error::StepFailed))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, require_option, PathExt};
|
||||
@@ -24,12 +22,20 @@ pub struct Emacs {
|
||||
}
|
||||
|
||||
impl Emacs {
|
||||
fn directory_path() -> Option<PathBuf> {
|
||||
fn directory_path(base_dirs: &BaseDirs) -> Option<PathBuf> {
|
||||
#[cfg(unix)]
|
||||
return {
|
||||
let emacs_xdg_dir = crate::XDG_DIRS.config_dir().join("emacs").if_exists();
|
||||
crate::HOME_DIR.join(".emacs.d").if_exists().or(emacs_xdg_dir)
|
||||
};
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
let emacs_xdg_dir = env::var("XDG_CONFIG_HOME")
|
||||
.ok()
|
||||
.and_then(|config| PathBuf::from(config).join("emacs").if_exists())
|
||||
.or_else(|| base_dirs.home_dir().join(".config/emacs").if_exists());
|
||||
} else {
|
||||
let emacs_xdg_dir = base_dirs.config_dir().join("emacs").if_exists();
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
return base_dirs.home_dir().join(".emacs.d").if_exists().or(emacs_xdg_dir);
|
||||
|
||||
#[cfg(windows)]
|
||||
return env::var("HOME")
|
||||
@@ -40,11 +46,11 @@ impl Emacs {
|
||||
.if_exists()
|
||||
.or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists())
|
||||
})
|
||||
.or_else(|| crate::WINDOWS_DIRS.data_dir().join(".emacs.d").if_exists());
|
||||
.or_else(|| base_dirs.data_dir().join(".emacs.d").if_exists());
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let directory = Emacs::directory_path();
|
||||
pub fn new(base_dirs: &BaseDirs) -> Self {
|
||||
let directory = Emacs::directory_path(base_dirs);
|
||||
let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists());
|
||||
Self { directory, doom }
|
||||
}
|
||||
@@ -67,7 +73,7 @@ impl Emacs {
|
||||
|
||||
command.args(["upgrade"]);
|
||||
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
|
||||
pub fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -75,16 +81,13 @@ impl Emacs {
|
||||
if let Some(doom) = &self.doom {
|
||||
Emacs::update_doom(doom, ctx)?;
|
||||
}
|
||||
let init_file = require_option(
|
||||
self.directory.as_ref(),
|
||||
t!("Emacs directory does not exist").to_string(),
|
||||
)?
|
||||
.join("init.el")
|
||||
.require()?;
|
||||
let init_file = require_option(self.directory.as_ref(), String::from("Emacs directory does not exist"))?
|
||||
.join("init.el")
|
||||
.require()?;
|
||||
|
||||
print_separator("Emacs");
|
||||
|
||||
let mut command = ctx.run_type().execute(emacs);
|
||||
let mut command = ctx.run_type().execute(&emacs);
|
||||
|
||||
command
|
||||
.args(["--batch", "--debug-init", "-l"])
|
||||
@@ -102,6 +105,6 @@ impl Emacs {
|
||||
#[cfg(not(unix))]
|
||||
command.arg(EMACS_UPGRADE);
|
||||
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
523
src/steps/git.rs
523
src/steps/git.rs
@@ -3,176 +3,141 @@ use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output, Stdio};
|
||||
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use console::style;
|
||||
use futures::stream::{iter, FuturesUnordered, StreamExt};
|
||||
use futures::stream::{iter, FuturesUnordered};
|
||||
use futures::StreamExt;
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use log::{debug, error};
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use tokio::runtime;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::Step;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::steps::emacs::Emacs;
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use rust_i18n::t;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
|
||||
pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut repos = RepoStep::try_new()?;
|
||||
let config = ctx.config();
|
||||
|
||||
// handle built-in repos
|
||||
if config.use_predefined_git_repos() {
|
||||
// should be executed on all the platforms
|
||||
{
|
||||
if config.should_run(Step::Emacs) {
|
||||
let emacs = Emacs::new();
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
}
|
||||
|
||||
repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
}
|
||||
|
||||
let powershell = crate::steps::powershell::Powershell::new();
|
||||
if let Some(profile) = powershell.profile() {
|
||||
repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
repos.insert_if_repo(crate::steps::zsh::zshrc());
|
||||
if config.should_run(Step::Tmux) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/fish"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
repos.insert_if_repo(
|
||||
WINDOWS_DIRS
|
||||
.cache_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
super::os::windows::insert_startup_scripts(&mut repos).ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user-defined repos
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
// user won't receive this warning in the cases where all the paths configured
|
||||
// are bad patterns.
|
||||
repos.bad_patterns.iter().for_each(|pattern| {
|
||||
print_warning(t!(
|
||||
"Path {pattern} did not contain any git repositories",
|
||||
pattern = pattern
|
||||
))
|
||||
});
|
||||
|
||||
if repos.is_repos_empty() {
|
||||
return Err(SkipStep(t!("No repositories to pull").to_string()).into());
|
||||
}
|
||||
|
||||
print_separator(t!("Git repositories"));
|
||||
|
||||
repos.pull_repos(ctx)
|
||||
}
|
||||
use crate::utils::{which, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning};
|
||||
|
||||
#[cfg(windows)]
|
||||
static PATH_PREFIX: &str = "\\\\?\\";
|
||||
|
||||
pub struct RepoStep {
|
||||
git: PathBuf,
|
||||
repos: HashSet<PathBuf>,
|
||||
#[derive(Debug)]
|
||||
pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
repositories: HashSet<String>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
fn check_output(output: Output) -> Result<()> {
|
||||
if !(output.status.success()) {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stderr = stderr.trim();
|
||||
Err(eyre!("{stderr}"))
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
Err(anyhow!(stderr))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
let before_revision = get_head_revision(git, &repo);
|
||||
|
||||
println!("{} {}", style("Pulling").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = check_output(pull_output).and_then(|_| check_output(submodule_output));
|
||||
|
||||
if let Err(message) = &result {
|
||||
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
||||
print!("{}", message);
|
||||
} else {
|
||||
let after_revision = get_head_revision(git, &repo);
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}:", style("Changed").yellow().bold(), &repo);
|
||||
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{}..{}", before, after),
|
||||
])
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
println!("{} {}", style("Up-to-date").green().bold(), &repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
fn get_head_revision(git: &Path, repo: &str) -> Option<String> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo.as_ref())
|
||||
.current_dir(repo)
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output_checked_utf8()
|
||||
.map(|output| output.stdout.trim().to_string())
|
||||
.check_output()
|
||||
.map(|output| output.trim().to_string())
|
||||
.map_err(|e| {
|
||||
error!("Error getting revision for {}: {e}", repo.as_ref().display(),);
|
||||
error!("Error getting revision for {}: {}", repo, e);
|
||||
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
impl RepoStep {
|
||||
/// Try to create a `RepoStep`, fail if `git` is not found.
|
||||
pub fn try_new() -> Result<Self> {
|
||||
let git = require("git")?;
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
git,
|
||||
repos: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
fn has_remotes(git: &Path, repo: &str) -> Option<bool> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo)
|
||||
.args(["remote", "show"])
|
||||
.check_output()
|
||||
.map(|output| output.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {}", repo, e);
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
impl Git {
|
||||
pub fn new() -> Self {
|
||||
Self { git: which("git") }
|
||||
}
|
||||
|
||||
/// Try to get the root of the repo specified in `path`.
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<String> {
|
||||
match path.as_ref().canonicalize() {
|
||||
Ok(mut path) => {
|
||||
debug_assert!(path.exists());
|
||||
@@ -196,210 +161,65 @@ impl RepoStep {
|
||||
path_string
|
||||
};
|
||||
|
||||
let output = Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.output_checked_utf8()
|
||||
.ok()
|
||||
// trim the last newline char
|
||||
.map(|output| PathBuf::from(output.stdout.trim()));
|
||||
|
||||
return output;
|
||||
if let Some(git) = &self.git {
|
||||
let output = Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.check_output()
|
||||
.ok()
|
||||
.map(|output| output.trim().to_string());
|
||||
return output;
|
||||
}
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {e}", path.as_ref().display(),),
|
||||
io::ErrorKind::NotFound => debug!("{} does not exists", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
||||
},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if `path` is a git repo, if yes, add it to `self.repos`.
|
||||
///
|
||||
/// Return the check result.
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
if let Some(repo) = self.get_repo_root(path) {
|
||||
self.repos.insert(repo);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `repo` has a remote.
|
||||
fn has_remotes<P: AsRef<Path>>(&self, repo: P) -> Option<bool> {
|
||||
let mut cmd = Command::new(&self.git);
|
||||
cmd.stdin(Stdio::null())
|
||||
.current_dir(repo.as_ref())
|
||||
.args(["remote", "show"]);
|
||||
|
||||
let res = cmd.output_checked_utf8();
|
||||
|
||||
res.map(|output| output.stdout.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {e}", repo.as_ref().display());
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Similar to `insert_if_repo`, with glob support.
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
for entry in glob {
|
||||
match entry {
|
||||
Ok(path) => {
|
||||
if let Some(last_git_repo) = &last_git_repo {
|
||||
if path.is_descendant_of(last_git_repo) {
|
||||
debug!(
|
||||
"Skipping {} because it's a descendant of last known repo {}",
|
||||
path.display(),
|
||||
last_git_repo.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if self.insert_if_repo(&path) {
|
||||
last_git_repo = Some(path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error in path {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_git_repo.is_none() {
|
||||
self.bad_patterns.push(String::from(pattern));
|
||||
}
|
||||
} else {
|
||||
error!("Bad glob pattern: {pattern}");
|
||||
}
|
||||
}
|
||||
|
||||
/// True if `self.repos` is empty.
|
||||
pub fn is_repos_empty(&self) -> bool {
|
||||
self.repos.is_empty()
|
||||
}
|
||||
|
||||
/// Remove `path` from `self.repos`.
|
||||
///
|
||||
// `cfg(unix)` because it is only used in the oh-my-zsh step.
|
||||
#[cfg(unix)]
|
||||
pub fn remove<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let _removed = self.repos.remove(path.as_ref());
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
|
||||
/// Try to pull a repo.
|
||||
async fn pull_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
|
||||
let before_revision = get_head_revision(&self.git, &repo);
|
||||
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
if repositories.repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
}
|
||||
|
||||
let mut command = AsyncCommand::new(&self.git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(&self.git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|_| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
||||
|
||||
if result.is_err() {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(t!("Failed")).red().bold(),
|
||||
t!("pulling"),
|
||||
repo.as_ref().display()
|
||||
);
|
||||
} else {
|
||||
let after_revision = get_head_revision(&self.git, repo.as_ref());
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}", style(t!("Changed")).yellow().bold(), repo.as_ref().display());
|
||||
|
||||
Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{before}..{after}"),
|
||||
])
|
||||
.status_checked()?;
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style(t!("Up-to-date")).green().bold(), repo.as_ref().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
print_separator("Git repositories");
|
||||
repositories
|
||||
.bad_patterns
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {} did not contain any git repositories", pattern)));
|
||||
self.multi_pull(repositories, ctx)
|
||||
}
|
||||
|
||||
/// Pull the repositories specified in `self.repos`.
|
||||
///
|
||||
/// # NOTE
|
||||
/// This function will create an async runtime and do the real job so the
|
||||
/// function itself is not async.
|
||||
fn pull_repos(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
if ctx.run_type().dry() {
|
||||
self.repos
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
let git = self.git.as_ref().unwrap();
|
||||
|
||||
if let RunType::Dry = ctx.run_type() {
|
||||
repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("{}", t!("Would pull {repo}", repo = repo.display())));
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !ctx.config().verbose() {
|
||||
println!(
|
||||
"\n{} {}\n",
|
||||
style(t!("Only")).green().bold(),
|
||||
t!("updated repositories will be shown...")
|
||||
);
|
||||
}
|
||||
|
||||
let futures_iterator = self
|
||||
.repos
|
||||
let futures_iterator = repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.filter(|repo| match self.has_remotes(repo) {
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(t!("Skipping")).yellow().bold(),
|
||||
repo.display(),
|
||||
t!("because it has no remotes")
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| self.pull_repo(ctx, repo));
|
||||
.map(|repo| pull_repository(repo.clone(), git, ctx));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
@@ -414,3 +234,74 @@ impl RepoStep {
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Repositories<'a> {
|
||||
pub fn new(git: &'a Git) -> Self {
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Self {
|
||||
git,
|
||||
repositories: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
self.repositories.insert(repo);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
for entry in glob {
|
||||
match entry {
|
||||
Ok(path) => {
|
||||
if let Some(last_git_repo) = &last_git_repo {
|
||||
if path.is_descendant_of(last_git_repo) {
|
||||
debug!(
|
||||
"Skipping {} because it's a decendant of last known repo {}",
|
||||
path.display(),
|
||||
last_git_repo.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if self.insert_if_repo(&path) {
|
||||
last_git_repo = Some(path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error in path {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_git_repo.is_none() {
|
||||
self.bad_patterns.push(String::from(pattern));
|
||||
}
|
||||
} else {
|
||||
error!("Bad glob pattern: {}", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.repositories.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils;
|
||||
use crate::utils::PathExt;
|
||||
|
||||
/// <https://github.com/Gelio/go-global-update>
|
||||
pub fn run_go_global_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let go_global_update = require_go_bin("go-global-update")?;
|
||||
|
||||
print_separator("go-global-update");
|
||||
|
||||
ctx.run_type().execute(go_global_update).status_checked()
|
||||
}
|
||||
|
||||
/// <https://github.com/nao1215/gup>
|
||||
pub fn run_go_gup(ctx: &ExecutionContext) -> Result<()> {
|
||||
let gup = require_go_bin("gup")?;
|
||||
|
||||
print_separator("gup");
|
||||
|
||||
ctx.run_type().execute(gup).arg("update").status_checked()
|
||||
}
|
||||
|
||||
/// Get the path of a Go binary.
|
||||
fn require_go_bin(name: &str) -> Result<PathBuf> {
|
||||
utils::require(name).or_else(|_| {
|
||||
let go = utils::require("go")?;
|
||||
// TODO: Does this work? `go help gopath` says that:
|
||||
// > The GOPATH environment variable lists places to look for Go code.
|
||||
// > On Unix, the value is a colon-separated string.
|
||||
// > On Windows, the value is a semicolon-separated string.
|
||||
// > On Plan 9, the value is a list.
|
||||
// Should we also fallback to the env variable?
|
||||
let gopath_output = Command::new(go).args(["env", "GOPATH"]).output_checked_utf8()?;
|
||||
let gopath = gopath_output.stdout.trim();
|
||||
|
||||
PathBuf::from(gopath).join("bin").join(name).require()
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::error::TopgradeError;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::ExecutorOutput;
|
||||
|
||||
const UPGRADE_KAK: &str = include_str!("upgrade.kak");
|
||||
|
||||
@@ -12,13 +13,19 @@ pub fn upgrade_kak_plug(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Kakoune");
|
||||
|
||||
// TODO: Why supress output for this command?
|
||||
ctx.run_type()
|
||||
.execute(kak)
|
||||
.args(["-ui", "dummy", "-e", UPGRADE_KAK])
|
||||
.output()?;
|
||||
let mut command = ctx.run_type().execute(&kak);
|
||||
command.args(["-ui", "dummy", "-e", UPGRADE_KAK]);
|
||||
|
||||
println!("{}", t!("Plugins upgraded"));
|
||||
let output = command.output()?;
|
||||
|
||||
if let ExecutorOutput::Wet(output) = output {
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
return Err(TopgradeError::ProcessFailed(status).into());
|
||||
} else {
|
||||
println!("Plugins upgraded")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ pub mod containers;
|
||||
pub mod emacs;
|
||||
pub mod generic;
|
||||
pub mod git;
|
||||
pub mod go;
|
||||
pub mod kakoune;
|
||||
pub mod node;
|
||||
pub mod os;
|
||||
|
||||
@@ -1,106 +1,66 @@
|
||||
use std::fmt::Display;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use crate::HOME_DIR;
|
||||
use color_eyre::eyre::Result;
|
||||
#[cfg(target_os = "linux")]
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use log::debug;
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Uid;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::terminal::{print_info, print_separator};
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
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 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<PathBuf> {
|
||||
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)
|
||||
.output_checked_utf8()
|
||||
.map(|s| PathBuf::from(s.stdout.trim()))
|
||||
.check_output()
|
||||
.map(|s| PathBuf::from(s.trim()))
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<Version> {
|
||||
let version_str = Command::new(&self.command)
|
||||
.args(["--version"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
.check_output()
|
||||
.map(|s| s.trim().to_owned());
|
||||
Version::parse(&version_str?).map_err(|err| err.into())
|
||||
}
|
||||
|
||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||
let args = ["update", self.global_location_arg()];
|
||||
if use_sudo {
|
||||
let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(&self.command)
|
||||
.args(args)
|
||||
.status_checked()?;
|
||||
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 {
|
||||
ctx.run_type().execute(&self.command).args(args).status_checked()?;
|
||||
["update", "--location=global"]
|
||||
};
|
||||
if use_sudo {
|
||||
run_type.execute("sudo").args(args).check_run()?;
|
||||
} else {
|
||||
run_type.execute(&self.command).args(args).check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -110,7 +70,7 @@ impl NPM {
|
||||
pub fn should_use_sudo(&self) -> Result<bool> {
|
||||
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)?;
|
||||
@@ -133,38 +93,27 @@ 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"]).output_checked_utf8();
|
||||
|
||||
matches!(version, Ok(ver) if ver.stdout.starts_with('1') || ver.stdout.starts_with('0'))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn root(&self) -> Result<PathBuf> {
|
||||
let args = ["global", "dir"];
|
||||
Command::new(&self.command)
|
||||
.args(args)
|
||||
.output_checked_utf8()
|
||||
.map(|s| PathBuf::from(s.stdout.trim()))
|
||||
.check_output()
|
||||
.map(|s| PathBuf::from(s.trim()))
|
||||
}
|
||||
|
||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> {
|
||||
print_separator("Yarn Package Manager");
|
||||
let args = ["global", "upgrade"];
|
||||
|
||||
if use_sudo {
|
||||
let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
run_type
|
||||
.execute("sudo")
|
||||
.arg(self.yarn.as_ref().unwrap_or(&self.command))
|
||||
.args(args)
|
||||
.status_checked()?;
|
||||
.check_run()?;
|
||||
} else {
|
||||
ctx.run_type().execute(&self.command).args(args).status_checked()?;
|
||||
run_type.execute(&self.command).args(args).check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -174,7 +123,7 @@ impl Yarn {
|
||||
pub fn should_use_sudo(&self) -> Result<bool> {
|
||||
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)?;
|
||||
@@ -213,112 +162,42 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result<bool> {
|
||||
}
|
||||
|
||||
pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?;
|
||||
|
||||
print_separator(t!("Node Package Manager"));
|
||||
let npm = require("pnpm").or_else(|_| require("npm")).map(NPM::new)?;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
npm.upgrade(ctx, should_use_sudo(&npm, ctx)?)
|
||||
npm.upgrade(ctx.run_type(), should_use_sudo(&npm, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
npm.upgrade(ctx, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
|
||||
|
||||
print_separator(t!("Performant Node Package Manager"));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
pnpm.upgrade(ctx, should_use_sudo(&pnpm, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
pnpm.upgrade(ctx, false)
|
||||
npm.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(());
|
||||
}
|
||||
|
||||
print_separator(t!("Yarn Package Manager"));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
yarn.upgrade(ctx, should_use_sudo_yarn(&yarn, ctx)?)
|
||||
yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
yarn.upgrade(ctx, false)
|
||||
yarn.upgrade(ctx.run_type(), false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let deno = require("deno")?;
|
||||
let deno_dir = HOME_DIR.join(".deno");
|
||||
let deno_dir = ctx.base_dirs().home_dir().join(".deno");
|
||||
|
||||
if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||
let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
|
||||
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string());
|
||||
return Err(skip_reason.into());
|
||||
}
|
||||
|
||||
print_separator("Deno");
|
||||
ctx.run_type().execute(&deno).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
/// There is no `volta upgrade` command, so we need to upgrade each package
|
||||
pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let volta = require("volta")?;
|
||||
|
||||
print_separator("Volta");
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
print_info(t!("Updating Volta packages..."));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let list_output = ctx
|
||||
.run_type()
|
||||
.execute(&volta)
|
||||
.args(["list", "--format=plain"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let installed_packages: Vec<&str> = list_output
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
// format is 'kind package@version ...'
|
||||
let mut parts = line.split_whitespace();
|
||||
parts.next();
|
||||
let package_part = parts.next()?;
|
||||
let version_index = package_part.rfind('@').unwrap_or(package_part.len());
|
||||
Some(package_part[..version_index].trim())
|
||||
})
|
||||
.collect();
|
||||
|
||||
if installed_packages.is_empty() {
|
||||
print_info(t!("No packages installed with Volta"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for package in installed_packages.iter() {
|
||||
ctx.run_type()
|
||||
.execute(&volta)
|
||||
.args(["install", package])
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
ctx.run_type().execute(&deno).arg("upgrade").check_run()
|
||||
}
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::require;
|
||||
use crate::utils::which;
|
||||
use crate::Step;
|
||||
use color_eyre::eyre::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
//let pkg = require("pkg")?;
|
||||
let pkg = which("nala").or_else(|| which("pkg")).unwrap();
|
||||
let pkg = require("pkg")?;
|
||||
|
||||
print_separator("Termux Packages");
|
||||
|
||||
let is_nala = pkg.ends_with("nala");
|
||||
|
||||
let mut command = ctx.run_type().execute(&pkg);
|
||||
command.arg("upgrade");
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if !is_nala && ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&pkg).arg("clean").status_checked()?;
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&pkg).arg("clean").check_run()?;
|
||||
|
||||
let apt = require("apt")?;
|
||||
let mut command = ctx.run_type().execute(apt);
|
||||
let mut command = ctx.run_type().execute(&apt);
|
||||
command.arg("autoremove");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use std::env::var_os;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use anyhow::Result;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::TopgradeError;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::utils::require_option;
|
||||
use crate::utils::which;
|
||||
use crate::{config, Step};
|
||||
|
||||
@@ -32,10 +29,11 @@ pub struct YayParu {
|
||||
impl ArchPackageManager for YayParu {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
if ctx.config().show_arch_news() {
|
||||
ctx.run_type()
|
||||
.execute(&self.executable)
|
||||
Command::new(&self.executable)
|
||||
.arg("-Pw")
|
||||
.status_checked_with_codes(&[1, 0])?;
|
||||
.spawn()
|
||||
.and_then(|mut p| p.wait())
|
||||
.ok();
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
@@ -50,7 +48,7 @@ impl ArchPackageManager for YayParu {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
@@ -58,7 +56,7 @@ impl ArchPackageManager for YayParu {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -74,37 +72,6 @@ impl YayParu {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GarudaUpdate {
|
||||
executable: PathBuf,
|
||||
}
|
||||
|
||||
impl ArchPackageManager for GarudaUpdate {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
|
||||
command
|
||||
.env("PATH", get_execution_path())
|
||||
.env("UPDATE_AUR", "1")
|
||||
.env("SKIP_MIRRORLIST", "1");
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.env("PACMAN_NOCONFIRM", "1");
|
||||
}
|
||||
command.args(ctx.config().garuda_update_arguments().split_whitespace());
|
||||
command.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GarudaUpdate {
|
||||
fn get() -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("garuda-update")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Trizen {
|
||||
executable: PathBuf,
|
||||
}
|
||||
@@ -121,7 +88,7 @@ impl ArchPackageManager for Trizen {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
@@ -129,7 +96,7 @@ impl ArchPackageManager for Trizen {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -145,13 +112,13 @@ impl Trizen {
|
||||
}
|
||||
|
||||
pub struct Pacman {
|
||||
sudo: PathBuf,
|
||||
executable: PathBuf,
|
||||
}
|
||||
|
||||
impl ArchPackageManager for Pacman {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), "sudo is required to run pacman".into())?;
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
let mut command = ctx.run_type().execute(&self.sudo);
|
||||
command
|
||||
.arg(&self.executable)
|
||||
.arg("-Syu")
|
||||
@@ -159,15 +126,15 @@ impl ArchPackageManager for Pacman {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
let mut command = ctx.run_type().execute(&self.sudo);
|
||||
command.arg(&self.executable).arg("-Scc");
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -175,9 +142,10 @@ impl ArchPackageManager for Pacman {
|
||||
}
|
||||
|
||||
impl Pacman {
|
||||
pub fn get() -> Option<Self> {
|
||||
pub fn get(ctx: &ExecutionContext) -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("powerpill").unwrap_or_else(|| PathBuf::from("pacman")),
|
||||
sudo: ctx.sudo().to_owned()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -207,7 +175,7 @@ impl ArchPackageManager for Pikaur {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
@@ -215,7 +183,7 @@ impl ArchPackageManager for Pikaur {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--noconfirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -246,7 +214,7 @@ impl ArchPackageManager for Pamac {
|
||||
command.arg("--no-confirm");
|
||||
}
|
||||
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
let mut command = ctx.run_type().execute(&self.executable);
|
||||
@@ -254,7 +222,7 @@ impl ArchPackageManager for Pamac {
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("--no-confirm");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -263,76 +231,47 @@ impl ArchPackageManager for Pamac {
|
||||
|
||||
pub struct Aura {
|
||||
executable: PathBuf,
|
||||
sudo: PathBuf,
|
||||
}
|
||||
|
||||
impl Aura {
|
||||
fn get() -> Option<Self> {
|
||||
fn get(ctx: &ExecutionContext) -> Option<Self> {
|
||||
Some(Self {
|
||||
executable: which("aura")?,
|
||||
sudo: ctx.sudo().to_owned()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchPackageManager for Aura {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
use semver::Version;
|
||||
let sudo = which("sudo").unwrap_or(PathBuf::new());
|
||||
let mut aur_update = ctx.run_type().execute(&sudo);
|
||||
|
||||
let version_cmd_output = ctx
|
||||
.run_type()
|
||||
.execute(&self.executable)
|
||||
.arg("--version")
|
||||
.output_checked_utf8()?;
|
||||
// Output will be something like: "aura x.x.x\n"
|
||||
let version_cmd_stdout = version_cmd_output.stdout;
|
||||
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
||||
let version = Version::parse(version_str).expect("invalid version");
|
||||
|
||||
// Aura, since version 4.0.6, no longer needs sudo.
|
||||
//
|
||||
// https://github.com/fosskers/aura/releases/tag/v4.0.6
|
||||
let version_no_sudo = Version::new(4, 0, 6);
|
||||
|
||||
if version >= version_no_sudo {
|
||||
let mut cmd = ctx.run_type().execute(&self.executable);
|
||||
cmd.arg("-Au")
|
||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
let mut cmd = ctx.run_type().execute(&self.executable);
|
||||
cmd.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
} else {
|
||||
let sudo = crate::utils::require_option(
|
||||
ctx.sudo().as_ref(),
|
||||
t!("Aura(<0.4.6) requires sudo installed to work with AUR packages").to_string(),
|
||||
)?;
|
||||
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg(&self.executable)
|
||||
if sudo.ends_with("sudo") {
|
||||
aur_update
|
||||
.arg(&self.executable)
|
||||
.arg("-Au")
|
||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
aur_update.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg(&self.executable)
|
||||
.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
aur_update.check_run()?;
|
||||
} else {
|
||||
println!("Aura requires sudo installed to work with AUR packages")
|
||||
}
|
||||
|
||||
let mut pacman_update = ctx.run_type().execute(&self.sudo);
|
||||
pacman_update
|
||||
.arg(&self.executable)
|
||||
.arg("-Syu")
|
||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||
if ctx.config().yes(Step::System) {
|
||||
pacman_update.arg("--noconfirm");
|
||||
}
|
||||
pacman_update.check_run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -345,29 +284,27 @@ pub fn get_arch_package_manager(ctx: &ExecutionContext) -> Option<Box<dyn ArchPa
|
||||
let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("pacman"));
|
||||
|
||||
match ctx.config().arch_package_manager() {
|
||||
config::ArchPackageManager::Autodetect => GarudaUpdate::get()
|
||||
config::ArchPackageManager::Autodetect => YayParu::get("paru", &pacman)
|
||||
.map(box_package_manager)
|
||||
.or_else(|| YayParu::get("paru", &pacman).map(box_package_manager))
|
||||
.or_else(|| YayParu::get("yay", &pacman).map(box_package_manager))
|
||||
.or_else(|| Trizen::get().map(box_package_manager))
|
||||
.or_else(|| Pikaur::get().map(box_package_manager))
|
||||
.or_else(|| Pamac::get().map(box_package_manager))
|
||||
.or_else(|| Pacman::get().map(box_package_manager))
|
||||
.or_else(|| Aura::get().map(box_package_manager)),
|
||||
config::ArchPackageManager::GarudaUpdate => GarudaUpdate::get().map(box_package_manager),
|
||||
.or_else(|| Pacman::get(ctx).map(box_package_manager))
|
||||
.or_else(|| Aura::get(ctx).map(box_package_manager)),
|
||||
config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager),
|
||||
config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager),
|
||||
config::ArchPackageManager::Pacman => Pacman::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Pacman => Pacman::get(ctx).map(box_package_manager),
|
||||
config::ArchPackageManager::Pikaur => Pikaur::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Pamac => Pamac::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Aura => Aura::get().map(box_package_manager),
|
||||
config::ArchPackageManager::Aura => Aura::get(ctx).map(box_package_manager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade_arch_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let package_manager =
|
||||
get_arch_package_manager(ctx).ok_or_else(|| eyre::Report::from(TopgradeError::FailedGettingPackageManager))?;
|
||||
get_arch_package_manager(ctx).ok_or_else(|| anyhow::Error::from(TopgradeError::FailedGettingPackageManager))?;
|
||||
package_manager.upgrade(ctx)
|
||||
}
|
||||
|
||||
@@ -384,7 +321,7 @@ pub fn show_pacnew() {
|
||||
.peekable();
|
||||
|
||||
if iter.peek().is_some() {
|
||||
println!("\n{}", t!("Pacman backup configuration files found:"));
|
||||
println!("\nPacman backup configuration files found:");
|
||||
|
||||
for entry in iter {
|
||||
println!("{}", entry.path().display());
|
||||
|
||||
@@ -1,36 +1,26 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::RunType;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use crate::Step;
|
||||
use color_eyre::eyre::Result;
|
||||
use crate::utils::require_option;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("DragonFly BSD Packages"));
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.status_checked()
|
||||
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("DrgaonFly BSD Packages");
|
||||
run_type
|
||||
.execute(sudo)
|
||||
.args(&["/usr/local/sbin/pkg", "upgrade"])
|
||||
.check_run()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
|
||||
print_separator(t!("DragonFly BSD Audit"));
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
if !Command::new(sudo)
|
||||
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
println!(t!(
|
||||
"The package audit was successful, but vulnerable packages still remain on the system"
|
||||
));
|
||||
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
|
||||
if let Some(sudo) = sudo {
|
||||
println!();
|
||||
Command::new(sudo)
|
||||
.args(&["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::RunType;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use crate::Step;
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use crate::utils::require_option;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("FreeBSD Update"));
|
||||
ctx.run_type()
|
||||
pub fn upgrade_freebsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("FreeBSD Update");
|
||||
run_type
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/freebsd-update", "fetch", "install"])
|
||||
.status_checked()
|
||||
.args(&["/usr/sbin/freebsd-update", "fetch", "install"])
|
||||
.check_run()
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("FreeBSD Packages"));
|
||||
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
|
||||
let sudo = require_option(sudo, String::from("No sudo detected"))?;
|
||||
print_separator("FreeBSD Packages");
|
||||
run_type.execute(sudo).args(&["/usr/sbin/pkg", "upgrade"]).check_run()
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
|
||||
command.args(["/usr/sbin/pkg", "upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
command.arg("-y");
|
||||
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> {
|
||||
if let Some(sudo) = sudo {
|
||||
println!();
|
||||
Command::new(sudo)
|
||||
.args(&["/usr/sbin/pkg", "audit", "-Fr"])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
}
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
|
||||
print_separator(t!("FreeBSD Audit"));
|
||||
|
||||
Command::new(sudo)
|
||||
.args(["/usr/sbin/pkg", "audit", "-Fr"])
|
||||
.status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +1,77 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::terminal::{print_separator, prompt_yesno};
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use crate::{utils::require, Step};
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use std::collections::HashSet;
|
||||
use crate::{error::TopgradeError, utils::require, Step};
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("port")?;
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
|
||||
let sudo = ctx.sudo().as_ref().unwrap();
|
||||
print_separator("MacPorts");
|
||||
ctx.run_type().execute(sudo).args(&["port", "selfupdate"]).check_run()?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["port", "selfupdate"])
|
||||
.status_checked()?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["port", "-u", "upgrade", "outdated"])
|
||||
.status_checked()?;
|
||||
.args(&["port", "-u", "upgrade", "outdated"])
|
||||
.check_run()?;
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["port", "-N", "reclaim"])
|
||||
.status_checked()?;
|
||||
.args(&["port", "-N", "reclaim"])
|
||||
.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_mas(run_type: RunType) -> Result<()> {
|
||||
let mas = require("mas")?;
|
||||
print_separator(t!("macOS App Store"));
|
||||
print_separator("macOS App Store");
|
||||
|
||||
ctx.run_type().execute(mas).arg("upgrade").status_checked()
|
||||
run_type.execute(mas).arg("upgrade").check_run()
|
||||
}
|
||||
|
||||
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator(t!("macOS system update"));
|
||||
print_separator("macOS system update");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
|
||||
let should_ask = !(ctx.config().yes(Step::System)) || (ctx.config().dry_run());
|
||||
if should_ask {
|
||||
println!("{}", t!("Finding available software"));
|
||||
println!("Finding available software");
|
||||
if system_update_available()? {
|
||||
let answer = prompt_yesno(t!("A system update is available. Do you wish to install it?").as_ref())?;
|
||||
let answer = prompt_yesno("A system update is available. Do you wish to install it?")?;
|
||||
if !answer {
|
||||
return Ok(());
|
||||
}
|
||||
println!();
|
||||
} else {
|
||||
println!("{}", t!("No new software available."));
|
||||
println!("No new software available.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute("softwareupdate");
|
||||
command.args(["--install", "--all"]);
|
||||
command.args(&["--install", "--all"]);
|
||||
|
||||
if should_ask {
|
||||
command.arg("--no-scan");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
|
||||
fn system_update_available() -> Result<bool> {
|
||||
let output = Command::new("softwareupdate").arg("--list").output_checked_utf8()?;
|
||||
|
||||
let output = Command::new("softwareupdate").arg("--list").output()?;
|
||||
debug!("{:?}", output);
|
||||
|
||||
Ok(!output.stderr.contains("No new software available"))
|
||||
let status = output.status;
|
||||
if !status.success() {
|
||||
return Err(TopgradeError::ProcessFailed(status).into());
|
||||
}
|
||||
let string_output = String::from_utf8(output.stderr)?;
|
||||
debug!("{:?}", string_output);
|
||||
Ok(!string_output.contains("No new software available"))
|
||||
}
|
||||
|
||||
pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -83,162 +81,15 @@ pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
for application in (fs::read_dir("/Applications")?).flatten() {
|
||||
let probe = Command::new(&sparkle)
|
||||
.args(["--probe", "--application"])
|
||||
.args(&["--probe", "--application"])
|
||||
.arg(application.path())
|
||||
.output_checked_utf8();
|
||||
.check_output();
|
||||
if probe.is_ok() {
|
||||
let mut command = ctx.run_type().execute(&sparkle);
|
||||
command.args(["bundle", "--check-immediately", "--application"]);
|
||||
command.args(&["bundle", "--check-immediately", "--application"]);
|
||||
command.arg(application.path());
|
||||
command.status_checked()?;
|
||||
command.spawn()?.wait()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
print_separator("Xcodes");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.config().dry_run());
|
||||
|
||||
let releases = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args(["update"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let releases_installed: Vec<String> = releases
|
||||
.lines()
|
||||
.filter(|r| r.contains("(Installed)"))
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if releases_installed.is_empty() {
|
||||
println!("{}", t!("No Xcode releases installed."));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (installed_gm, installed_beta, installed_regular) =
|
||||
releases_installed
|
||||
.iter()
|
||||
.fold((false, false, false), |(gm, beta, regular), release| {
|
||||
(
|
||||
gm || release.contains("GM") || release.contains("Release Candidate"),
|
||||
beta || release.contains("Beta"),
|
||||
regular
|
||||
|| !(release.contains("GM")
|
||||
|| release.contains("Release Candidate")
|
||||
|| release.contains("Beta")),
|
||||
)
|
||||
});
|
||||
|
||||
let releases_gm = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("GM").count() > 0 || r.matches("Release Candidate").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_beta = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("Beta").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_regular = releases
|
||||
.lines()
|
||||
.filter(|&r| {
|
||||
r.matches("GM").count() == 0
|
||||
&& r.matches("Release Candidate").count() == 0
|
||||
&& r.matches("Beta").count() == 0
|
||||
})
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if installed_gm {
|
||||
process_xcodes_releases(releases_gm, should_ask, ctx)?;
|
||||
}
|
||||
if installed_beta {
|
||||
process_xcodes_releases(releases_beta, should_ask, ctx)?;
|
||||
}
|
||||
if installed_regular {
|
||||
process_xcodes_releases(releases_regular, should_ask, ctx)?;
|
||||
}
|
||||
|
||||
let releases_new = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args(["list"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let releases_gm_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)") && (release.contains("GM") || release.contains("Release Candidate"))
|
||||
})
|
||||
.collect();
|
||||
let releases_beta_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| release.contains("(Installed)") && release.contains("Beta"))
|
||||
.collect();
|
||||
let releases_regular_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)")
|
||||
&& !(release.contains("GM") || release.contains("Release Candidate") || release.contains("Beta"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
for releases_new_installed in [
|
||||
releases_gm_new_installed,
|
||||
releases_beta_new_installed,
|
||||
releases_regular_new_installed,
|
||||
] {
|
||||
if should_ask && releases_new_installed.len() == 2 {
|
||||
let answer_uninstall =
|
||||
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
|
||||
if answer_uninstall {
|
||||
let _ = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args([
|
||||
"uninstall",
|
||||
releases_new_installed.iter().next().cloned().unwrap_or_default(),
|
||||
])
|
||||
.status_checked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
|
||||
if releases_filtered
|
||||
.last()
|
||||
.map(|s| !s.contains("(Installed)"))
|
||||
.unwrap_or(true)
|
||||
&& !releases_filtered.is_empty()
|
||||
{
|
||||
println!(
|
||||
"{} {}",
|
||||
t!("New Xcode release detected:"),
|
||||
releases_filtered.last().cloned().unwrap_or_default()
|
||||
);
|
||||
if should_ask {
|
||||
let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
|
||||
if answer_install {
|
||||
let _ = ctx
|
||||
.run_type()
|
||||
.execute(xcodes)
|
||||
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
|
||||
.status_checked();
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ pub mod freebsd;
|
||||
pub mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
pub mod openbsd;
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{get_require_sudo_string, require_option};
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("OpenBSD Update"));
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/sysupgrade", "-n"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
print_separator(t!("OpenBSD Packages"));
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/pkg_delete", "-ac"])
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/pkg_add", "-u"])
|
||||
.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
6
src/steps/os/os_release/anarchy
Normal file
6
src/steps/os/os_release/anarchy
Normal file
@@ -0,0 +1,6 @@
|
||||
NAME="Anarchy Linux"
|
||||
PRETTY_NAME="Anarchy Linux"
|
||||
ID=anarchy
|
||||
ID_LIKE=anarchylinux
|
||||
ANSI_COLOR="0;36"
|
||||
HOME_URL="https://anarchylinux.org/"
|
||||
10
src/steps/os/os_release/antergos
Normal file
10
src/steps/os/os_release/antergos
Normal file
@@ -0,0 +1,10 @@
|
||||
NAME="Antergos Linux"
|
||||
VERSION="18.7-ISO-Rolling"
|
||||
ID="antergos"
|
||||
ID_LIKE="arch"
|
||||
PRETTY_NAME="Antergos Linux"
|
||||
CPE_NAME="cpe:/o:antergosproject:antergos:18.7"
|
||||
ANSI_COLOR="1;34;40"
|
||||
HOME_URL="antergos.com"
|
||||
SUPPORT_URL="forum.antergos.com"
|
||||
BUG_REPORT_URL="@antergos"
|
||||
@@ -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/"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
PRETTY_NAME="Deepin 20.9"
|
||||
NAME="Deepin"
|
||||
VERSION_ID="20.9"
|
||||
VERSION="20.9"
|
||||
VERSION_CODENAME="apricot"
|
||||
ID=Deepin
|
||||
HOME_URL="https://www.deepin.org/"
|
||||
BUG_REPORT_URL="https://bbs.deepin.org/"
|
||||
@@ -1,22 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240415.0 (IoT Edition)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240415.0 (IoT Edition)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
HOME_URL="https://fedoraproject.org/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f39/system-administrators-guide/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="IoT Edition"
|
||||
VARIANT_ID=iot
|
||||
OSTREE_VERSION='39.20240415.0'
|
||||
@@ -1,23 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240105.0 (Kinoite)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://kinoite.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=kinoite
|
||||
OSTREE_VERSION='39.20240105.0'
|
||||
@@ -1,22 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Onyx)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Onyx)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/onyx/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Onyx"
|
||||
VARIANT_ID=onyx
|
||||
@@ -1,22 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Sericea)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Sericea)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/sericea/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Sericea"
|
||||
VARIANT_ID=sericea
|
||||
@@ -1,22 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Silverblue)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://silverblue.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Silverblue"
|
||||
VARIANT_ID=silverblue
|
||||
@@ -1,23 +0,0 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="40.20240426.0 (Sway Atomic)"
|
||||
ID=fedora
|
||||
VERSION_ID=40
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f40"
|
||||
PRETTY_NAME="Fedora Linux 40.20240426.0 (Sway Atomic)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:40"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/atomic-desktops/sway/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=40
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=40
|
||||
SUPPORT_END=2025-05-13
|
||||
VARIANT="Sway Atomic"
|
||||
VARIANT_ID=sway-atomic
|
||||
OSTREE_VERSION='40.20240426.0'
|
||||
@@ -1,6 +0,0 @@
|
||||
ID="funtoo"
|
||||
NAME="Funtoo"
|
||||
PRETTY_NAME="Funtoo Linux"
|
||||
ANSI_COLOR="0;34"
|
||||
HOME_URL="https://www.funtoo.org"
|
||||
BUG_REPORT_URL="https://bugs.funtoo.org"
|
||||
@@ -1,8 +0,0 @@
|
||||
ID=nilrt
|
||||
NAME="NI Linux Real-Time"
|
||||
VERSION="10.0 (kirkstone)"
|
||||
VERSION_ID=10.0
|
||||
PRETTY_NAME="NI Linux Real-Time 10.0 (kirkstone)"
|
||||
DISTRO_CODENAME="kirkstone"
|
||||
BUILD_ID="23.8.0f153-x64"
|
||||
VERSION_CODENAME="kirkstone"
|
||||
@@ -1,23 +0,0 @@
|
||||
NAME="Nobara Linux"
|
||||
VERSION="39 (GNOME Edition)"
|
||||
ID=nobara
|
||||
ID_LIKE="rhel centos fedora"
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Nobara Linux 39 (GNOME Edition)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=nobara-logo-icon
|
||||
CPE_NAME="cpe:/o:nobaraproject:nobara:39"
|
||||
DEFAULT_HOSTNAME="nobara"
|
||||
HOME_URL="https://nobaraproject.org/"
|
||||
DOCUMENTATION_URL="https://www.nobaraproject.org/"
|
||||
SUPPORT_URL="https://www.nobaraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/gloriouseggroll/nobara-images"
|
||||
REDHAT_BUGZILLA_PRODUCT="Nobara"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Nobara"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="GNOME Edition"
|
||||
VARIANT_ID=gnome
|
||||
@@ -1,9 +0,0 @@
|
||||
NAME="PCLinuxOS"
|
||||
VERSION="2022"
|
||||
ID=pclinuxos
|
||||
VERSION_ID=2022
|
||||
ID_LIKE="mandriva"
|
||||
PRETTY_NAME="PCLinuxOS 2022"
|
||||
ANSI_COLOR="1;37"
|
||||
HOME_URL="http://www.pclinuxos.com/"
|
||||
SUPPORT_URL="http://www.pclinuxos.com/"
|
||||
@@ -1,11 +0,0 @@
|
||||
NAME="Solus"
|
||||
VERSION="4.4"
|
||||
ID="solus"
|
||||
VERSION_CODENAME=harmony
|
||||
VERSION_ID="4.4"
|
||||
PRETTY_NAME="Solus 4.4 Harmony"
|
||||
ANSI_COLOR="1;34"
|
||||
HOME_URL="https://getsol.us"
|
||||
SUPPORT_URL="https://help.getsol.us/docs/user/contributing/getting-involved"
|
||||
BUG_REPORT_URL="https://dev.getsol.us/"
|
||||
LOGO="distributor-logo-solus"
|
||||
@@ -1,12 +0,0 @@
|
||||
PRETTY_NAME="VanillaOS 22.10 all"
|
||||
NAME="VanillaOS"
|
||||
VERSION_ID="22.10"
|
||||
VERSION="22.10 all"
|
||||
VERSION_CODENAME="kinetic"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
HOME_URL="https://github.com/vanilla-os"
|
||||
SUPPORT_URL="https://github.com/vanilla-os"
|
||||
BUG_REPORT_URL="https://github.com/vanilla-os"
|
||||
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
|
||||
UBUNTU_CODENAME="kinetic"
|
||||
@@ -1,5 +0,0 @@
|
||||
ID=wolfi
|
||||
NAME="Wolfi"
|
||||
PRETTY_NAME="Wolfi"
|
||||
VERSION_ID="20230201"
|
||||
HOME_URL="https://wolfi.dev"
|
||||
@@ -1,51 +1,33 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Component;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env::var, path::Path};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::{Step, HOME_DIR};
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::{CommandExt, Executor, ExecutorExitStatus, RunType};
|
||||
use crate::terminal::print_separator;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use crate::utils::require_option;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::Step;
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use home;
|
||||
use ini::Ini;
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::unistd::Uid;
|
||||
use rust_i18n::t;
|
||||
use semver::Version;
|
||||
use tracing::debug;
|
||||
use log::debug;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env, path::Path};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::linux::Distribution;
|
||||
use crate::error::SkipStep;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use crate::executor::Executor;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use crate::executor::RunType;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{get_require_sudo_string, require, require_option, PathExt};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
const INTEL_BREW: &str = "/usr/local/bin/brew";
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
const ARM_BREW: &str = "/opt/homebrew/bin/brew";
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub enum BrewVariant {
|
||||
Path,
|
||||
MacIntel,
|
||||
MacArm,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl BrewVariant {
|
||||
fn binary_name(self) -> &'static str {
|
||||
match self {
|
||||
@@ -95,148 +77,92 @@ impl BrewVariant {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_fisher(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
|
||||
let fish = require("fish")?;
|
||||
|
||||
Command::new(&fish)
|
||||
.args(["-c", "type -t fisher"])
|
||||
.output_checked_utf8()
|
||||
.map(|_| ())
|
||||
.map_err(|_| SkipStep(t!("`fisher` is not defined in `fish`").to_string()))?;
|
||||
|
||||
Command::new(&fish)
|
||||
.args(["-c", "echo \"$__fish_config_dir/fish_plugins\""])
|
||||
.output_checked_utf8()
|
||||
.and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ()))
|
||||
.map_err(|err| SkipStep(t!("`fish_plugins` path doesn't exist: {err}", err = err).to_string()))?;
|
||||
|
||||
Command::new(&fish)
|
||||
.args(["-c", "fish_update_completions"])
|
||||
.output_checked_utf8()
|
||||
.map(|_| ())
|
||||
.map_err(|_| SkipStep(t!("`fish_update_completions` is not available").to_string()))?;
|
||||
if env::var("fisher_path").is_err() {
|
||||
base_dirs
|
||||
.home_dir()
|
||||
.join(".config/fish/functions/fisher.fish")
|
||||
.require()?;
|
||||
}
|
||||
|
||||
print_separator("Fisher");
|
||||
|
||||
let version_str = ctx
|
||||
.run_type()
|
||||
let version_str = run_type
|
||||
.execute(&fish)
|
||||
.args(["-c", "fisher --version"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
.check_output()?;
|
||||
debug!("Fisher version: {}", version_str);
|
||||
|
||||
if version_str.starts_with("fisher version 3.") {
|
||||
// v3 - see https://github.com/topgrade-rs/topgrade/pull/37#issuecomment-1283844506
|
||||
ctx.run_type().execute(&fish).args(["-c", "fisher"]).status_checked()
|
||||
run_type.execute(&fish).args(["-c", "fisher"]).check_run()
|
||||
} else {
|
||||
// v4
|
||||
ctx.run_type()
|
||||
.execute(&fish)
|
||||
.args(["-c", "fisher update"])
|
||||
.status_checked()
|
||||
run_type.execute(&fish).args(["-c", "fisher update"]).check_run()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
|
||||
HOME_DIR.join(".bash_it").require()?;
|
||||
ctx.base_dirs().home_dir().join(".bash_it").require()?;
|
||||
|
||||
print_separator("Bash-it");
|
||||
|
||||
ctx.run_type()
|
||||
.execute("bash")
|
||||
.args(["-lic", &format!("bash-it update {}", ctx.config().bashit_branch())])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_oh_my_bash(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("bash")?;
|
||||
let oh_my_bash = var("OSH")
|
||||
// default to `~/.oh-my-bash`
|
||||
.unwrap_or(
|
||||
HOME_DIR
|
||||
.join(".oh-my-bash")
|
||||
.to_str()
|
||||
.expect("should be UTF-8 encoded")
|
||||
.to_string(),
|
||||
)
|
||||
.require()?;
|
||||
|
||||
print_separator("oh-my-bash");
|
||||
|
||||
let mut update_script = oh_my_bash;
|
||||
update_script.push_str("/tools/upgrade.sh");
|
||||
|
||||
ctx.run_type().execute("bash").arg(update_script).status_checked()
|
||||
.check_run()
|
||||
}
|
||||
|
||||
pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
|
||||
let fish = require("fish")?;
|
||||
HOME_DIR.join(".local/share/omf/pkg/omf/functions/omf.fish").require()?;
|
||||
ctx.base_dirs()
|
||||
.home_dir()
|
||||
.join(".local/share/omf/pkg/omf/functions/omf.fish")
|
||||
.require()?;
|
||||
|
||||
print_separator("oh-my-fish");
|
||||
|
||||
ctx.run_type().execute(fish).args(["-c", "omf update"]).status_checked()
|
||||
ctx.run_type().execute(&fish).args(["-c", "omf update"]).check_run()
|
||||
}
|
||||
|
||||
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pkgin = require("pkgin")?;
|
||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
||||
|
||||
print_separator("Pkgin");
|
||||
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap());
|
||||
command.arg(&pkgin).arg("update");
|
||||
if ctx.config().yes(Step::Pkgin) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.status_checked()?;
|
||||
command.check_run()?;
|
||||
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap());
|
||||
command.arg(&pkgin).arg("upgrade");
|
||||
if ctx.config().yes(Step::Pkgin) {
|
||||
command.arg("-y");
|
||||
}
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
|
||||
pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
|
||||
let fish = require("fish")?;
|
||||
HOME_DIR
|
||||
ctx.base_dirs()
|
||||
.home_dir()
|
||||
.join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish")
|
||||
.require()?;
|
||||
|
||||
print_separator("fish-plug");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(fish)
|
||||
.args(["-c", "plug update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
/// Upgrades `fundle` and `fundle` plugins.
|
||||
///
|
||||
/// `fundle` is a package manager for the Fish shell.
|
||||
///
|
||||
/// See: <https://github.com/danhper/fundle>
|
||||
pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
|
||||
let fish = require("fish")?;
|
||||
HOME_DIR.join(".config/fish/fundle").require()?;
|
||||
|
||||
print_separator("fundle");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(fish)
|
||||
.args(["-c", "fundle self-update && fundle update"])
|
||||
.status_checked()
|
||||
ctx.run_type().execute(&fish).args(["-c", "plug update"]).check_run()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "macos")))]
|
||||
pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||
let gdbus = require("gdbus")?;
|
||||
require_option(
|
||||
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
||||
t!("Desktop doest not appear to be gnome").to_string(),
|
||||
env::var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
||||
"Desktop doest not appear to be gnome".to_string(),
|
||||
)?;
|
||||
let output = Command::new("gdbus")
|
||||
.args([
|
||||
@@ -249,14 +175,14 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||
"--method",
|
||||
"org.freedesktop.DBus.ListActivatableNames",
|
||||
])
|
||||
.output_checked_utf8()?;
|
||||
.check_output()?;
|
||||
|
||||
debug!("Checking for gnome extensions: {}", output);
|
||||
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
||||
return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
|
||||
if !output.contains("org.gnome.Shell.Extensions") {
|
||||
return Err(SkipStep(String::from("Gnome shell extensions are unregistered in DBus")).into());
|
||||
}
|
||||
|
||||
print_separator(t!("Gnome Shell extensions"));
|
||||
print_separator("Gnome Shell extensions");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(gdbus)
|
||||
@@ -270,27 +196,9 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||
"--method",
|
||||
"org.gnome.Shell.Extensions.CheckForUpdates",
|
||||
])
|
||||
.status_checked()
|
||||
.check_run()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn brew_linux_sudo_uid() -> Option<u32> {
|
||||
let linuxbrew_directory = "/home/linuxbrew/.linuxbrew";
|
||||
if let Ok(metadata) = std::fs::metadata(linuxbrew_directory) {
|
||||
let owner_id = metadata.uid();
|
||||
let current_id = Uid::effective();
|
||||
// print debug these two values
|
||||
debug!("linuxbrew_directory owner_id: {}, current_id: {}", owner_id, current_id);
|
||||
return if owner_id == current_id.as_raw() {
|
||||
None // no need for sudo if linuxbrew is owned by the current user
|
||||
} else {
|
||||
Some(owner_id) // otherwise use sudo to run brew as the owner
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
|
||||
#[allow(unused_variables)]
|
||||
let binary_name = require(variant.binary_name())?;
|
||||
@@ -298,57 +206,25 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
|
||||
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
|
||||
return Err(SkipStep("Not a custom brew for macOS".to_string()).into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let sudo_uid = brew_linux_sudo_uid();
|
||||
// if brew is owned by another user, execute "sudo -Hu <uid> brew update"
|
||||
if let Some(user_id) = sudo_uid {
|
||||
let uid = nix::unistd::Uid::from_raw(user_id);
|
||||
let user = nix::unistd::User::from_uid(uid)
|
||||
.expect("failed to call getpwuid()")
|
||||
.expect("this user should exist");
|
||||
|
||||
let sudo_as_user = t!("sudo as user '{user}'", user = user.name);
|
||||
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
|
||||
|
||||
let sudo = crate::utils::require_option(ctx.sudo().as_ref(), crate::utils::get_require_sudo_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.current_dir("/tmp") // brew needs a writable current directory
|
||||
.args([
|
||||
"--set-home",
|
||||
&format!("--user={}", user.name),
|
||||
&format!("{}", binary_name.to_string_lossy()),
|
||||
"update",
|
||||
])
|
||||
.status_checked()?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
print_separator(variant.step_title());
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
variant.execute(run_type).arg("update").status_checked()?;
|
||||
|
||||
let mut command = variant.execute(run_type);
|
||||
command.args(["upgrade", "--formula"]);
|
||||
|
||||
if ctx.config().brew_fetch_head() {
|
||||
command.arg("--fetch-HEAD");
|
||||
}
|
||||
|
||||
command.status_checked()?;
|
||||
variant.execute(run_type).arg("update").check_run()?;
|
||||
variant
|
||||
.execute(run_type)
|
||||
.args(["upgrade", "--ignore-pinned", "--formula"])
|
||||
.check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
variant.execute(run_type).arg("cleanup").status_checked()?;
|
||||
variant.execute(run_type).arg("cleanup").check_run()?;
|
||||
}
|
||||
|
||||
if ctx.config().brew_autoremove() {
|
||||
variant.execute(run_type).arg("autoremove").status_checked()?;
|
||||
variant.execute(run_type).arg("autoremove").check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -358,41 +234,35 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
||||
pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
|
||||
let binary_name = require(variant.binary_name())?;
|
||||
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
|
||||
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
|
||||
return Err(SkipStep("Not a custom brew for macOS".to_string()).into());
|
||||
}
|
||||
print_separator(format!("{} - Cask", variant.step_title()));
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
let cask_upgrade_exists = variant
|
||||
.execute(RunType::Wet)
|
||||
.args(["--repository", "buo/cask-upgrade"])
|
||||
.output_checked_utf8()
|
||||
.map(|p| Path::new(p.stdout.trim()).exists())?;
|
||||
.args(&["--repository", "buo/cask-upgrade"])
|
||||
.check_output()
|
||||
.map(|p| Path::new(p.trim()).exists())?;
|
||||
|
||||
let mut brew_args = vec![];
|
||||
|
||||
if cask_upgrade_exists {
|
||||
brew_args.extend(["cu", "-y"]);
|
||||
brew_args.extend(&["cu", "-y"]);
|
||||
if ctx.config().brew_cask_greedy() {
|
||||
brew_args.push("-a");
|
||||
}
|
||||
} else {
|
||||
brew_args.extend(["upgrade", "--cask"]);
|
||||
brew_args.extend(&["upgrade", "--cask"]);
|
||||
if ctx.config().brew_cask_greedy() {
|
||||
brew_args.push("--greedy");
|
||||
}
|
||||
if ctx.config().brew_greedy_latest() {
|
||||
brew_args.push("--greedy-latest");
|
||||
}
|
||||
if ctx.config().brew_greedy_auto_updates() {
|
||||
brew_args.push("--greedy-auto-updates");
|
||||
}
|
||||
}
|
||||
|
||||
variant.execute(run_type).args(&brew_args).status_checked()?;
|
||||
variant.execute(run_type).args(&brew_args).check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
variant.execute(run_type).arg("cleanup").status_checked()?;
|
||||
variant.execute(run_type).arg("cleanup").check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -403,7 +273,7 @@ pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
let output = Command::new(&guix).arg("pull").output_checked_utf8();
|
||||
let output = Command::new(&guix).arg("pull").check_output();
|
||||
debug!("guix pull output: {:?}", output);
|
||||
let should_upgrade = output.is_ok();
|
||||
debug!("Can Upgrade Guix: {:?}", should_upgrade);
|
||||
@@ -411,16 +281,15 @@ pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Guix");
|
||||
|
||||
if should_upgrade {
|
||||
return run_type.execute(&guix).args(["package", "-u"]).status_checked();
|
||||
return run_type.execute(&guix).args(["package", "-u"]).check_run();
|
||||
}
|
||||
Err(SkipStep(t!("Guix Pull Failed, Skipping").to_string()).into())
|
||||
Err(SkipStep(String::from("Guix Pull Failed, Skipping")).into())
|
||||
}
|
||||
|
||||
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
let nix = require("nix")?;
|
||||
let nix_channel = require("nix-channel")?;
|
||||
let nix_env = require("nix-env")?;
|
||||
// TODO: Is None possible here?
|
||||
let profile_path = match home::home_dir() {
|
||||
Some(home) => Path::new(&home).join(".nix-profile"),
|
||||
None => Path::new("/nix/var/nix/profiles/per-user/default").into(),
|
||||
@@ -428,173 +297,56 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("nix profile: {:?}", profile_path);
|
||||
let manifest_json_path = profile_path.join("manifest.json");
|
||||
|
||||
let output = Command::new(&nix_env).args(["--query", "nix"]).check_output();
|
||||
debug!("nix-env output: {:?}", output);
|
||||
let should_self_upgrade = output.is_ok();
|
||||
|
||||
print_separator("Nix");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if require("darwin-rebuild").is_ok() {
|
||||
return Err(
|
||||
SkipStep(t!("Nix-darwin on macOS must be upgraded via darwin-rebuild switch").to_string()).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let run_type = ctx.run_type();
|
||||
run_type.execute(nix_channel).arg("--update").status_checked()?;
|
||||
|
||||
let mut get_version_cmd = ctx.run_type().execute(&nix);
|
||||
get_version_cmd.arg("--version");
|
||||
let get_version_cmd_output = get_version_cmd.output_checked_utf8()?;
|
||||
let get_version_cmd_first_line_stdout = get_version_cmd_output
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
.expect("nix --version gives an empty output");
|
||||
let splitted: Vec<&str> = get_version_cmd_first_line_stdout.split_whitespace().collect();
|
||||
let version = if splitted.len() >= 3 {
|
||||
Version::parse(splitted[2]).expect("invalid version")
|
||||
} else {
|
||||
panic!("nix --version output format changed, file an issue to Topgrade!")
|
||||
};
|
||||
|
||||
debug!("Nix version: {:?}", version);
|
||||
|
||||
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages
|
||||
let packages = if version >= Version::new(2, 21, 0) {
|
||||
vec!["--all", "--impure"]
|
||||
} else {
|
||||
vec![".*"]
|
||||
};
|
||||
|
||||
if Path::new(&manifest_json_path).exists() {
|
||||
run_type
|
||||
.execute(nix)
|
||||
.args(nix_args())
|
||||
.arg("profile")
|
||||
.arg("upgrade")
|
||||
.args(&packages)
|
||||
.arg("--verbose")
|
||||
.status_checked()
|
||||
} else {
|
||||
let mut command = run_type.execute(nix_env);
|
||||
command.arg("--upgrade");
|
||||
if let Some(args) = ctx.config().nix_env_arguments() {
|
||||
command.args(args.split_whitespace());
|
||||
};
|
||||
command.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let nix = require("nix")?;
|
||||
|
||||
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
|
||||
#[allow(unused_mut)]
|
||||
let mut should_self_upgrade = cfg!(target_os = "macos");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// We can't use `nix upgrade-nix` on NixOS.
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
should_self_upgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
if !should_self_upgrade {
|
||||
return Err(SkipStep(t!("`nix upgrade-nix` can only be used on macOS or non-NixOS Linux").to_string()).into());
|
||||
}
|
||||
|
||||
if nix_profile_dir(&nix)?.is_none() {
|
||||
return Err(
|
||||
SkipStep(t!("`nix upgrade-nix` cannot be run when Nix is installed in a profile").to_string()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
print_separator(t!("Nix (self-upgrade)"));
|
||||
|
||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||
debug!("Multi user nix: {}", multi_user);
|
||||
|
||||
let nix_args = nix_args();
|
||||
if multi_user {
|
||||
ctx.execute_elevated(&nix, true)?
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()
|
||||
} else {
|
||||
ctx.run_type()
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use super::linux::Distribution;
|
||||
|
||||
/// If we try to `nix upgrade-nix` but Nix is installed with `nix profile`, we'll get a `does not
|
||||
/// appear to be part of a Nix profile` error.
|
||||
///
|
||||
/// We duplicate some of the `nix` logic here to avoid this.
|
||||
/// See: <https://github.com/NixOS/nix/blob/f0180487a0e4c0091b46cb1469c44144f5400240/src/nix/upgrade-nix.cc#L102-L139>
|
||||
///
|
||||
/// See: <https://github.com/NixOS/nix/issues/5473>
|
||||
fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
||||
// NOTE: `nix` uses the location of the `nix-env` binary for this but we're using the `nix`
|
||||
// binary; should be the same.
|
||||
let nix_bin_dir = nix.parent();
|
||||
if nix_bin_dir.and_then(|p| p.file_name()) != Some(OsStr::new("bin")) {
|
||||
debug!("Nix is not installed in a `bin` directory: {nix_bin_dir:?}");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let nix_dir = nix_bin_dir
|
||||
.and_then(|bin_dir| bin_dir.parent())
|
||||
.ok_or_else(|| eyre!("Unable to find Nix install directory from Nix binary {nix:?}"))?;
|
||||
|
||||
debug!("Found Nix in {nix_dir:?}");
|
||||
|
||||
let mut profile_dir = nix_dir.to_path_buf();
|
||||
while profile_dir.is_symlink() {
|
||||
profile_dir = profile_dir
|
||||
.parent()
|
||||
.ok_or_else(|| eyre!("Path has no parent: {profile_dir:?}"))?
|
||||
.join(
|
||||
profile_dir
|
||||
.read_link()
|
||||
.wrap_err_with(|| format!("Failed to read symlink {profile_dir:?}"))?,
|
||||
);
|
||||
|
||||
// NOTE: `nix` uses a hand-rolled canonicalize function, Rust just uses `realpath`.
|
||||
if profile_dir
|
||||
.canonicalize()
|
||||
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?
|
||||
.components()
|
||||
.any(|component| component == Component::Normal(OsStr::new("profiles")))
|
||||
{
|
||||
break;
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
return Err(SkipStep(String::from("Nix on NixOS must be upgraded via nixos-rebuild switch")).into());
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Found Nix profile {profile_dir:?}");
|
||||
let user_env = profile_dir
|
||||
.canonicalize()
|
||||
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Ok(..) = require("darwin-rebuild") {
|
||||
return Err(SkipStep(String::from(
|
||||
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(
|
||||
if user_env
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.ends_with("user-environment"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(profile_dir)
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
if should_self_upgrade {
|
||||
if multi_user {
|
||||
ctx.execute_elevated(&nix, true)?.arg("upgrade-nix").check_run()?;
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
run_type.execute(&nix).arg("upgrade-nix").check_run()?;
|
||||
}
|
||||
}
|
||||
|
||||
fn nix_args() -> [&'static str; 2] {
|
||||
["--extra-experimental-features", "nix-command"]
|
||||
run_type.execute(&nix_channel).arg("--update").check_run()?;
|
||||
|
||||
if std::path::Path::new(&manifest_json_path).exists() {
|
||||
run_type
|
||||
.execute(&nix)
|
||||
.arg("profile")
|
||||
.arg("upgrade")
|
||||
.arg(".*")
|
||||
.check_run()
|
||||
} else {
|
||||
run_type.execute(&nix_env).arg("--upgrade").check_run()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -602,95 +354,50 @@ pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("yadm");
|
||||
|
||||
ctx.run_type().execute(yadm).arg("pull").status_checked()
|
||||
ctx.run_type().execute(&yadm).arg("pull").check_run()
|
||||
}
|
||||
|
||||
pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_asdf(run_type: RunType) -> Result<()> {
|
||||
let asdf = require("asdf")?;
|
||||
|
||||
print_separator("asdf");
|
||||
ctx.run_type()
|
||||
.execute(&asdf)
|
||||
.arg("update")
|
||||
.status_checked_with_codes(&[42])?;
|
||||
let exit_status = run_type.execute(&asdf).arg("update").spawn()?.wait()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&asdf)
|
||||
.args(["plugin", "update", "--all"])
|
||||
.status_checked()
|
||||
if let ExecutorExitStatus::Wet(e) = exit_status {
|
||||
if !(e.success() || e.code().map(|c| c == 42).unwrap_or(false)) {
|
||||
return Err(TopgradeError::ProcessFailed(e).into());
|
||||
}
|
||||
}
|
||||
run_type.execute(&asdf).args(["plugin", "update", "--all"]).check_run()
|
||||
}
|
||||
|
||||
pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mise = require("mise")?;
|
||||
|
||||
print_separator("mise");
|
||||
|
||||
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&mise)
|
||||
.args(["plugins", "update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_home_manager(run_type: RunType) -> Result<()> {
|
||||
let home_manager = require("home-manager")?;
|
||||
|
||||
print_separator("home-manager");
|
||||
|
||||
let mut cmd = ctx.run_type().execute(home_manager);
|
||||
cmd.arg("switch");
|
||||
|
||||
if let Some(extra_args) = ctx.config().home_manager() {
|
||||
cmd.args(extra_args);
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
run_type.execute(&home_manager).arg("switch").check_run()
|
||||
}
|
||||
|
||||
pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_tldr(run_type: RunType) -> Result<()> {
|
||||
let tldr = require("tldr")?;
|
||||
|
||||
print_separator("TLDR");
|
||||
ctx.run_type().execute(tldr).arg("--update").status_checked()
|
||||
run_type.execute(&tldr).arg("--update").check_run()
|
||||
}
|
||||
|
||||
pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_pearl(run_type: RunType) -> Result<()> {
|
||||
let pearl = require("pearl")?;
|
||||
print_separator("pearl");
|
||||
|
||||
ctx.run_type().execute(pearl).arg("update").status_checked()
|
||||
run_type.execute(&pearl).arg("update").check_run()
|
||||
}
|
||||
|
||||
pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pyenv = require("pyenv")?;
|
||||
print_separator("pyenv");
|
||||
|
||||
let pyenv_dir = var("PYENV_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
|
||||
|
||||
if !pyenv_dir.exists() {
|
||||
return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
|
||||
}
|
||||
|
||||
if !pyenv_dir.join(".git").exists() {
|
||||
return Err(SkipStep(t!("pyenv is not a git repository").to_string()).into());
|
||||
}
|
||||
|
||||
if !pyenv_dir.join("plugins").join("pyenv-update").exists() {
|
||||
return Err(SkipStep(t!("pyenv-update plugin is not installed").to_string()).into());
|
||||
}
|
||||
|
||||
ctx.run_type().execute(pyenv).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Result<()> {
|
||||
let bash = require("bash")?;
|
||||
|
||||
let sdkman_init_path = var("SDKMAN_DIR")
|
||||
let sdkman_init_path = env::var("SDKMAN_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
||||
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman"))
|
||||
.join("bin")
|
||||
.join("sdkman-init.sh")
|
||||
.require()
|
||||
@@ -698,9 +405,9 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("SDKMAN!");
|
||||
|
||||
let sdkman_config_path = var("SDKMAN_DIR")
|
||||
let sdkman_config_path = env::var("SDKMAN_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
||||
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman"))
|
||||
.join("etc")
|
||||
.join("config")
|
||||
.require()?;
|
||||
@@ -713,78 +420,44 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
if selfupdate_enabled == "true" {
|
||||
let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path);
|
||||
ctx.run_type()
|
||||
run_type
|
||||
.execute(&bash)
|
||||
.args(["-c", cmd_selfupdate.as_str()])
|
||||
.status_checked()?;
|
||||
.check_run()?;
|
||||
}
|
||||
|
||||
let cmd_update = format!("source {} && sdk update", &sdkman_init_path);
|
||||
ctx.run_type()
|
||||
.execute(&bash)
|
||||
.args(["-c", cmd_update.as_str()])
|
||||
.status_checked()?;
|
||||
run_type.execute(&bash).args(["-c", cmd_update.as_str()]).check_run()?;
|
||||
|
||||
let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path);
|
||||
ctx.run_type()
|
||||
.execute(&bash)
|
||||
.args(["-c", cmd_upgrade.as_str()])
|
||||
.status_checked()?;
|
||||
run_type.execute(&bash).args(["-c", cmd_upgrade.as_str()]).check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
if cleanup {
|
||||
let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path);
|
||||
ctx.run_type()
|
||||
run_type
|
||||
.execute(&bash)
|
||||
.args(["-c", cmd_flush_archives.as_str()])
|
||||
.status_checked()?;
|
||||
.check_run()?;
|
||||
|
||||
let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path);
|
||||
ctx.run_type()
|
||||
run_type
|
||||
.execute(&bash)
|
||||
.args(["-c", cmd_flush_temp.as_str()])
|
||||
.status_checked()?;
|
||||
.check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
|
||||
let bun = require("bun")?;
|
||||
|
||||
print_separator(t!("Bun Packages"));
|
||||
print_separator("Bun");
|
||||
|
||||
let mut package_json: PathBuf = var("BUN_INSTALL")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
|
||||
package_json.push("install/global/package.json");
|
||||
|
||||
if !package_json.exists() {
|
||||
println!("{}", t!("No global packages installed"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
|
||||
ctx.run_type().execute(&bun).arg("upgrade").check_run()
|
||||
}
|
||||
|
||||
/// Update dotfiles with `rcm(7)`.
|
||||
///
|
||||
/// See: <https://github.com/thoughtbot/rcm>
|
||||
pub fn run_rcm(ctx: &ExecutionContext) -> Result<()> {
|
||||
let rcup = require("rcup")?;
|
||||
|
||||
print_separator("rcm");
|
||||
ctx.run_type().execute(rcup).arg("-v").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
|
||||
let maza = require("maza")?;
|
||||
|
||||
print_separator("maza");
|
||||
ctx.run_type().execute(maza).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn reboot() -> Result<()> {
|
||||
print!("{}", t!("Rebooting..."));
|
||||
|
||||
Command::new("sudo").arg("reboot").status_checked()
|
||||
pub fn reboot() {
|
||||
print!("Rebooting...");
|
||||
Command::new("sudo").arg("reboot").spawn().unwrap().wait().unwrap();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
use std::{ffi::OsStr, process::Command};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use tracing::debug;
|
||||
use anyhow::Result;
|
||||
use log::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::{CommandExt, RunType};
|
||||
use crate::terminal::{print_separator, print_warning};
|
||||
use crate::utils::{require, which};
|
||||
use crate::{error::SkipStep, steps::git::RepoStep};
|
||||
use crate::utils::require;
|
||||
use crate::{error::SkipStep, steps::git::Repositories};
|
||||
use crate::{powershell, Step};
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
||||
let choco = require("choco")?;
|
||||
@@ -19,22 +18,23 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Chocolatey");
|
||||
|
||||
let mut command = match ctx.sudo() {
|
||||
Some(sudo) => {
|
||||
let mut command = ctx.run_type().execute(sudo);
|
||||
command.arg(choco);
|
||||
command
|
||||
}
|
||||
None => ctx.run_type().execute(choco),
|
||||
};
|
||||
let mut cmd = &choco;
|
||||
let mut args = vec!["upgrade", "all"];
|
||||
|
||||
command.args(["upgrade", "all"]);
|
||||
if let Some(sudo) = ctx.sudo() {
|
||||
cmd = sudo;
|
||||
args.insert(0, "choco");
|
||||
}
|
||||
|
||||
let mut command = ctx.run_type().execute(&cmd);
|
||||
|
||||
command.args(&args);
|
||||
|
||||
if yes {
|
||||
command.arg("--yes");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
|
||||
pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -42,143 +42,57 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("winget");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(winget)
|
||||
.args(["upgrade", "--all"])
|
||||
.status_checked()
|
||||
if !ctx.config().enable_winget() {
|
||||
print_warning("Winget is disabled by default. Enable it by setting enable_winget=true in the [windows] section in the configuration.");
|
||||
return Err(SkipStep(String::from("Winget is disabled by default")).into());
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&winget).args(&["upgrade", "--all"]).check_run()
|
||||
}
|
||||
|
||||
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> {
|
||||
let scoop = require("scoop")?;
|
||||
|
||||
print_separator("Scoop");
|
||||
|
||||
ctx.run_type().execute(&scoop).args(["update"]).status_checked()?;
|
||||
ctx.run_type().execute(&scoop).args(["update", "*"]).status_checked()?;
|
||||
run_type.execute(&scoop).args(&["update"]).check_run()?;
|
||||
run_type.execute(&scoop).args(&["update", "*"]).check_run()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?;
|
||||
ctx.run_type()
|
||||
.execute(&scoop)
|
||||
.args(["cache", "rm", "-a"])
|
||||
.status_checked()?
|
||||
if cleanup {
|
||||
run_type.execute(&scoop).args(&["cleanup", "*"]).check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep(t!("WSL not installed").to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
|
||||
print_separator(t!("Update WSL"));
|
||||
|
||||
let mut wsl_command = ctx.run_type().execute(wsl);
|
||||
wsl_command.args(["--update"]);
|
||||
|
||||
if ctx.config().wsl_update_pre_release() {
|
||||
wsl_command.args(["--pre-release"]);
|
||||
}
|
||||
|
||||
if ctx.config().wsl_update_use_web_download() {
|
||||
wsl_command.args(["--web-download"]);
|
||||
}
|
||||
wsl_command.status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect if WSL is installed or not.
|
||||
///
|
||||
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
|
||||
/// versions of Windows (since windows 10 version 2004), this commmand is
|
||||
/// installed by default.
|
||||
///
|
||||
/// If the command is installed and the user hasn't installed any Linux distros
|
||||
/// on it, command `wsl -l` would print a help message and exit with failure, we
|
||||
/// use this to check whether WSL is install or not.
|
||||
fn is_wsl_installed() -> Result<bool> {
|
||||
if let Some(wsl) = which("wsl") {
|
||||
// Don't use `output_checked` as an execution failure log is not wanted
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let output = Command::new(wsl).arg("-l").output()?;
|
||||
let status = output.status;
|
||||
|
||||
if status.success() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
|
||||
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
|
||||
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"])
|
||||
.output_checked_utf8()
|
||||
.map_err(|_| SkipStep(t!("Could not find Topgrade installed in WSL").to_string()))?
|
||||
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
|
||||
let mut command = ctx.run_type().execute(wsl);
|
||||
|
||||
// The `arg` method automatically quotes its arguments.
|
||||
// This means we can't append additional arguments to `topgrade` in WSL
|
||||
// by calling `arg` successively.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// ```rust
|
||||
// command
|
||||
// .args(["-d", dist, "bash", "-c"])
|
||||
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
||||
// ```
|
||||
//
|
||||
// creates a command string like:
|
||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
|
||||
//
|
||||
// Adding the following:
|
||||
//
|
||||
// ```rust
|
||||
// command.arg("-v");
|
||||
// ```
|
||||
//
|
||||
// appends the next argument like so:
|
||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
|
||||
// which means `-v` isn't passed to `topgrade`.
|
||||
let mut args = String::new();
|
||||
if ctx.config().verbose() {
|
||||
args.push_str("-v");
|
||||
}
|
||||
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);
|
||||
command
|
||||
.args(["-d", dist, "bash", "-c"])
|
||||
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
|
||||
.args(&["-d", dist, "bash", "-c"])
|
||||
.arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade));
|
||||
|
||||
if ctx.config().yes(Step::Wsl) {
|
||||
command.arg("-y");
|
||||
}
|
||||
|
||||
command.status_checked()
|
||||
command.check_run()
|
||||
}
|
||||
|
||||
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep(t!("WSL not installed").to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
let wsl_distributions = get_wsl_distributions(&wsl)?;
|
||||
let mut ran = false;
|
||||
@@ -199,36 +113,33 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
if ran {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SkipStep(t!("Could not find Topgrade in any WSL disribution").to_string()).into())
|
||||
Err(SkipStep(String::from("Could not find Topgrade in any WSL disribution")).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = powershell::Powershell::windows_powershell();
|
||||
|
||||
print_separator(t!("Windows Update"));
|
||||
|
||||
if powershell.supports_windows_update() {
|
||||
println!("The installer will request to run as administrator, expect a prompt.");
|
||||
|
||||
powershell.windows_update(ctx)
|
||||
} else {
|
||||
print_warning(t!(
|
||||
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
|
||||
));
|
||||
|
||||
Err(SkipStep(t!("USOClient not supported.").to_string()).into())
|
||||
print_separator("Windows Update");
|
||||
return powershell.windows_update(ctx);
|
||||
}
|
||||
|
||||
let usoclient = require("UsoClient")?;
|
||||
|
||||
print_separator("Windows Update");
|
||||
println!("Running Windows Update. Check the control panel for progress.");
|
||||
ctx.run_type().execute(&usoclient).arg("ScanInstallWait").check_run()?;
|
||||
ctx.run_type().execute(&usoclient).arg("StartInstall").check_run()
|
||||
}
|
||||
|
||||
pub fn reboot() -> Result<()> {
|
||||
// If this works, it won't return, but if it doesn't work, it may return a useful error
|
||||
// message.
|
||||
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
|
||||
pub fn reboot() {
|
||||
Command::new("shutdown").args(&["/R", "/T", "0"]).spawn().ok();
|
||||
}
|
||||
|
||||
pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
|
||||
let startup_dir = crate::WINDOWS_DIRS
|
||||
pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> {
|
||||
let startup_dir = ctx
|
||||
.base_dirs()
|
||||
.data_dir()
|
||||
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
|
||||
for entry in std::fs::read_dir(&startup_dir)?.flatten() {
|
||||
@@ -237,7 +148,7 @@ pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
|
||||
if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) {
|
||||
debug!("Startup link: {:?}", lnk);
|
||||
if let Some(path) = lnk.relative_path() {
|
||||
git_repos.insert_if_repo(startup_dir.join(path));
|
||||
git_repos.insert_if_repo(&startup_dir.join(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::CommandExt;
|
||||
use crate::terminal::{is_dumb, print_separator};
|
||||
use crate::utils::{require_option, which, PathExt};
|
||||
use crate::Step;
|
||||
@@ -28,8 +27,8 @@ impl Powershell {
|
||||
let profile = path.as_ref().and_then(|path| {
|
||||
Command::new(path)
|
||||
.args(["-NoProfile", "-Command", "Split-Path $profile"])
|
||||
.output_checked_utf8()
|
||||
.map(|output| PathBuf::from(output.stdout.trim()))
|
||||
.check_output()
|
||||
.map(|output| PathBuf::from(output.trim()))
|
||||
.and_then(|p| p.require())
|
||||
.ok()
|
||||
});
|
||||
@@ -47,14 +46,14 @@ 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}"),
|
||||
&format!("Get-Module -ListAvailable {}", command),
|
||||
])
|
||||
.output_checked_utf8()
|
||||
.map(|result| !result.stdout.is_empty())
|
||||
.check_output()
|
||||
.map(|result| !result.is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -63,9 +62,9 @@ impl Powershell {
|
||||
}
|
||||
|
||||
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?;
|
||||
|
||||
print_separator(t!("Powershell Modules Update"));
|
||||
print_separator("Powershell Modules Update");
|
||||
|
||||
let mut cmd = vec!["Update-Module"];
|
||||
|
||||
@@ -77,12 +76,11 @@ impl Powershell {
|
||||
cmd.push("-Force")
|
||||
}
|
||||
|
||||
println!("{}", t!("Updating modules..."));
|
||||
println!("Updating modules...");
|
||||
ctx.run_type()
|
||||
.execute(powershell)
|
||||
// This probably doesn't need `shell_words::join`.
|
||||
.args(["-NoProfile", "-Command", &cmd.join(" ")])
|
||||
.status_checked()
|
||||
.check_run()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -95,28 +93,31 @@ impl Powershell {
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?;
|
||||
|
||||
debug_assert!(self.supports_windows_update());
|
||||
|
||||
let accept_all = if ctx.config().accept_all_windows_updates() {
|
||||
"-AcceptAll"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let install_windowsupdate_verbose = "Install-WindowsUpdate -Verbose".to_string();
|
||||
|
||||
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(["-NoProfile", &install_windowsupdate_verbose, accept_all])
|
||||
.status_checked()
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
&format!(
|
||||
"Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose",
|
||||
if ctx.config().accept_all_windows_updates() {
|
||||
"-AcceptAll"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
])
|
||||
.check_run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,52 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use rust_i18n::t;
|
||||
|
||||
use crate::{
|
||||
command::CommandExt, error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils,
|
||||
};
|
||||
|
||||
fn prepare_async_ssh_command(args: &mut Vec<&str>) {
|
||||
args.insert(0, "ssh");
|
||||
args.push("--keep");
|
||||
}
|
||||
|
||||
pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
|
||||
let ssh = utils::require("ssh")?;
|
||||
|
||||
let topgrade = ctx.config().remote_topgrade_path();
|
||||
let mut args = vec!["-t", hostname];
|
||||
|
||||
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
|
||||
args.extend(ssh_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let env = format!("TOPGRADE_PREFIX={hostname}");
|
||||
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
|
||||
|
||||
if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
prepare_async_ssh_command(&mut args);
|
||||
crate::tmux::run_command(ctx, hostname, &shell_words::join(args))?;
|
||||
Err(SkipStep(String::from(t!("Remote Topgrade launched in Tmux"))).into())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
unreachable!("Tmux execution is only implemented in Unix");
|
||||
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
|
||||
prepare_async_ssh_command(&mut args);
|
||||
ctx.run_type().execute("wt").args(&args).spawn()?;
|
||||
Err(SkipStep(String::from(t!("Remote Topgrade launched in an external terminal"))).into())
|
||||
} else {
|
||||
let mut args = vec!["-t", hostname];
|
||||
|
||||
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
|
||||
args.extend(ssh_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let env = format!("TOPGRADE_PREFIX={hostname}");
|
||||
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
|
||||
|
||||
print_separator(format!("Remote ({hostname})"));
|
||||
println!("{}", t!("Connecting to {hostname}...", hostname = hostname));
|
||||
|
||||
ctx.run_type().execute(ssh).args(&args).status_checked()
|
||||
}
|
||||
}
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils};
|
||||
|
||||
fn prepare_async_ssh_command(args: &mut Vec<&str>) {
|
||||
args.insert(0, "ssh");
|
||||
args.push("--keep");
|
||||
}
|
||||
|
||||
pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
|
||||
let ssh = utils::require("ssh")?;
|
||||
|
||||
let topgrade = ctx.config().remote_topgrade_path();
|
||||
let mut args = vec!["-t", hostname];
|
||||
|
||||
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
|
||||
args.extend(ssh_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let env = format!("TOPGRADE_PREFIX={}", hostname);
|
||||
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
|
||||
|
||||
if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
prepare_async_ssh_command(&mut args);
|
||||
crate::tmux::run_command(ctx, &args.join(" "))?;
|
||||
Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
unreachable!("Tmux execution is only implemented in Unix");
|
||||
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
|
||||
prepare_async_ssh_command(&mut args);
|
||||
ctx.run_type().execute("wt").args(&args).spawn()?;
|
||||
Err(SkipStep(String::from("Remote Topgrade launched in an external terminal")).into())
|
||||
} else {
|
||||
let mut args = vec!["-t", hostname];
|
||||
|
||||
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
|
||||
args.extend(ssh_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let env = format!("TOPGRADE_PREFIX={}", hostname);
|
||||
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
|
||||
|
||||
print_separator(format!("Remote ({})", hostname));
|
||||
println!("Connecting to {}...", hostname);
|
||||
|
||||
ctx.run_type().execute(&ssh).args(&args).check_run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::{fmt::Display, rc::Rc, str::FromStr};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use anyhow::Result;
|
||||
use log::{debug, error};
|
||||
use regex::Regex;
|
||||
use rust_i18n::t;
|
||||
use strum::EnumString;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::executor::CommandExt;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{error::SkipStep, utils, Step};
|
||||
|
||||
@@ -62,11 +61,10 @@ impl Vagrant {
|
||||
let output = Command::new(&self.path)
|
||||
.arg("status")
|
||||
.current_dir(directory)
|
||||
.output_checked_utf8()?;
|
||||
.check_output()?;
|
||||
debug!("Vagrant output in {}: {}", directory, output);
|
||||
|
||||
let boxes = output
|
||||
.stdout
|
||||
.split('\n')
|
||||
.skip(2)
|
||||
.take_while(|line| !(line.is_empty() || line.starts_with('\r')))
|
||||
@@ -117,7 +115,7 @@ impl<'a> TemporaryPowerOn<'a> {
|
||||
.execute(vagrant)
|
||||
.args([subcommand, &vagrant_box.name])
|
||||
.current_dir(vagrant_box.path.clone())
|
||||
.status_checked()?;
|
||||
.check_run()?;
|
||||
Ok(TemporaryPowerOn {
|
||||
vagrant,
|
||||
vagrant_box,
|
||||
@@ -144,7 +142,7 @@ impl<'a> Drop for TemporaryPowerOn<'a> {
|
||||
.execute(self.vagrant)
|
||||
.args([subcommand, &self.vagrant_box.name])
|
||||
.current_dir(self.vagrant_box.path.clone())
|
||||
.status_checked()
|
||||
.check_run()
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
@@ -152,14 +150,14 @@ impl<'a> Drop for TemporaryPowerOn<'a> {
|
||||
pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> {
|
||||
let directories = utils::require_option(
|
||||
ctx.config().vagrant_directories(),
|
||||
String::from(t!("No Vagrant directories were specified in the configuration file")),
|
||||
String::from("No Vagrant directories were specified in the configuration file"),
|
||||
)?;
|
||||
let vagrant = Vagrant {
|
||||
path: utils::require("vagrant")?,
|
||||
};
|
||||
|
||||
print_separator("Vagrant");
|
||||
println!("{}", t!("Collecting Vagrant boxes"));
|
||||
println!("Collecting Vagrant boxes");
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
@@ -184,11 +182,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
|
||||
let mut _poweron = None;
|
||||
if !vagrant_box.initial_status.powered_on() {
|
||||
if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
|
||||
return Err(SkipStep(format!(
|
||||
"{}",
|
||||
t!("Skipping powered off box {vagrant_box}", vagrant_box = vagrant_box)
|
||||
))
|
||||
.into());
|
||||
return Err(SkipStep(format!("Skipping powered off box {}", vagrant_box)).into());
|
||||
} else {
|
||||
print_separator(seperator);
|
||||
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
|
||||
@@ -205,21 +199,21 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
|
||||
.execute(&vagrant.path)
|
||||
.current_dir(&vagrant_box.path)
|
||||
.args(["ssh", "-c", &command])
|
||||
.status_checked()
|
||||
.check_run()
|
||||
}
|
||||
|
||||
pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
|
||||
let vagrant = utils::require("vagrant")?;
|
||||
print_separator(t!("Vagrant boxes"));
|
||||
print_separator("Vagrant boxes");
|
||||
|
||||
let outdated = Command::new(&vagrant)
|
||||
.args(["box", "outdated", "--global"])
|
||||
.output_checked_utf8()?;
|
||||
.check_output()?;
|
||||
|
||||
let re = Regex::new(r"\* '(.*?)' for '(.*?)' is outdated").unwrap();
|
||||
|
||||
let mut found = false;
|
||||
for ele in re.captures_iter(&outdated.stdout) {
|
||||
for ele in re.captures_iter(&outdated) {
|
||||
found = true;
|
||||
let _ = ctx
|
||||
.run_type()
|
||||
@@ -228,16 +222,13 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
|
||||
.arg(ele.get(1).unwrap().as_str())
|
||||
.arg("--provider")
|
||||
.arg(ele.get(2).unwrap().as_str())
|
||||
.status_checked();
|
||||
.check_run();
|
||||
}
|
||||
|
||||
if !found {
|
||||
println!("{}", t!("No outdated boxes"))
|
||||
println!("No outdated boxes")
|
||||
} else {
|
||||
ctx.run_type()
|
||||
.execute(&vagrant)
|
||||
.args(["box", "prune"])
|
||||
.status_checked()?;
|
||||
ctx.run_type().execute(&vagrant).args(["box", "prune"]).check_run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,38 +1,26 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::TmuxConfig;
|
||||
use crate::config::TmuxSessionMode;
|
||||
use crate::executor::RunType;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::HOME_DIR;
|
||||
use crate::{
|
||||
execution_context::ExecutionContext,
|
||||
utils::{which, PathExt},
|
||||
utils::{which, Check, PathExt},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use directories::BaseDirs;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{exit, Command};
|
||||
|
||||
use rust_i18n::t;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
|
||||
pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
|
||||
let tpm = match env::var("TMUX_PLUGIN_MANAGER_PATH") {
|
||||
// If `TMUX_PLUGIN_MANAGER_PATH` is set, search for
|
||||
// `$TMUX_PLUGIN_MANAGER_PATH/bin/install_plugins/tpm/bin/update_plugins`
|
||||
Ok(var) => PathBuf::from(var).join("bin/install_plugins/tpm/bin/update_plugins"),
|
||||
// Otherwise, use the default location `~/.tmux/plugins/tpm/bin/update_plugins`
|
||||
Err(_) => HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins"),
|
||||
}
|
||||
.require()?;
|
||||
pub fn run_tpm(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
|
||||
let tpm = base_dirs
|
||||
.home_dir()
|
||||
.join(".tmux/plugins/tpm/bin/update_plugins")
|
||||
.require()?;
|
||||
|
||||
print_separator("tmux plugins");
|
||||
|
||||
ctx.run_type().execute(tpm).arg("all").status_checked()
|
||||
run_type.execute(&tpm).arg("all").check_run()
|
||||
}
|
||||
|
||||
struct Tmux {
|
||||
@@ -41,10 +29,12 @@ struct Tmux {
|
||||
}
|
||||
|
||||
impl Tmux {
|
||||
fn new(args: Vec<String>) -> Self {
|
||||
fn new(args: &Option<String>) -> 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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,144 +46,73 @@ impl Tmux {
|
||||
command
|
||||
}
|
||||
|
||||
fn has_session(&self, session_name: &str) -> Result<bool> {
|
||||
fn has_session(&self, session_name: &str) -> Result<bool, io::Error> {
|
||||
Ok(self
|
||||
.build()
|
||||
.args(["has-session", "-t", session_name])
|
||||
.output_checked_with(|_| Ok(()))?
|
||||
.output()?
|
||||
.status
|
||||
.success())
|
||||
}
|
||||
|
||||
/// Create a new tmux session with the given name, running the given command.
|
||||
/// The command is passed to `sh` (see "shell-command arguments are sh(1) commands" in the
|
||||
/// `tmux(1)` man page).
|
||||
fn new_session(&self, session_name: &str, window_name: &str, command: &str) -> Result<()> {
|
||||
let _ = self
|
||||
fn new_session(&self, session_name: &str) -> Result<bool, io::Error> {
|
||||
Ok(self
|
||||
.build()
|
||||
// `-d`: initial size comes from the global `default-size` option (instead
|
||||
// of passing `-x` and `-y` arguments.
|
||||
// (What do those even do?)
|
||||
// `-s`: session name
|
||||
// `-n`: window name (always `topgrade`)
|
||||
.args(["new-session", "-d", "-s", session_name, "-n", window_name, command])
|
||||
.output_checked()?;
|
||||
.args(["new-session", "-d", "-s", session_name, "-n", "dummy"])
|
||||
.spawn()?
|
||||
.wait()?
|
||||
.success())
|
||||
}
|
||||
|
||||
fn run_in_session(&self, command: &str) -> Result<()> {
|
||||
self.build()
|
||||
.args(["new-window", "-t", "topgrade", command])
|
||||
.spawn()?
|
||||
.wait()?
|
||||
.check()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like [`new_session`] but it appends a digit to the session name (if necessary) to
|
||||
/// avoid duplicate session names.
|
||||
///
|
||||
/// The session name is returned.
|
||||
fn new_unique_session(&self, session_name: &str, window_name: &str, command: &str) -> Result<String> {
|
||||
let mut session = session_name.to_owned();
|
||||
for i in 1.. {
|
||||
if !self
|
||||
.has_session(&session)
|
||||
.context("Error determining if a tmux session exists")?
|
||||
{
|
||||
self.new_session(&session, window_name, command)
|
||||
.context("Error running Topgrade in tmux")?;
|
||||
return Ok(session);
|
||||
}
|
||||
session = format!("{session_name}-{i}");
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Create a new window in the given tmux session, running the given command.
|
||||
fn new_window(&self, session_name: &str, window_name: &str, command: &str) -> Result<()> {
|
||||
self.build()
|
||||
// `-d`: initial size comes from the global `default-size` option (instead
|
||||
// of passing `-x` and `-y` arguments.
|
||||
// (What do those even do?)
|
||||
// `-s`: session name
|
||||
// `-n`: window name
|
||||
.args([
|
||||
"new-window",
|
||||
"-a",
|
||||
"-t",
|
||||
&format!("{session_name}:{window_name}"),
|
||||
"-n",
|
||||
window_name,
|
||||
command,
|
||||
])
|
||||
.env_remove("TMUX")
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
fn window_indices(&self, session_name: &str) -> Result<Vec<usize>> {
|
||||
self.build()
|
||||
.args(["list-windows", "-F", "#{window_index}", "-t", session_name])
|
||||
.output_checked_utf8()?
|
||||
.stdout
|
||||
.lines()
|
||||
.map(|l| l.parse())
|
||||
.collect::<Result<Vec<usize>, _>>()
|
||||
.context("Failed to compute tmux windows")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
||||
pub fn run_in_tmux(args: &Option<String>) -> ! {
|
||||
let command = {
|
||||
let mut command = vec![
|
||||
String::from("env"),
|
||||
String::from("TOPGRADE_KEEP_END=1"),
|
||||
String::from("TOPGRADE_INSIDE_TMUX=1"),
|
||||
];
|
||||
// TODO: Should we use `topgrade` instead of the first argument here, which may be
|
||||
// a local path?
|
||||
command.extend(env::args());
|
||||
shell_words::join(command)
|
||||
command.join(" ")
|
||||
};
|
||||
|
||||
let tmux = Tmux::new(config.args);
|
||||
let tmux = Tmux::new(args);
|
||||
|
||||
// Find an unused session and run `topgrade` in it with the current command's arguments.
|
||||
let session_name = "topgrade";
|
||||
let window_name = "topgrade";
|
||||
let session = tmux.new_unique_session(session_name, window_name, &command)?;
|
||||
|
||||
let is_inside_tmux = env::var("TMUX").is_ok();
|
||||
let err = match config.session_mode {
|
||||
TmuxSessionMode::AttachIfNotInSession => {
|
||||
if is_inside_tmux {
|
||||
// Only attach to the newly-created session if we're not currently in a tmux session.
|
||||
println!("{}", t!("Topgrade launched in a new tmux session"));
|
||||
return Ok(());
|
||||
} else {
|
||||
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||
}
|
||||
}
|
||||
|
||||
TmuxSessionMode::AttachAlways => {
|
||||
if is_inside_tmux {
|
||||
tmux.build().args(["switch-client", "-t", &session]).exec()
|
||||
} else {
|
||||
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(eyre!("{err}")).context("Failed to `execvp(3)` tmux")
|
||||
}
|
||||
|
||||
pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> {
|
||||
let tmux = Tmux::new(ctx.config().tmux_config()?.args);
|
||||
|
||||
match ctx.get_tmux_session() {
|
||||
Some(session_name) => {
|
||||
let indices = tmux.window_indices(&session_name)?;
|
||||
let last_window = indices
|
||||
.iter()
|
||||
.last()
|
||||
.ok_or_else(|| eyre!("tmux session {session_name} has no windows"))?;
|
||||
tmux.new_window(&session_name, &format!("{last_window}"), command)?;
|
||||
}
|
||||
None => {
|
||||
let name = tmux.new_unique_session("topgrade", window_name, command)?;
|
||||
ctx.set_tmux_session(name);
|
||||
}
|
||||
if !tmux.has_session("topgrade").expect("Error detecting a tmux session") {
|
||||
tmux.new_session("topgrade").expect("Error creating a tmux session");
|
||||
}
|
||||
|
||||
tmux.run_in_session(&command).expect("Error running topgrade in tmux");
|
||||
tmux.build()
|
||||
.args(["kill-window", "-t", "topgrade:dummy"])
|
||||
.output()
|
||||
.expect("Error killing the dummy tmux window");
|
||||
|
||||
if env::var("TMUX").is_err() {
|
||||
let err = tmux.build().args(["attach", "-t", "topgrade"]).exec();
|
||||
panic!("{:?}", err);
|
||||
} else {
|
||||
println!("Topgrade launched in a new tmux session");
|
||||
exit(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> {
|
||||
Tmux::new(ctx.config().tmux_arguments())
|
||||
.build()
|
||||
.args(["new-window", "-a", "-t", "topgrade:1", command])
|
||||
.env_remove("TMUX")
|
||||
.spawn()?
|
||||
.wait()?
|
||||
.check()
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::Step;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::{execution_context::ExecutionContext, utils::require};
|
||||
use log::debug;
|
||||
use std::path::Path;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use tracing::debug;
|
||||
|
||||
fn list_toolboxes(toolbx: &Path) -> Result<Vec<String>> {
|
||||
let output = Command::new(toolbx)
|
||||
.args(["list", "--containers"])
|
||||
.output_checked_utf8()?;
|
||||
let output = Command::new(toolbx).args(["list", "--containers"]).output()?;
|
||||
let output_str = String::from_utf8(output.stdout)?;
|
||||
|
||||
let proc: Vec<String> = output
|
||||
.stdout
|
||||
let proc: Vec<String> = output_str
|
||||
.lines()
|
||||
// Skip the first line since that contains only status information
|
||||
.skip(1)
|
||||
@@ -36,13 +33,13 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("Toolboxes to inspect: {:?}", toolboxes);
|
||||
|
||||
let mut topgrade_path = PathBuf::from("/run/host");
|
||||
// Path of the running Topgrade executable
|
||||
// Path of the running topgrade executable
|
||||
// Skip 1 to eliminate the path root, otherwise push overwrites the path
|
||||
topgrade_path.push(std::env::current_exe()?.components().skip(1).collect::<PathBuf>());
|
||||
let topgrade_path = topgrade_path.to_str().unwrap();
|
||||
|
||||
for tb in toolboxes.iter() {
|
||||
let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {tb}'");
|
||||
let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {}'", tb);
|
||||
let mut args = vec![
|
||||
"run",
|
||||
"-c",
|
||||
@@ -52,14 +49,12 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
|
||||
topgrade_path,
|
||||
"--only",
|
||||
"system",
|
||||
"--no-self-update",
|
||||
"--skip-notify",
|
||||
];
|
||||
if ctx.config().yes(Step::Toolbx) {
|
||||
args.push("--yes");
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&toolbx).args(&args).status_checked()?;
|
||||
let _output = ctx.run_type().execute(&toolbx).args(&args).check_run();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
" AstroUpdate calls a plugin manager - Lazy as of this writing. So we check for it before
|
||||
" others. Add to init.lua:
|
||||
" updater = {
|
||||
" skip_prompts = true,
|
||||
" },
|
||||
if exists(":AstroUpdate")
|
||||
echo "AstroUpdate"
|
||||
AstroUpdate
|
||||
quitall
|
||||
endif
|
||||
|
||||
if exists(":MasonUpdate")
|
||||
echo "MasonUpdate"
|
||||
MasonUpdate
|
||||
endif
|
||||
|
||||
if exists(":NeoBundleUpdate")
|
||||
echo "NeoBundle"
|
||||
NeoBundleUpdate
|
||||
@@ -34,6 +18,11 @@ if exists(":PlugUpgrade")
|
||||
endif
|
||||
endif
|
||||
|
||||
if exists(":PackerUpdate")
|
||||
echo "Packer"
|
||||
PackerSync
|
||||
endif
|
||||
|
||||
if exists("*dein#update()")
|
||||
echo "dein#update()"
|
||||
call dein#update()
|
||||
@@ -49,15 +38,9 @@ if exists(":PaqUpdate")
|
||||
PaqUpdate
|
||||
endif
|
||||
|
||||
if exists(":Lazy")
|
||||
echo "Lazy Update"
|
||||
Lazy! sync | qa
|
||||
if exists(":CocUpdateSync")
|
||||
echo "CocUpdateSync"
|
||||
CocUpdateSync
|
||||
endif
|
||||
|
||||
if exists(':PackerSync')
|
||||
echo "Packer"
|
||||
autocmd User PackerComplete quitall
|
||||
PackerSync
|
||||
else
|
||||
quitall
|
||||
endif
|
||||
quitall
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user