Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0a0d69bb | ||
|
|
4b3a3e74f8 | ||
|
|
2c4751c7b2 | ||
|
|
30941ed26d | ||
|
|
c7163b63db | ||
|
|
6e6b3dcbfe | ||
|
|
1d136a6635 | ||
|
|
0ee67d78ef | ||
|
|
7356b920d4 | ||
|
|
ce8a325c1f | ||
|
|
a2f57e4769 | ||
|
|
751f41bc5e | ||
|
|
fd406f0f82 | ||
|
|
801dddacd4 | ||
|
|
397a537eef | ||
|
|
0423c836eb | ||
|
|
3250337e70 | ||
|
|
9dcd7fffe2 | ||
|
|
30b727b138 | ||
|
|
b86d6981ab | ||
|
|
2bf6a2b100 | ||
|
|
3dc8d31d57 | ||
|
|
b308fb92c0 | ||
|
|
bc9746455e | ||
|
|
109a9c76e3 |
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@@ -22,10 +22,28 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
run: |
|
run: |
|
||||||
|
rustup component add rustfmt
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
step-enum-sorted:
|
||||||
|
name: Step enum sorted
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check if `Step` enum is sorted
|
||||||
|
run: |
|
||||||
|
ENUM_NAME="Step"
|
||||||
|
FILE="src/config.rs"
|
||||||
|
awk "/enum $ENUM_NAME/,/}/" "$FILE" | \
|
||||||
|
grep -E '^\s*[A-Za-z_][A-Za-z0-9_]*\s*,?$' | \
|
||||||
|
sed 's/[, ]//g' > original.txt
|
||||||
|
sort original.txt > sorted.txt
|
||||||
|
diff original.txt sorted.txt
|
||||||
|
|
||||||
main:
|
main:
|
||||||
needs: fmt
|
needs: [fmt, step-enum-sorted]
|
||||||
name: ${{ matrix.target_name }} (check, clippy)
|
name: ${{ matrix.target_name }} (check, clippy)
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
@@ -79,7 +97,9 @@ jobs:
|
|||||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Run cargo/cross clippy
|
- name: Run cargo/cross clippy
|
||||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
run: |
|
||||||
|
rustup component add clippy
|
||||||
|
${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
# ONLY run test with cargo
|
# ONLY run test with cargo
|
||||||
|
|||||||
105
.github/workflows/create_release_assets.yml
vendored
105
.github/workflows/create_release_assets.yml
vendored
@@ -1,26 +1,23 @@
|
|||||||
name: Publish release files for CD native environments
|
name: Publish release files for CD native and non-cd-native environments
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# workflow_run:
|
|
||||||
# workflows: ["Check SemVer compliance"]
|
|
||||||
# types:
|
|
||||||
# - completed
|
|
||||||
release:
|
release:
|
||||||
types: [ created ]
|
types: [ created ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
# Publish release files for CD native environments
|
||||||
|
native_build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
|
platform: [ ubuntu-22.04, macos-latest, macos-13, windows-latest ]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install cargo-deb
|
- name: Install cargo-deb
|
||||||
run: cargo install cargo-deb
|
run: cargo install cargo-deb
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Check format
|
- name: Check format
|
||||||
@@ -59,14 +56,14 @@ jobs:
|
|||||||
rm -rf target/release
|
rm -rf target/release
|
||||||
cargo build --release
|
cargo build --release
|
||||||
cargo deb --no-build --no-strip
|
cargo deb --no-build --no-strip
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Move Debian-based system package
|
- name: Move Debian-based system package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p assets
|
mkdir -p assets
|
||||||
mv target/debian/*.deb assets
|
mv target/debian/*.deb assets
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Rename Release (Windows)
|
- name: Rename Release (Windows)
|
||||||
@@ -86,3 +83,91 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: assets/*
|
files: assets/*
|
||||||
|
|
||||||
|
# Publish release files for non-CD-native environments
|
||||||
|
cross_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 cross compilation dependencies for armv7
|
||||||
|
run: sudo apt-get install libc6-armhf-cross libgcc-s1-armhf-cross
|
||||||
|
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install cargo-deb
|
||||||
|
run: cargo install cargo-deb
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
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' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
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' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: assets/*
|
||||||
|
|||||||
@@ -1,97 +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 cross compilation dependencies for armv7
|
|
||||||
run: sudo apt-get install libc6-armhf-cross libgcc-s1-armhf-cross
|
|
||||||
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install cargo-deb
|
|
||||||
run: cargo install cargo-deb
|
|
||||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
|
||||||
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' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
|
||||||
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' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: assets/*
|
|
||||||
13
.github/workflows/release_to_aur.yml
vendored
13
.github/workflows/release_to_aur.yml
vendored
@@ -1,13 +1,12 @@
|
|||||||
name: Publish to AUR
|
name: Publish to AUR
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# workflow_run:
|
# Step "Publish binary AUR package" needs the binaries built by the following
|
||||||
# workflows: ["Check SemVer compliance"]
|
# workflow, so we wait for it to complete.
|
||||||
# types:
|
workflow_run:
|
||||||
# - completed
|
workflows: ["Publish release files for CD native and non-cd-native environments"]
|
||||||
push:
|
types:
|
||||||
tags:
|
- completed
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
aur-publish:
|
aur-publish:
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
1. The `jet_brains_toolbox` step was renamed to `jetbrains_toolbox`. If you're
|
||||||
|
using the old name in your configuration file in the `disable` or `only`
|
||||||
|
fields, simply change it to `jetbrains_toolbox`.
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ To add a new `step` to `topgrade`:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub enum Step {
|
pub enum Step {
|
||||||
// Existed steps
|
// Existing steps
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// Your new step here!
|
// Your new step here!
|
||||||
// You may want it to be sorted alphabetically because that looks great:)
|
// Make sure it stays sorted alphabetically because that looks great :)
|
||||||
Xxx,
|
Xxx,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -78,7 +78,7 @@ To add a new `step` to `topgrade`:
|
|||||||
to separate the steps, for example, for steps that are Linux-only, it goes
|
to separate the steps, for example, for steps that are Linux-only, it goes
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
```
|
```rust
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
// Xxx is Linux-only
|
// Xxx is Linux-only
|
||||||
@@ -86,7 +86,7 @@ To add a new `step` to `topgrade`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Congrats, you just added a new `step`:)
|
Congrats, you just added a new `step` :)
|
||||||
|
|
||||||
## Modification to the configuration entries
|
## Modification to the configuration entries
|
||||||
|
|
||||||
|
|||||||
51
Cargo.lock
generated
51
Cargo.lock
generated
@@ -1449,9 +1449,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jetbrains-toolbox-updater"
|
name = "jetbrains-toolbox-updater"
|
||||||
version = "1.4.0"
|
version = "5.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9d86b38fee698b3f63c9772fe2832d03a84e0724e409d499517c8a102949717"
|
checksum = "5c6bb35a4c18ced364ba2a3952bf5ca2b9231451974f5c2a4c8fa14f300a545b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 6.0.0",
|
"dirs 6.0.0",
|
||||||
"json",
|
"json",
|
||||||
@@ -1481,9 +1481,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.170"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
@@ -1730,6 +1730,15 @@ dependencies = [
|
|||||||
"objc_id",
|
"objc_id",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc_id"
|
name = "objc_id"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2002,26 +2011,6 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon"
|
|
||||||
version = "1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"rayon-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon-core"
|
|
||||||
version = "1.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -2638,15 +2627,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.33.1"
|
version = "0.34.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
|
checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ntapi",
|
"ntapi",
|
||||||
"rayon",
|
"objc2-core-foundation",
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2786,9 +2774,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.38.0"
|
version = "1.38.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2884,7 +2872,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "topgrade"
|
name = "topgrade"
|
||||||
version = "16.0.3"
|
version = "16.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2898,7 +2886,6 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"home",
|
"home",
|
||||||
"jetbrains-toolbox-updater",
|
"jetbrains-toolbox-updater",
|
||||||
"lazy_static",
|
|
||||||
"merge",
|
"merge",
|
||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ keywords = ["upgrade", "update"]
|
|||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
repository = "https://github.com/topgrade-rs/topgrade"
|
repository = "https://github.com/topgrade-rs/topgrade"
|
||||||
rust-version = "1.84.1"
|
rust-version = "1.84.1"
|
||||||
version = "16.0.3"
|
version = "16.0.4"
|
||||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
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", "BREAKINGCHANGES_dev.md"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -33,7 +33,6 @@ clap_complete = "~4.5"
|
|||||||
clap_mangen = "~0.2"
|
clap_mangen = "~0.2"
|
||||||
walkdir = "~2.5"
|
walkdir = "~2.5"
|
||||||
console = "~0.15"
|
console = "~0.15"
|
||||||
lazy_static = "~1.4"
|
|
||||||
chrono = "~0.4"
|
chrono = "~0.4"
|
||||||
glob = "~0.3"
|
glob = "~0.3"
|
||||||
strum = { version = "~0.26", features = ["derive"] }
|
strum = { version = "~0.26", features = ["derive"] }
|
||||||
@@ -54,7 +53,7 @@ notify-rust = "~4.11"
|
|||||||
wildmatch = "2.3.0"
|
wildmatch = "2.3.0"
|
||||||
rust-i18n = "3.0.1"
|
rust-i18n = "3.0.1"
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
jetbrains-toolbox-updater = "1.1.0"
|
jetbrains-toolbox-updater = "5.0.0"
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||||
|
|||||||
@@ -654,29 +654,29 @@ _version: 2
|
|||||||
zh_CN: "无法使用 `fish_update_completions`"
|
zh_CN: "无法使用 `fish_update_completions`"
|
||||||
zh_TW: "無法使用 `fish_update_completions`"
|
zh_TW: "無法使用 `fish_update_completions`"
|
||||||
de: "`fish_update_completions` ist nicht verfügbar"
|
de: "`fish_update_completions` ist nicht verfügbar"
|
||||||
"Desktop doest not appear to be gnome":
|
"Desktop does not appear to be GNOME":
|
||||||
en: "Desktop doest not appear to be gnome"
|
en: "Desktop does not appear to be GNOME"
|
||||||
lt: "Darbalaukis, matyt, nėra Gnome"
|
lt: "Darbalaukis, matyt, nėra GNOME"
|
||||||
es: "El escritorio no parece ser Gnome"
|
es: "El escritorio no parece ser GNOME"
|
||||||
fr: "Le bureau ne semble pas être Gnome"
|
fr: "Le bureau ne semble pas être GNOME"
|
||||||
zh_CN: "桌面环境不是 Gnome"
|
zh_CN: "桌面环境不是 GNOME"
|
||||||
zh_TW: "桌面環境不是 Gnome"
|
zh_TW: "桌面環境不是 GNOME"
|
||||||
de: "Desktop scheint nicht GNOME zu sein"
|
de: "Desktop scheint nicht GNOME zu sein"
|
||||||
"Gnome shell extensions are unregistered in DBus":
|
"GNOME shell extensions are unregistered in DBus":
|
||||||
en: "Gnome shell extensions are unregistered in DBus"
|
en: "GNOME shell extensions are unregistered in DBus"
|
||||||
lt: "Gnome Shell priedai nėra užregistruoti DBus'e"
|
lt: "GNOME Shell priedai nėra užregistruoti DBus'e"
|
||||||
es: "Las extensiones de Gnome Shell no están registradas en DBus"
|
es: "Las extensiones de GNOME Shell no están registradas en DBus"
|
||||||
fr: "Les extensions de Gnome Shell ne sont pas enregistrées dans DBus"
|
fr: "Les extensions de GNOME Shell ne sont pas enregistrées dans DBus"
|
||||||
zh_CN: "Gnome Shell 扩展在 DBus中未被注册"
|
zh_CN: "GNOME Shell 扩展在 DBus中未被注册"
|
||||||
zh_TW: "Gnome Shell 擴充功能在 DBus 中未被註冊"
|
zh_TW: "GNOME Shell 擴充功能在 DBus 中未被註冊"
|
||||||
de: "GNOME-Shell-Erweiterungen sind im DBus nicht registriert"
|
de: "GNOME-Shell-Erweiterungen sind im DBus nicht registriert"
|
||||||
"Gnome Shell extensions":
|
"GNOME Shell extensions":
|
||||||
en: "Gnome Shell extensions"
|
en: "GNOME Shell extensions"
|
||||||
lt: "Gnome Shell priedai"
|
lt: "GNOME Shell priedai"
|
||||||
es: "Extensiones de Gnome Shell"
|
es: "Extensiones de GNOME Shell"
|
||||||
fr: "Extensions de Gnome Shell"
|
fr: "Extensions de GNOME Shell"
|
||||||
zh_CN: "Gnome Shell 扩展"
|
zh_CN: "GNOME Shell 扩展"
|
||||||
zh_TW: "Gnome Shell 擴充功能"
|
zh_TW: "GNOME Shell 擴充功能"
|
||||||
de: "GNOME-Shell-Erweiterungen"
|
de: "GNOME-Shell-Erweiterungen"
|
||||||
"Not a custom brew for macOS":
|
"Not a custom brew for macOS":
|
||||||
en: "Not a custom brew for macOS"
|
en: "Not a custom brew for macOS"
|
||||||
@@ -1288,3 +1288,11 @@ _version: 2
|
|||||||
zh_CN: "jetbrains-toolbox-updater 在更新过程中遇到意外错误"
|
zh_CN: "jetbrains-toolbox-updater 在更新过程中遇到意外错误"
|
||||||
zh_TW: "jetbrains-toolbox-updater 在更新過程中遇到意外錯誤:"
|
zh_TW: "jetbrains-toolbox-updater 在更新過程中遇到意外錯誤:"
|
||||||
de: "jetbrains-toolbox-updater ist auf einen unerwarteten Fehler während der Aktualisierung gestoßen:"
|
de: "jetbrains-toolbox-updater ist auf einen unerwarteten Fehler während der Aktualisierung gestoßen:"
|
||||||
|
"<output from `deb-get clean` omitted>":
|
||||||
|
en: "<output from `deb-get clean` omitted>"
|
||||||
|
lt: "<išvestis iš `deb-get clean` praleista>"
|
||||||
|
es: "<salida de `deb-get clean` omitido>"
|
||||||
|
fr: "<sortie de `deb-get clean` omise>"
|
||||||
|
zh_CN: "<省略了 `deb-get clean` 的输出>"
|
||||||
|
zh_TW: "<省略了 `deb-get clean` 的輸出>"
|
||||||
|
de: "<Ausgabe von `deb-get clean` ausgelassen>"
|
||||||
|
|||||||
@@ -52,10 +52,11 @@ pub type Commands = BTreeMap<String, String>;
|
|||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Step {
|
pub enum Step {
|
||||||
AM,
|
AM,
|
||||||
|
AndroidStudio,
|
||||||
AppMan,
|
AppMan,
|
||||||
|
Aqua,
|
||||||
Asdf,
|
Asdf,
|
||||||
Atom,
|
Atom,
|
||||||
Aqua,
|
|
||||||
Audit,
|
Audit,
|
||||||
AutoCpufreq,
|
AutoCpufreq,
|
||||||
Bin,
|
Bin,
|
||||||
@@ -90,30 +91,46 @@ pub enum Step {
|
|||||||
Gcloud,
|
Gcloud,
|
||||||
Gem,
|
Gem,
|
||||||
Ghcup,
|
Ghcup,
|
||||||
GithubCliExtensions,
|
|
||||||
GitRepos,
|
GitRepos,
|
||||||
|
GithubCliExtensions,
|
||||||
GnomeShellExtensions,
|
GnomeShellExtensions,
|
||||||
Go,
|
Go,
|
||||||
Guix,
|
Guix,
|
||||||
Haxelib,
|
Haxelib,
|
||||||
|
Helix,
|
||||||
Helm,
|
Helm,
|
||||||
HomeManager,
|
HomeManager,
|
||||||
JetBrainsToolbox,
|
// These names are miscapitalized on purpose, so the CLI name is
|
||||||
|
// `jetbrains_pycharm` instead of `jet_brains_py_charm`.
|
||||||
|
JetbrainsAqua,
|
||||||
|
JetbrainsClion,
|
||||||
|
JetbrainsDatagrip,
|
||||||
|
JetbrainsDataspell,
|
||||||
|
JetbrainsGateway,
|
||||||
|
JetbrainsGoland,
|
||||||
|
JetbrainsIdea,
|
||||||
|
JetbrainsMps,
|
||||||
|
JetbrainsPhpstorm,
|
||||||
|
JetbrainsPycharm,
|
||||||
|
JetbrainsRider,
|
||||||
|
JetbrainsRubymine,
|
||||||
|
JetbrainsRustrover,
|
||||||
|
JetbrainsToolbox,
|
||||||
|
JetbrainsWebstorm,
|
||||||
Jetpack,
|
Jetpack,
|
||||||
Julia,
|
Julia,
|
||||||
Juliaup,
|
Juliaup,
|
||||||
Kakoune,
|
Kakoune,
|
||||||
Helix,
|
|
||||||
Krew,
|
Krew,
|
||||||
Lure,
|
|
||||||
Lensfun,
|
Lensfun,
|
||||||
|
Lure,
|
||||||
Macports,
|
Macports,
|
||||||
Mamba,
|
Mamba,
|
||||||
Miktex,
|
|
||||||
Mas,
|
Mas,
|
||||||
Maza,
|
Maza,
|
||||||
Micro,
|
Micro,
|
||||||
MicrosoftStore,
|
MicrosoftStore,
|
||||||
|
Miktex,
|
||||||
Mise,
|
Mise,
|
||||||
Myrepos,
|
Myrepos,
|
||||||
Nix,
|
Nix,
|
||||||
@@ -174,6 +191,7 @@ pub enum Step {
|
|||||||
Xcodes,
|
Xcodes,
|
||||||
Yadm,
|
Yadm,
|
||||||
Yarn,
|
Yarn,
|
||||||
|
Yazi,
|
||||||
Zigup,
|
Zigup,
|
||||||
Zvm,
|
Zvm,
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/main.rs
56
src/main.rs
@@ -446,9 +446,63 @@ fn run() -> Result<()> {
|
|||||||
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
|
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
|
||||||
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
|
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
|
||||||
runner.execute(Step::Zigup, "zigup", || generic::run_zigup(&ctx))?;
|
runner.execute(Step::Zigup, "zigup", || generic::run_zigup(&ctx))?;
|
||||||
runner.execute(Step::JetBrainsToolbox, "JetBrains Toolbox", || {
|
runner.execute(Step::JetbrainsToolbox, "JetBrains Toolbox", || {
|
||||||
generic::run_jetbrains_toolbox(&ctx)
|
generic::run_jetbrains_toolbox(&ctx)
|
||||||
})?;
|
})?;
|
||||||
|
runner.execute(Step::AndroidStudio, "Android Studio plugins", || {
|
||||||
|
generic::run_android_studio(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsAqua, "JetBrains Aqua plugins", || {
|
||||||
|
generic::run_jetbrains_aqua(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsClion, "JetBrains CLion plugins", || {
|
||||||
|
generic::run_jetbrains_clion(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsDatagrip, "JetBrains DataGrip plugins", || {
|
||||||
|
generic::run_jetbrains_datagrip(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsDataspell, "JetBrains DataSpell plugins", || {
|
||||||
|
generic::run_jetbrains_dataspell(&ctx)
|
||||||
|
})?;
|
||||||
|
// JetBrains dotCover has no CLI
|
||||||
|
// JetBrains dotMemory has no CLI
|
||||||
|
// JetBrains dotPeek has no CLI
|
||||||
|
// JetBrains dotTrace has no CLI
|
||||||
|
// JetBrains Fleet has a different CLI without a `fleet update` command.
|
||||||
|
runner.execute(Step::JetbrainsGateway, "JetBrains Gateway plugins", || {
|
||||||
|
generic::run_jetbrains_gateway(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsGoland, "JetBrains GoLand plugins", || {
|
||||||
|
generic::run_jetbrains_goland(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsIdea, "JetBrains IntelliJ IDEA plugins", || {
|
||||||
|
generic::run_jetbrains_idea(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsMps, "JetBrains MPS plugins", || {
|
||||||
|
generic::run_jetbrains_mps(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsPhpstorm, "JetBrains PhpStorm plugins", || {
|
||||||
|
generic::run_jetbrains_phpstorm(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsPycharm, "JetBrains PyCharm plugins", || {
|
||||||
|
generic::run_jetbrains_pycharm(&ctx)
|
||||||
|
})?;
|
||||||
|
// JetBrains ReSharper has no CLI (it's a VSCode extension)
|
||||||
|
// JetBrains ReSharper C++ has no CLI (it's a VSCode extension)
|
||||||
|
runner.execute(Step::JetbrainsRider, "JetBrains Rider plugins", || {
|
||||||
|
generic::run_jetbrains_rider(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsRubymine, "JetBrains RubyMine plugins", || {
|
||||||
|
generic::run_jetbrains_rubymine(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::JetbrainsRustrover, "JetBrains RustRover plugins", || {
|
||||||
|
generic::run_jetbrains_rustrover(&ctx)
|
||||||
|
})?;
|
||||||
|
// JetBrains Space Desktop does not have a CLI
|
||||||
|
runner.execute(Step::JetbrainsWebstorm, "JetBrains WebStorm plugins", || {
|
||||||
|
generic::run_jetbrains_webstorm(&ctx)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::Yazi, "Yazi packages", || generic::run_yazi(&ctx))?;
|
||||||
|
|
||||||
if should_run_powershell {
|
if should_run_powershell {
|
||||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
use std::ffi::{OsStr, OsString};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::{env, path::Path};
|
|
||||||
use std::{fs, io::Write};
|
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use color_eyre::eyre::{eyre, OptionExt};
|
||||||
use jetbrains_toolbox_updater::{find_jetbrains_toolbox, update_jetbrains_toolbox, FindError};
|
use jetbrains_toolbox_updater::{find_jetbrains_toolbox, update_jetbrains_toolbox, FindError};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::Regex;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use std::{env, path::Path};
|
||||||
|
use std::{fs, io::Write};
|
||||||
use tempfile::tempfile_in;
|
use tempfile::tempfile_in;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::command::{CommandExt, Utf8Output};
|
use crate::command::{CommandExt, Utf8Output};
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::executor::ExecutorOutput;
|
use crate::executor::ExecutorOutput;
|
||||||
use crate::terminal::{print_separator, shell};
|
use crate::terminal::{print_separator, shell};
|
||||||
use crate::utils::{self, check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
|
use crate::utils::{check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
|
||||||
use crate::Step;
|
|
||||||
use crate::HOME_DIR;
|
use crate::HOME_DIR;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{SkipStep, StepFailed, TopgradeError},
|
error::{SkipStep, StepFailed, TopgradeError},
|
||||||
terminal::print_warning,
|
terminal::print_warning,
|
||||||
};
|
};
|
||||||
|
use crate::{output_changed_message, Step};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn is_wsl() -> Result<bool> {
|
pub fn is_wsl() -> Result<bool> {
|
||||||
@@ -223,15 +220,46 @@ pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
|
enum Aqua {
|
||||||
|
JetBrainsAqua(PathBuf),
|
||||||
|
AquaCLI(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aqua {
|
||||||
|
fn aqua_cli(self) -> Result<PathBuf> {
|
||||||
|
match self {
|
||||||
|
Aqua::AquaCLI(aqua) => Ok(aqua),
|
||||||
|
Aqua::JetBrainsAqua(_) => {
|
||||||
|
Err(SkipStep("Command `aqua` probably points to JetBrains Aqua".to_string()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn jetbrains_aqua(self) -> Result<PathBuf> {
|
||||||
|
match self {
|
||||||
|
Aqua::JetBrainsAqua(path) => Ok(path),
|
||||||
|
Aqua::AquaCLI(_) => Err(SkipStep("Command `aqua` probably points to Aqua CLI".to_string()).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_aqua(ctx: &ExecutionContext) -> Result<Aqua> {
|
||||||
let aqua = require("aqua")?;
|
let aqua = require("aqua")?;
|
||||||
|
|
||||||
// Check if `aqua --help` mentions "aqua". JetBrains aqua does not, aqua CLI does.
|
// Check if `aqua --help` mentions "aqua". JetBrains Aqua does not, Aqua CLI does.
|
||||||
let output = ctx.run_type().execute(&aqua).arg("--help").output_checked()?;
|
let output = ctx.run_type().execute(&aqua).arg("--help").output_checked()?;
|
||||||
|
|
||||||
if !String::from_utf8(output.stdout)?.contains("aqua") {
|
if String::from_utf8(output.stdout)?.contains("aqua") {
|
||||||
return Err(SkipStep("Command aqua probably points to JetBrains Aqua".to_string()).into());
|
debug!("Detected `aqua` as Aqua CLI");
|
||||||
|
Ok(Aqua::AquaCLI(aqua))
|
||||||
|
} else {
|
||||||
|
debug!("Detected `aqua` as JetBrains Aqua");
|
||||||
|
Ok(Aqua::JetBrainsAqua(aqua))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let aqua = get_aqua(ctx)?.aqua_cli()?;
|
||||||
|
|
||||||
print_separator("Aqua");
|
print_separator("Aqua");
|
||||||
if ctx.run_type().dry() {
|
if ctx.run_type().dry() {
|
||||||
@@ -410,88 +438,86 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make VSCodium a separate step because:
|
/// This functions runs for both VSCode and VSCodium, as most of the process is the same for both.
|
||||||
///
|
fn run_vscode_compatible<const VSCODIUM: bool>(ctx: &ExecutionContext) -> Result<()> {
|
||||||
/// 1. Users could use both VSCode and VSCodium
|
// Calling VSCode/VSCodium in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
||||||
/// 2. Just in case, VSCodium could have incompatible changes with VSCode
|
|
||||||
pub fn run_vscodium_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
|
||||||
// Calling vscodoe in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
|
||||||
if is_wsl()? {
|
if is_wsl()? {
|
||||||
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let vscodium = require("codium")?;
|
let name = if VSCODIUM { "VSCodium" } else { "VSCode" };
|
||||||
|
let bin_name = if VSCODIUM { "codium" } else { "code" };
|
||||||
|
let bin = require(bin_name)?;
|
||||||
|
|
||||||
// VSCode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
// VSCode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
||||||
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
||||||
//
|
//
|
||||||
// This should apply to VSCodium as well.
|
// This should apply to VSCodium as well.
|
||||||
let version: Result<Version> = match Command::new(&vscodium)
|
let version: Result<Version> = match Command::new(&bin)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.output_checked_utf8()?
|
.output_checked_utf8()?
|
||||||
.stdout
|
.stdout
|
||||||
.lines()
|
.lines()
|
||||||
.next()
|
.next()
|
||||||
{
|
{
|
||||||
Some(item) => Version::parse(item).map_err(std::convert::Into::into),
|
Some(item) => {
|
||||||
_ => return Err(SkipStep(String::from("Cannot find vscodium version")).into()),
|
// Strip leading zeroes because `semver` does not allow them, but VSCodium uses them sometimes.
|
||||||
|
// This is not the case for VSCode, but just in case, and it can't really cause any issues.
|
||||||
|
let item = item
|
||||||
|
.split('.')
|
||||||
|
.map(|s| if s == "0" { "0" } else { s.trim_start_matches('0') })
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(".");
|
||||||
|
Version::parse(&item).map_err(std::convert::Into::into)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(eyre!(output_changed_message!(
|
||||||
|
&format!("{bin_name} --version"),
|
||||||
|
"No first line"
|
||||||
|
)))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
|
// Raise any errors in parsing the version
|
||||||
return Err(SkipStep(String::from(
|
// The benefit of handling VSCodium versions so old that the version format is something
|
||||||
"Too old vscodium version to have update extensions command",
|
// unexpected is outweighed by the benefit of failing fast on new breaking versions
|
||||||
))
|
let version =
|
||||||
.into());
|
version.wrap_err_with(|| output_changed_message!(&format!("{bin_name} --version"), "Invalid version"))?;
|
||||||
|
debug!("Detected {name} version as: {version}");
|
||||||
|
|
||||||
|
if version < Version::new(1, 86, 0) {
|
||||||
|
return Err(SkipStep(format!("Too old {name} version to have update extensions command")).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
print_separator("VSCodium extensions");
|
print_separator(if VSCODIUM {
|
||||||
|
"VSCodium extensions"
|
||||||
|
} else {
|
||||||
|
"Visual Studio Code extensions"
|
||||||
|
});
|
||||||
|
|
||||||
ctx.run_type()
|
let mut cmd = ctx.run_type().execute(bin);
|
||||||
.execute(vscodium)
|
// If its VSCode (not VSCodium)
|
||||||
.arg("--update-extensions")
|
if !VSCODIUM {
|
||||||
.status_checked()
|
// And we have configured use of a profile
|
||||||
|
if let Some(profile) = ctx.config().vscode_profile() {
|
||||||
|
// Add the profile argument
|
||||||
|
cmd.arg("--profile").arg(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.arg("--update-extensions").status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make VSCodium a separate step because:
|
||||||
|
///
|
||||||
|
/// 1. Users could use both VSCode and VSCodium
|
||||||
|
/// 2. Just in case, VSCodium could have incompatible changes with VSCode
|
||||||
|
pub fn run_vscodium_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_vscode_compatible::<true>(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
// Calling vscode in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
run_vscode_compatible::<false>(ctx)
|
||||||
if is_wsl()? {
|
|
||||||
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let vscode = require("code")?;
|
|
||||||
|
|
||||||
// Vscode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
|
||||||
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
|
||||||
let version: Result<Version> = match Command::new(&vscode)
|
|
||||||
.arg("--version")
|
|
||||||
.output_checked_utf8()?
|
|
||||||
.stdout
|
|
||||||
.lines()
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
Some(item) => Version::parse(item).map_err(std::convert::Into::into),
|
|
||||||
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
|
|
||||||
return Err(SkipStep(String::from("Too old vscode version to have update extensions command")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
print_separator("Visual Studio Code extensions");
|
|
||||||
|
|
||||||
if let Some(profile) = ctx.config().vscode_profile() {
|
|
||||||
ctx.run_type()
|
|
||||||
.execute(vscode)
|
|
||||||
.arg("--profile")
|
|
||||||
.arg(profile)
|
|
||||||
.arg("--update-extensions")
|
|
||||||
.status_checked()
|
|
||||||
} else {
|
|
||||||
ctx.run_type()
|
|
||||||
.execute(vscode)
|
|
||||||
.arg("--update-extensions")
|
|
||||||
.status_checked()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -647,9 +673,12 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
{
|
{
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
let stdout = output.stdout.trim();
|
let stdout = output.stdout.trim();
|
||||||
stdout
|
stdout.parse::<bool>().wrap_err_with(|| {
|
||||||
.parse::<bool>()
|
output_changed_message!(
|
||||||
.expect("unexpected output that is not `true` or `false`")
|
"pip config get global.break-system-packages",
|
||||||
|
"unexpected output that is not `true` or `false`"
|
||||||
|
)
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
// it can fail because this key may not be set
|
// it can fail because this key may not be set
|
||||||
//
|
//
|
||||||
@@ -976,8 +1005,39 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Hx {
|
||||||
|
Helix(PathBuf),
|
||||||
|
HxHexdump,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hx {
|
||||||
|
fn helix(self) -> Result<PathBuf> {
|
||||||
|
match self {
|
||||||
|
Hx::Helix(hx) => Ok(hx),
|
||||||
|
Hx::HxHexdump => {
|
||||||
|
Err(SkipStep("Command `hx` probably points to hx (hexdump alternative)".to_string()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hx(ctx: &ExecutionContext) -> Result<Hx> {
|
||||||
|
let hx = require("hx")?;
|
||||||
|
|
||||||
|
// Check if `hx --help` mentions "helix". Helix does, hx (hexdump alternative) doesn't.
|
||||||
|
let output = ctx.run_type().execute(&hx).arg("--help").output_checked()?;
|
||||||
|
|
||||||
|
if String::from_utf8(output.stdout)?.contains("helix") {
|
||||||
|
debug!("Detected `hx` as Helix");
|
||||||
|
Ok(Hx::Helix(hx))
|
||||||
|
} else {
|
||||||
|
debug!("Detected `hx` as hx (hexdump alternative)");
|
||||||
|
Ok(Hx::HxHexdump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let helix = require("helix").or(require("hx"))?;
|
let helix = require("helix").or(get_hx(ctx)?.helix())?;
|
||||||
|
|
||||||
print_separator("Helix");
|
print_separator("Helix");
|
||||||
|
|
||||||
@@ -1169,11 +1229,11 @@ pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
// Parse the standard Unix shebang line: #!interpreter [optional-arg]
|
// Parse the standard Unix shebang line: #!interpreter [optional-arg]
|
||||||
// Spaces and tabs on either side of interpreter are ignored.
|
// Spaces and tabs on either side of interpreter are ignored.
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
lazy_static! {
|
static SHEBANG_REGEX: LazyLock<Regex> =
|
||||||
static ref SHEBANG_REGEX: Regex = Regex::new(r"^#![ \t]*([^ \t\n]+)(?:[ \t]+([^\n]+)?)?").unwrap();
|
LazyLock::new(|| Regex::new(r"^#![ \t]*([^ \t\n]+)(?:[ \t]+([^\n]+)?)?").unwrap());
|
||||||
}
|
|
||||||
|
|
||||||
let script = fs::read(poetry)?;
|
let script = fs::read(poetry)?;
|
||||||
if let Some(c) = SHEBANG_REGEX.captures(&script) {
|
if let Some(c) = SHEBANG_REGEX.captures(&script) {
|
||||||
@@ -1192,10 +1252,8 @@ pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
lazy_static! {
|
static SHEBANG_REGEX: LazyLock<Regex> =
|
||||||
static ref SHEBANG_REGEX: Regex =
|
LazyLock::new(|| Regex::new(r#"^#![ \t]*(?:"([^"\n]+)"|([^" \t\n]+))(?:[ \t]+([^\n]+)?)?"#).unwrap());
|
||||||
Regex::new(r#"^#![ \t]*(?:"([^"\n]+)"|([^" \t\n]+))(?:[ \t]+([^\n]+)?)?"#).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = fs::read(poetry)?;
|
let data = fs::read(poetry)?;
|
||||||
|
|
||||||
@@ -1277,24 +1335,117 @@ pub fn run_uv(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let uv_exec = require("uv")?;
|
let uv_exec = require("uv")?;
|
||||||
print_separator("uv");
|
print_separator("uv");
|
||||||
|
|
||||||
// try uv self --help first - if it succeeds, we call uv self update
|
// 1. Run `uv self update` if the `uv` binary is built with the `self-update`
|
||||||
let result = ctx
|
// cargo feature enabled.
|
||||||
|
//
|
||||||
|
// To check if this feature is enabled or not, different version of `uv` need
|
||||||
|
// different approaches, we need to know the version first and handle them
|
||||||
|
// separately.
|
||||||
|
let uv_version_output = ctx
|
||||||
.run_type()
|
.run_type()
|
||||||
.execute(&uv_exec)
|
.execute(&uv_exec)
|
||||||
.args(["self", "--help"])
|
.arg("--version")
|
||||||
.output_checked();
|
.output_checked_utf8()?;
|
||||||
|
// Multiple possible output formats are possible according to uv source code
|
||||||
|
//
|
||||||
|
// https://github.com/astral-sh/uv/blob/6b7f60c1eaa840c2e933a0fb056ab46f99c991a5/crates/uv-cli/src/version.rs#L28-L42
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// "uv 0.5.11 (c4d0caaee 2024-12-19)\n"
|
||||||
|
// "uv 0.5.11+1 (xxxd0cee 2024-12-20)\n"
|
||||||
|
// "uv 0.6.14\n"
|
||||||
|
|
||||||
if result.is_ok() {
|
let uv_version_output_stdout = uv_version_output.stdout;
|
||||||
ctx.run_type()
|
|
||||||
|
let version_str = {
|
||||||
|
// Trim the starting "uv" and " " (whitespace)
|
||||||
|
let start_trimmed = uv_version_output_stdout
|
||||||
|
.trim_start_matches("uv")
|
||||||
|
.trim_start_matches(' ');
|
||||||
|
// Remove the tailing part " (c4d0caaee 2024-12-19)\n", if it's there
|
||||||
|
match start_trimmed.find(' ') {
|
||||||
|
None => start_trimmed.trim_end_matches('\n'), // Otherwise, just strip the newline
|
||||||
|
Some(i) => &start_trimmed[..i],
|
||||||
|
}
|
||||||
|
|
||||||
|
// After trimming, it should be a string in 2 possible formats, both can be handled by `Version::parse()`
|
||||||
|
//
|
||||||
|
// 1. "0.5.11"
|
||||||
|
// 2. "0.5.11+1"
|
||||||
|
};
|
||||||
|
let version =
|
||||||
|
Version::parse(version_str).wrap_err_with(|| output_changed_message!("uv --version", "Invalid version"))?;
|
||||||
|
|
||||||
|
if version < Version::new(0, 4, 25) {
|
||||||
|
// For uv before version 0.4.25 (exclusive), the `self` sub-command only
|
||||||
|
// exists under the `self-update` feature, we run `uv self --help` to check
|
||||||
|
// the feature gate.
|
||||||
|
let self_update_feature_enabled = ctx
|
||||||
|
.run_type()
|
||||||
|
.execute(&uv_exec)
|
||||||
|
.args(["self", "--help"])
|
||||||
|
.output_checked()
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if self_update_feature_enabled {
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(&uv_exec)
|
||||||
|
.args(["self", "update"])
|
||||||
|
.status_checked()?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// After 0.4.25 (inclusive), running `uv self` succeeds regardless of the
|
||||||
|
// feature gate, so the above approach won't work.
|
||||||
|
//
|
||||||
|
// We run `uv self update` directly, if it outputs:
|
||||||
|
//
|
||||||
|
// "uv was installed through an external package manager, and self-update is not available. Please use your package manager to update uv.\n"
|
||||||
|
|
||||||
|
const ERROR_MSG: &str = "uv was installed through an external package manager, and self-update is not available. Please use your package manager to update uv.";
|
||||||
|
|
||||||
|
let output = ctx
|
||||||
|
.run_type()
|
||||||
.execute(&uv_exec)
|
.execute(&uv_exec)
|
||||||
.args(["self", "update"])
|
.args(["self", "update"])
|
||||||
.status_checked()?;
|
// `output()` captures the output so that users won't see it for now.
|
||||||
}
|
.output()
|
||||||
|
.expect("this should be ok regardless of this child process's exit code");
|
||||||
|
let output = match output {
|
||||||
|
ExecutorOutput::Wet(wet) => wet,
|
||||||
|
ExecutorOutput::Dry => unreachable!("the whole function returns when we run `uv --version` under dry-run"),
|
||||||
|
};
|
||||||
|
let stderr = std::str::from_utf8(&output.stderr).expect("output should be UTF-8 encoded");
|
||||||
|
|
||||||
|
if stderr.contains(ERROR_MSG) {
|
||||||
|
// Feature `self-update` is disabled, nothing to do.
|
||||||
|
} else {
|
||||||
|
// Feature is enabled, flush the captured output so that users know we did the self-update.
|
||||||
|
|
||||||
|
std::io::stdout().write_all(&output.stdout)?;
|
||||||
|
std::io::stderr().write_all(&output.stderr)?;
|
||||||
|
|
||||||
|
// And, if self update failed, fail the step as well.
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(eyre!("uv self update failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Update the installed tools
|
||||||
ctx.run_type()
|
ctx.run_type()
|
||||||
.execute(&uv_exec)
|
.execute(&uv_exec)
|
||||||
.args(["tool", "upgrade", "--all"])
|
.args(["tool", "upgrade", "--all"])
|
||||||
.status_checked()
|
.status_checked()?;
|
||||||
|
|
||||||
|
if ctx.config().cleanup() {
|
||||||
|
// 3. Prune cache
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(&uv_exec)
|
||||||
|
.args(["cache", "prune"])
|
||||||
|
.status_checked()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Involve `zvm upgrade` to update ZVM
|
/// Involve `zvm upgrade` to update ZVM
|
||||||
@@ -1397,3 +1548,115 @@ pub fn run_jetbrains_toolbox(_ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_jetbrains_ide_generic<const IS_JETBRAINS: bool>(ctx: &ExecutionContext, bin: PathBuf, name: &str) -> Result<()> {
|
||||||
|
let prefix = if IS_JETBRAINS { "JetBrains " } else { "" };
|
||||||
|
print_separator(format!("{prefix}{name} plugins"));
|
||||||
|
|
||||||
|
// The `update` command is undocumented, but tested on all of the below.
|
||||||
|
let output = ctx.run_type().execute(&bin).arg("update").output()?;
|
||||||
|
let output = match output {
|
||||||
|
ExecutorOutput::Dry => return Ok(()),
|
||||||
|
ExecutorOutput::Wet(output) => output,
|
||||||
|
};
|
||||||
|
// Write the output which we swallowed in all cases
|
||||||
|
std::io::stdout().lock().write_all(&output.stdout).unwrap();
|
||||||
|
std::io::stderr().lock().write_all(&output.stderr).unwrap();
|
||||||
|
|
||||||
|
let stdout = String::from_utf8(output.stdout.clone()).wrap_err("Expected valid UTF-8 output")?;
|
||||||
|
|
||||||
|
// "Only one instance of RustRover can be run at a time."
|
||||||
|
if stdout.contains("Only one instance of ") && stdout.contains(" can be run at a time.") {
|
||||||
|
// It's always paired with status code 1
|
||||||
|
let status_code = output
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.ok_or_eyre("Failed to get status code; was killed with signal")?;
|
||||||
|
if status_code != 1 {
|
||||||
|
return Err(eyre!("Expected status code 1 ('Only one instance of <IDE> can be run at a time.'), but found status code {}. Output: {output:?}", status_code));
|
||||||
|
}
|
||||||
|
// Don't crash, but don't be silent either
|
||||||
|
warn!("{name} is already running, can't update it now.");
|
||||||
|
Err(SkipStep(format!("{name} is already running, can't update it now.")).into())
|
||||||
|
} else if !output.status.success() {
|
||||||
|
// Unknown failure
|
||||||
|
Err(eyre!("Running `{bin:?} update` failed. Output: {output:?}"))
|
||||||
|
} else {
|
||||||
|
// Success. Output was already written above
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_jetbrains_ide(ctx: &ExecutionContext, bin: PathBuf, name: &str) -> Result<()> {
|
||||||
|
run_jetbrains_ide_generic::<true>(ctx, bin, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_android_studio(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
// We don't use `run_jetbrains_ide` here because that would print "JetBrains Android Studio",
|
||||||
|
// which is incorrect as Android Studio is made by Google. Just "Android Studio" is fine.
|
||||||
|
run_jetbrains_ide_generic::<false>(ctx, require("studio")?, "Android Studio")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_aqua(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, get_aqua(ctx)?.jetbrains_aqua()?, "Aqua")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_clion(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("clion")?, "CLion")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_datagrip(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("datagrip")?, "DataGrip")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_dataspell(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("dataspell")?, "DataSpell")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_gateway(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("gateway")?, "Gateway")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_goland(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("goland")?, "Goland")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_idea(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("idea")?, "IntelliJ IDEA")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_mps(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("mps")?, "MPS")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_phpstorm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("phpstorm")?, "PhpStorm")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_pycharm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("pycharm")?, "PyCharm")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_rider(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("rider")?, "Rider")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_rubymine(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("rubymine")?, "RubyMine")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_rustrover(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("rustrover")?, "RustRover")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_jetbrains_webstorm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
run_jetbrains_ide(ctx, require("webstorm")?, "WebStorm")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_yazi(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let ya = require("ya")?;
|
||||||
|
|
||||||
|
print_separator("Yazi packages");
|
||||||
|
|
||||||
|
ctx.run_type().execute(ya).args(["pack", "-u"]).status_checked()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::ffi::OsString;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::{Context, Result};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ use crate::error::TopgradeError;
|
|||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::utils::require_option;
|
use crate::utils::require_option;
|
||||||
use crate::utils::which;
|
use crate::utils::which;
|
||||||
use crate::{config, Step};
|
use crate::{config, output_changed_message, Step};
|
||||||
|
|
||||||
fn get_execution_path() -> OsString {
|
fn get_execution_path() -> OsString {
|
||||||
let mut path = OsString::from("/usr/bin:");
|
let mut path = OsString::from("/usr/bin:");
|
||||||
@@ -285,7 +285,8 @@ impl ArchPackageManager for Aura {
|
|||||||
// Output will be something like: "aura x.x.x\n"
|
// Output will be something like: "aura x.x.x\n"
|
||||||
let version_cmd_stdout = version_cmd_output.stdout;
|
let version_cmd_stdout = version_cmd_output.stdout;
|
||||||
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
||||||
let version = Version::parse(version_str).expect("invalid version");
|
let version = Version::parse(version_str)
|
||||||
|
.wrap_err_with(|| output_changed_message!("aura --version", "invalid version"))?;
|
||||||
|
|
||||||
// Aura, since version 4.0.6, no longer needs sudo.
|
// Aura, since version 4.0.6, no longer needs sudo.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ impl Distribution {
|
|||||||
Some("nobara") => Distribution::Nobara,
|
Some("nobara") => Distribution::Nobara,
|
||||||
Some("void") => Distribution::Void,
|
Some("void") => Distribution::Void,
|
||||||
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
|
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
|
||||||
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
|
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") | Some("cachyos") => Distribution::Arch,
|
||||||
Some("solus") => Distribution::Solus,
|
Some("solus") => Distribution::Solus,
|
||||||
Some("gentoo") | Some("funtoo") => Distribution::Gentoo,
|
Some("gentoo") | Some("funtoo") => Distribution::Gentoo,
|
||||||
Some("exherbo") => Distribution::Exherbo,
|
Some("exherbo") => Distribution::Exherbo,
|
||||||
@@ -587,7 +587,11 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
ctx.run_type().execute(&deb_get).arg("upgrade").status_checked()?;
|
ctx.run_type().execute(&deb_get).arg("upgrade").status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
ctx.run_type().execute(&deb_get).arg("clean").status_checked()?;
|
let output = ctx.run_type().execute(&deb_get).arg("clean").output_checked()?;
|
||||||
|
// Swallow the output, as it's very noisy and not useful.
|
||||||
|
// The output is automatically printed as part of `output_checked` when an error occurs.
|
||||||
|
println!("{}", t!("<output from `deb-get clean` omitted>"));
|
||||||
|
debug!("`deb-get clean` output: {output:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1316,4 +1320,9 @@ mod tests {
|
|||||||
fn test_bazzite() {
|
fn test_bazzite() {
|
||||||
test_template(include_str!("os_release/bazzite"), Distribution::FedoraImmutable);
|
test_template(include_str!("os_release/bazzite"), Distribution::FedoraImmutable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cachyos() {
|
||||||
|
test_template(include_str!("os_release/cachyos"), Distribution::Arch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/steps/os/os_release/cachyos
Normal file
11
src/steps/os/os_release/cachyos
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
NAME="CachyOS Linux"
|
||||||
|
PRETTY_NAME="CachyOS"
|
||||||
|
ID=cachyos
|
||||||
|
BUILD_ID=rolling
|
||||||
|
ANSI_COLOR="38;2;23;147;209"
|
||||||
|
HOME_URL="https://cachyos.org/"
|
||||||
|
DOCUMENTATION_URL="https://wiki.cachyos.org/"
|
||||||
|
SUPPORT_URL="https://discuss.cachyos.org/"
|
||||||
|
BUG_REPORT_URL="https://github.com/cachyos"
|
||||||
|
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
|
||||||
|
LOGO=cachyos
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
|
use crate::command::CommandExt;
|
||||||
|
use crate::{output_changed_message, Step, HOME_DIR};
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
|
use color_eyre::eyre::Context;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use home;
|
||||||
|
use ini::Ini;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use nix::unistd::Uid;
|
||||||
|
use regex::Regex;
|
||||||
|
use rust_i18n::t;
|
||||||
|
use semver::Version;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::{env::var, path::Path};
|
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 home;
|
|
||||||
use ini::Ini;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use nix::unistd::Uid;
|
|
||||||
use regex::Regex;
|
|
||||||
use rust_i18n::t;
|
|
||||||
use semver::Version;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -238,7 +237,7 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let gdbus = require("gdbus")?;
|
let gdbus = require("gdbus")?;
|
||||||
require_option(
|
require_option(
|
||||||
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
||||||
t!("Desktop doest not appear to be gnome").to_string(),
|
t!("Desktop does not appear to be GNOME").to_string(),
|
||||||
)?;
|
)?;
|
||||||
let output = Command::new("gdbus")
|
let output = Command::new("gdbus")
|
||||||
.args([
|
.args([
|
||||||
@@ -253,12 +252,12 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
])
|
])
|
||||||
.output_checked_utf8()?;
|
.output_checked_utf8()?;
|
||||||
|
|
||||||
debug!("Checking for gnome extensions: {}", output);
|
debug!("Checking for GNOME extensions: {}", output);
|
||||||
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
||||||
return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
|
return Err(SkipStep(t!("GNOME shell extensions are unregistered in DBus").to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
print_separator(t!("Gnome Shell extensions"));
|
print_separator(t!("GNOME Shell extensions"));
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.run_type()
|
||||||
.execute(gdbus)
|
.execute(gdbus)
|
||||||
@@ -461,28 +460,31 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
"`nix --version` output"
|
"`nix --version` output"
|
||||||
);
|
);
|
||||||
|
|
||||||
lazy_static! {
|
static NIX_VERSION_REGEX: LazyLock<Regex> =
|
||||||
static ref NIX_VERSION_REGEX: Regex =
|
LazyLock::new(|| Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles"));
|
||||||
Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles");
|
|
||||||
}
|
|
||||||
|
|
||||||
if get_version_cmd_first_line_stdout.is_empty() {
|
if get_version_cmd_first_line_stdout.is_empty() {
|
||||||
return Err(eyre!("`nix --version` output was empty"));
|
return Err(eyre!("`nix --version` output was empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let captures = NIX_VERSION_REGEX.captures(get_version_cmd_first_line_stdout);
|
let captures = NIX_VERSION_REGEX
|
||||||
let raw_version = match &captures {
|
.captures(get_version_cmd_first_line_stdout)
|
||||||
None => {
|
.ok_or_else(|| eyre!(output_changed_message!("nix --version", "regex did not match")))?;
|
||||||
return Err(eyre!(
|
let raw_version = &captures[1];
|
||||||
"`nix --version` output was weird: {get_version_cmd_first_line_stdout:?}\n\
|
|
||||||
If the `nix --version` output format changed, please file an issue to Topgrade"
|
debug!("Raw Nix version: {raw_version}");
|
||||||
));
|
|
||||||
}
|
// Nix 2.29.0 outputs "2.29" instead of "2.29.0", so we need to add that if necessary.
|
||||||
Some(captures) => &captures[1],
|
let corrected_raw_version = if raw_version.chars().filter(|&c| c == '.').count() == 1 {
|
||||||
|
&format!("{raw_version}.0")
|
||||||
|
} else {
|
||||||
|
raw_version
|
||||||
};
|
};
|
||||||
|
|
||||||
let version =
|
debug!("Corrected raw Nix version: {corrected_raw_version}");
|
||||||
Version::parse(raw_version).wrap_err_with(|| format!("Unable to parse Nix version: {raw_version:?}"))?;
|
|
||||||
|
let version = Version::parse(corrected_raw_version)
|
||||||
|
.wrap_err_with(|| output_changed_message!("nix --version", "Invalid version"))?;
|
||||||
|
|
||||||
debug!("Nix version: {:?}", version);
|
debug!("Nix version: {:?}", version);
|
||||||
|
|
||||||
@@ -648,15 +650,19 @@ pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
// v0.15.0-31e8c93
|
// v0.15.0-31e8c93
|
||||||
//
|
//
|
||||||
// ```
|
// ```
|
||||||
|
// ```
|
||||||
|
// $ asdf version
|
||||||
|
// v0.16.7
|
||||||
|
// ```
|
||||||
let version_stdout = version_output.stdout.trim();
|
let version_stdout = version_output.stdout.trim();
|
||||||
// trim the starting 'v'
|
// trim the starting 'v'
|
||||||
let mut remaining = version_stdout.trim_start_matches('v');
|
let mut remaining = version_stdout.trim_start_matches('v');
|
||||||
let idx = remaining
|
// remove the hash part if present
|
||||||
.find('-')
|
if let Some(idx) = remaining.find('-') {
|
||||||
.expect("the output of `asdf version` changed, please file an issue to Topgrade");
|
remaining = &remaining[..idx];
|
||||||
// remove the hash part
|
}
|
||||||
remaining = &remaining[..idx];
|
let version =
|
||||||
let version = Version::parse(remaining).expect("should be a valid version");
|
Version::parse(remaining).wrap_err_with(|| output_changed_message!("asdf version", "invalid version"))?;
|
||||||
if version < Version::new(0, 15, 0) {
|
if version < Version::new(0, 15, 0) {
|
||||||
ctx.run_type()
|
ctx.run_type()
|
||||||
.execute(&asdf)
|
.execute(&asdf)
|
||||||
|
|||||||
@@ -42,12 +42,18 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("winget");
|
print_separator("winget");
|
||||||
|
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(&winget)
|
||||||
|
.args(["source", "update"])
|
||||||
|
.status_checked()?;
|
||||||
|
|
||||||
let mut args = vec!["upgrade", "--all"];
|
let mut args = vec!["upgrade", "--all"];
|
||||||
if ctx.config().winget_silent_install() {
|
if ctx.config().winget_silent_install() {
|
||||||
args.push("--silent");
|
args.push("--silent");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type().execute(winget).args(args).status_checked()
|
ctx.run_type().execute(&winget).args(args).status_checked()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -65,7 +71,6 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.args(["cache", "rm", "-a"])
|
.args(["cache", "rm", "-a"])
|
||||||
.status_checked()?
|
.status_checked()?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#[cfg(windows)]
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -18,22 +16,9 @@ pub struct Powershell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Powershell {
|
impl Powershell {
|
||||||
/// Returns a powershell instance.
|
|
||||||
///
|
|
||||||
/// If the powershell binary is not found, or the current terminal is dumb
|
|
||||||
/// then the instance of this struct will skip all the powershell steps.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let path = which("pwsh").or_else(|| which("powershell")).filter(|_| !is_dumb());
|
let path = which("pwsh").or_else(|| which("powershell")).filter(|_| !is_dumb());
|
||||||
|
let profile = path.as_ref().and_then(Self::get_profile);
|
||||||
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()))
|
|
||||||
.and_then(super::super::utils::PathExt::require)
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
Powershell { path, profile }
|
Powershell { path, profile }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,117 +30,178 @@ impl Powershell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn profile(&self) -> Option<&PathBuf> {
|
||||||
|
self.profile.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_profile(path: &PathBuf) -> Option<PathBuf> {
|
||||||
|
Self::execute_with_command(path, &["-NoProfile", "-Command", "Split-Path $PROFILE"], |stdout| {
|
||||||
|
Ok(stdout)
|
||||||
|
})
|
||||||
|
.ok() // Convert the Result<String> to Option<String>
|
||||||
|
.and_then(|s| super::super::utils::PathExt::require(PathBuf::from(s)).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_with_command<F>(path: &PathBuf, args: &[&str], f: F) -> Result<String>
|
||||||
|
where
|
||||||
|
F: FnOnce(String) -> Result<String>,
|
||||||
|
{
|
||||||
|
let output = Command::new(path).args(args).output_checked_utf8()?;
|
||||||
|
let stdout = output.stdout.trim().to_string();
|
||||||
|
f(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a command with common arguments and optional sudo support.
|
||||||
|
fn build_command_internal<'a>(
|
||||||
|
&self,
|
||||||
|
ctx: &'a ExecutionContext,
|
||||||
|
additional_args: &[&str],
|
||||||
|
) -> Result<impl CommandExt + 'a> {
|
||||||
|
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
||||||
|
let executor = &mut ctx.run_type();
|
||||||
|
let mut command = if let Some(sudo) = ctx.sudo() {
|
||||||
|
let mut cmd = executor.execute(sudo);
|
||||||
|
cmd.arg(powershell);
|
||||||
|
cmd
|
||||||
|
} else {
|
||||||
|
executor.execute(powershell)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// Check execution policy and return early if it's not set correctly
|
||||||
|
self.execution_policy_args_if_needed()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
command.args(Self::common_args()).args(additional_args);
|
||||||
|
Ok(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
print_separator(t!("Powershell Modules Update"));
|
||||||
|
let mut cmd_args = vec!["Update-Module"];
|
||||||
|
|
||||||
|
if ctx.config().verbose() {
|
||||||
|
cmd_args.push("-Verbose");
|
||||||
|
}
|
||||||
|
if ctx.config().yes(Step::Powershell) {
|
||||||
|
cmd_args.push("-Force");
|
||||||
|
}
|
||||||
|
println!("{}", t!("Updating modules..."));
|
||||||
|
self.build_command_internal(ctx, &cmd_args)?.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common_args() -> &'static [&'static str] {
|
||||||
|
&["-NoProfile"]
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn has_module(powershell: &Path, command: &str) -> bool {
|
pub fn execution_policy_args_if_needed(&self) -> Result<()> {
|
||||||
|
if !self.is_execution_policy_set("RemoteSigned") {
|
||||||
|
Err(color_eyre::eyre::eyre!(
|
||||||
|
"PowerShell execution policy is too restrictive. \
|
||||||
|
Please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser' in PowerShell \
|
||||||
|
(or use Unrestricted/Bypass if you're sure about the security implications)"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_execution_policy_set(&self, policy: &str) -> bool {
|
||||||
|
if let Some(powershell) = &self.path {
|
||||||
|
// These policies are ordered from most restrictive to least restrictive
|
||||||
|
let valid_policies = ["Restricted", "AllSigned", "RemoteSigned", "Unrestricted", "Bypass"];
|
||||||
|
|
||||||
|
// Find the index of our target policy
|
||||||
|
let target_idx = valid_policies.iter().position(|&p| p == policy);
|
||||||
|
|
||||||
|
let output = Command::new(powershell)
|
||||||
|
.args(["-NoProfile", "-Command", "Get-ExecutionPolicy"])
|
||||||
|
.output_checked_utf8();
|
||||||
|
|
||||||
|
if let Ok(output) = output {
|
||||||
|
let current_policy = output.stdout.trim();
|
||||||
|
|
||||||
|
// Find the index of the current policy
|
||||||
|
let current_idx = valid_policies.iter().position(|&p| p == current_policy);
|
||||||
|
|
||||||
|
// Check if current policy exists and is at least as permissive as the target
|
||||||
|
return match (current_idx, target_idx) {
|
||||||
|
(Some(current), Some(target)) => current >= target,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
impl Powershell {
|
||||||
|
pub fn supports_windows_update(&self) -> bool {
|
||||||
|
windows::supports_windows_update(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
windows::windows_update(self, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn microsoft_store(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
windows::microsoft_store(self, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn supports_windows_update(powershell: &Powershell) -> bool {
|
||||||
|
powershell
|
||||||
|
.path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| has_module(p, "PSWindowsUpdate"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn windows_update(powershell: &Powershell, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
debug_assert!(supports_windows_update(powershell));
|
||||||
|
|
||||||
|
// Build the full command string
|
||||||
|
let mut command_str = "Install-WindowsUpdate -Verbose".to_string();
|
||||||
|
if ctx.config().accept_all_windows_updates() {
|
||||||
|
command_str.push_str(" -AcceptAll");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the command string using the -Command flag
|
||||||
|
powershell
|
||||||
|
.build_command_internal(ctx, &["-Command", &command_str])?
|
||||||
|
.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn microsoft_store(powershell: &Powershell, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
println!("{}", t!("Scanning for updates..."));
|
||||||
|
let update_command = "Start-Process powershell -Verb RunAs -ArgumentList '-Command', \
|
||||||
|
'(Get-CimInstance -Namespace \"Root\\cimv2\\mdm\\dmmap\" \
|
||||||
|
-ClassName \"MDM_EnterpriseModernAppManagement_AppManagement01\" | \
|
||||||
|
Invoke-CimMethod -MethodName UpdateScanMethod).ReturnValue'";
|
||||||
|
|
||||||
|
powershell
|
||||||
|
.build_command_internal(ctx, &["-Command", update_command])?
|
||||||
|
.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_module(powershell: &PathBuf, command: &str) -> bool {
|
||||||
Command::new(powershell)
|
Command::new(powershell)
|
||||||
.args([
|
.args([
|
||||||
"-NoProfile",
|
"-NoProfile",
|
||||||
"-Command",
|
"-Command",
|
||||||
&format!("Get-Module -ListAvailable {command}"),
|
&format!("Get-Module -ListAvailable {}", command),
|
||||||
])
|
])
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map(|result| !result.stdout.is_empty())
|
.map(|result| !result.stdout.is_empty())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profile(&self) -> Option<&PathBuf> {
|
|
||||||
self.profile.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
|
||||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
|
||||||
|
|
||||||
print_separator(t!("Powershell Modules Update"));
|
|
||||||
|
|
||||||
let mut cmd = vec!["Update-Module"];
|
|
||||||
|
|
||||||
if ctx.config().verbose() {
|
|
||||||
cmd.push("-Verbose");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.config().yes(Step::Powershell) {
|
|
||||||
cmd.push("-Force");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", t!("Updating modules..."));
|
|
||||||
ctx.run_type()
|
|
||||||
.execute(powershell)
|
|
||||||
// This probably doesn't need `shell_words::join`.
|
|
||||||
.args(["-NoProfile", "-Command", &cmd.join(" ")])
|
|
||||||
.status_checked()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn supports_windows_update(&self) -> bool {
|
|
||||||
self.path
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| Self::has_module(p, "PSWindowsUpdate"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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())?;
|
|
||||||
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
ctx.run_type().execute(powershell)
|
|
||||||
};
|
|
||||||
|
|
||||||
command
|
|
||||||
.args(["-NoProfile", &install_windowsupdate_verbose, accept_all])
|
|
||||||
.status_checked()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn microsoft_store(&self, ctx: &ExecutionContext) -> Result<()> {
|
|
||||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
|
||||||
|
|
||||||
let mut command = if let Some(sudo) = ctx.sudo() {
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
|
||||||
command.arg(powershell);
|
|
||||||
command
|
|
||||||
} else {
|
|
||||||
ctx.run_type().execute(powershell)
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{}", t!("Scanning for updates..."));
|
|
||||||
|
|
||||||
// Scan for updates using the MDM UpdateScanMethod
|
|
||||||
// This method is also available for non-MDM devices
|
|
||||||
let update_command = "(Get-CimInstance -Namespace \"Root\\cimv2\\mdm\\dmmap\" -ClassName \"MDM_EnterpriseModernAppManagement_AppManagement01\" | Invoke-CimMethod -MethodName UpdateScanMethod).ReturnValue";
|
|
||||||
|
|
||||||
command.args(["-NoProfile", update_command]);
|
|
||||||
|
|
||||||
command
|
|
||||||
.output_checked_with_utf8(|output| {
|
|
||||||
if output.stdout.trim() == "0" {
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
t!("Success, Microsoft Store apps are being updated in the background")
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
t!("Unable to update Microsoft Store apps, manual intervention is required")
|
|
||||||
);
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ use std::cmp::{max, min};
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::Mutex;
|
use std::sync::{LazyLock, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::{Local, Timelike};
|
use chrono::{Local, Timelike};
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use console::{style, Key, Term};
|
use console::{style, Key, Term};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use notify_rust::{Notification, Timeout};
|
use notify_rust::{Notification, Timeout};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
@@ -19,9 +18,7 @@ use which_crate::which;
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::report::StepResult;
|
use crate::report::StepResult;
|
||||||
|
|
||||||
lazy_static! {
|
static TERMINAL: LazyLock<Mutex<Terminal>> = LazyLock::new(|| Mutex::new(Terminal::new()));
|
||||||
static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn shell() -> String {
|
pub fn shell() -> String {
|
||||||
|
|||||||
12
src/utils.rs
12
src/utils.rs
@@ -282,3 +282,15 @@ pub fn install_color_eyre() -> Result<()> {
|
|||||||
.display_location_section(true)
|
.display_location_section(true)
|
||||||
.install()
|
.install()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Macro to construct an error message for when the output of a command is unexpected.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! output_changed_message {
|
||||||
|
($command:expr, $message:expr) => {
|
||||||
|
format!(
|
||||||
|
"The output of `{}` changed: {}. This is not your fault, this is an issue in Topgrade. Please open an issue at: https://github.com/topgrade-rs/topgrade/issues/new?template=bug_report.md",
|
||||||
|
$command,
|
||||||
|
$message,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user