Compare commits

...

54 Commits

Author SHA1 Message Date
SteveLauC
4488f3d5d3 chore: bump version to 16.0.3 (#1094) 2025-04-03 17:56:01 +08:00
Gideon
5a7958d20e Fix aqua CLI and JetBrains Aqua conflict (#1092) 2025-04-03 17:40:52 +08:00
Gideon
481a942b76 Fix pixi self-update running when pixi is not installed with the … (#1087)
* Fix `pixi self-update` running when `pixi` is not installed with the `self-update` feature

* Format
2025-04-03 17:38:38 +08:00
Red Wizard
a601d8429d added silent install option for winget (#1089)
* added silent install option for winget

* corrected formatting issues.

* Update src/steps/os/windows.rs

Remove code duplication.

Co-authored-by: SteveLauC <stevelauc@outlook.com>

---------

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2025-03-30 21:11:04 +08:00
tdslot
a4a2d52a6d i18n(app.yml): new language lt (#1069)
* i18n(app.yml): new language lt

Lithuanian language.

* 🌐 i18n(app.yml): update translations for WSL error message

- resolve merge conflict in translation strings
- update spanish and french translations for clarity
- add lithuanian translation for WSL error message
- standardize zh_TW translation format

* 🌐 i18n(locales): add missing translations

- add zh_CN translation for "Topgrade not found in any WSL distribution"
- add lt translations for JetBrains Toolbox related messages
- add lt translations for operating system and updater error messages

* 🌐 i18n(locales): update spanish translation for WSL error message

- improve accuracy of spanish translation for "Could not find Topgrade in any WSL distribution"
- change from "Topgrade no se ha instalado dentro de WSL" to "No se pudo encontrar Topgrade en ninguna distribución WSL"
2025-03-30 14:50:37 +08:00
Nils
47fa3ba7de Add German translations to localization file (#1065)
* Add German translations to localization file

* refactor(localization): remove unused/duplicate translations from app.yml

* i18n(app.yml): add German translations for JetBrains Toolbox messages

* chore: Convert locales/app.yml from CRLF to LF line endings

* Update locales/app.yml

* fix: correct German translation for "Breaking changes"

---------

Co-authored-by: nistee <lo9s4b7qp@mozmail.com>
Co-authored-by: SteveLauC <stevelauc@outlook.com>
2025-03-26 08:59:37 +08:00
Gideon
e6bb6709b3 Update jetbrains-toolbox-updater (#1077) 2025-03-25 10:40:04 +08:00
Alexandre Veyrenc
c421742c4f fix:(emacs): fix issue #1075 (#1076) 2025-03-21 17:01:58 +08:00
ZeroDegress
1312cc8f6e i18n(app.yml): new language zh_CN (#1072) 2025-03-19 09:24:10 +08:00
Gideon
ed37763d30 Add JetBrains Toolbox via jetbrains-toolbox-updater (#1064)
* Add jetbrains-toolbox-updater

* Update jetbrains-toolbox-updater

* Update jetbrains-toolbox-updater

* Update jetbrains-toolbox-updater

* Localize prints

* Update jetbrains-toolbox-updater

* Format

* Add localization

* Fix translation
2025-03-18 11:19:37 +08:00
Justin
583bbf65e2 docs: fix --log-filter link in --help (#1073)
docs: fix EnvFilter link
2025-03-17 14:07:40 +08:00
Izzy Meyer
5770a5caa7 FIX: Allow for -beta OR -current detection and use the correct system upgrade command for the OpenBSD step (#1066)
* FIX: Allow for -beta OR -current detection and use the correct system upgrade command for the OpenBSD step

* FIX: Run fmt, clippy, and test
2025-03-13 09:02:45 +08:00
Gijs Key
722903fec3 Create Armv7l debian package (#1068)
* Create Armv7l debian package

* returned inadvertently removed comment.

---------

Co-authored-by: Gijs Keij <gijs.keij@bit-key.nl>
2025-03-12 11:20:19 +08:00
Xarblu
30f1c3c1b4 feat(sudo): add run0 as a sudo variant (#1067) 2025-03-12 09:07:37 +08:00
dependabot[bot]
ef7d146282 chore(deps): bump ring from 0.17.8 to 0.17.13 (#1062)
Bumps [ring](https://github.com/briansmith/ring) from 0.17.8 to 0.17.13.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

---
updated-dependencies:
- dependency-name: ring
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-09 10:47:33 +08:00
Andreas02-dev
20667a23d3 fix(sudo): fix sudo detection & pre-sudo for GSudo (#1058) 2025-03-06 08:51:40 +08:00
JaRoSchm
26f05827ae feat(pixi): add support for pixi global (#1037)
feat(pixi): add support for pixi global
2025-03-05 08:58:21 +08:00
Max Kapur
b1ffe7d553 Run conda clean after conda update if cleanup = true (#1047)
* Run `conda clean` after `conda upgrade` if `cleanup = true`

* Also run `mamba clean`
2025-03-04 09:15:02 +08:00
yggdr
368a060529 Add pipxu step (#1052) 2025-03-04 09:11:57 +08:00
Gideon
b40bffb1f2 Add "Cinnamon spices" step (#1055)
* Add "Cinnamon spices" step

* Format

* Move step to Linux
2025-03-04 08:57:12 +08:00
Andre Toerien
488ae149f7 fix(poetry): parse arg in script shebang line (#1028)
* fix(poetry): parse arg in script shebang line

* fix(poetry): improved shebang line parsing on windows
2025-02-25 20:00:53 +08:00
Tom van Dijk
fa3e4726b7 fix: uBlue OS should be detected as FedoraImmutable (#1043)
* refactor(parse_os_release): Don't rely on specific `ID`s for Fedora Immutable

Instead match `ID=fedora` or `ID_LIKE=fedora` and decide wether or not
the distro is immutable by `VARIANT`.

* feat: add aurora,bluefin,coreos support

The `os_release`s came from the following images:

- ghcr.io/ublue-os/aurora:93f0fd9f20b3
- ghcr.io/ublue-os/bluefin:5d37394a5d4b
- ghcr.io/ublue-os/ucore:63cd1200c344

* fix: bazzite should be detected as FedoraImmutable

* squash me: cargo fmt

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
2025-02-17 09:41:34 +08:00
Ivan Andre Scheel
66a12cc8bf feat(vscode): updated extensions for a given profile (#1022)
* [feat] select user profile for vscode

* [feat] Update example config file

* [fix] Remove unneeded imports

* [feat] PR comments

* [fix] formatting
2025-02-11 12:51:52 +08:00
Alex Böhm
3e0c21e981 docs: fix typo in description (#1032) 2025-02-08 20:03:32 +08:00
Laura Demkowicz-Duffy
da270ae7d9 Add zigup step (#1030)
* feat: add zigup step

* feat(zigup): add various configuration options

* feat(zigup): add cleanup option

* feat(zigup): multiple version support and cleanup

* refactor(zigup): remove set_default and simplify execution

* fix(zigup): always pass path args to zigup for consistent behaviour

* refactor(zigup): use shellexpand to expand tildes
2025-02-08 14:25:10 +08:00
Laura Demkowicz-Duffy
4624f11ba5 Run juliaup gc if cleanup is enabled (#1031)
refactor(juliaup): run juliaup gc if cleanup is enabled
2025-02-08 14:15:25 +08:00
Dan Sully
224bb96a98 chore: update toolchain to 1.84.1. apply clippy fixes & rustfmt (#1026)
* chore: update to stable toolchain. apply clippy fixes & rustfmt

* Bump MSRV

* Try MSRV without the patch version

* fix: pin toolchain to MSRV

* trying again

* fix dead code warning

---------

Co-authored-by: Dan Sully <dsully@users.noreply.github.com>
2025-02-03 11:24:57 +08:00
SteveLauC
9a6fe8eea9 feat: support VSCodium (#788) 2025-01-09 10:35:45 +08:00
SteveLauC
aebc035ec0 fix: do not run asdf update if version >= 0.15.0 (#1008) 2024-12-20 13:31:29 +08:00
LILAY
bd348c328e Add Fedora Copr to Readme.md (#1005)
Update README.md
2024-12-12 10:01:10 +08:00
Samuel Grahn
c5f2d7b473 Detect Elan self update disabled (e.g. installed from distro repos) (#998)
* Add config option for Elan self-update

* Format & Config

* Revert "Format & Config"

This reverts commit 9eedecce8b312f8ad60563488c98cccfd50c0173.

* Revert "Add config option for Elan self-update"

This reverts commit 8c80c7a7d63ecd0936e0bd5cb07c2cbb1452c1fd.

* Allow self-update to fail when disabled

* Formatting

* Don't print in case of failed self-update

* Formatting

* Use the code suggested :)

* Follow the recommendations by Clippy
2024-12-12 09:19:03 +08:00
SteveLauC
dc9d8d55f2 fix: Executor::spawn()/output() should not use their _checked() variants (#1002) 2024-12-11 09:12:57 +08:00
Steve Lau
b172ba7f03 fix: Executor::spawn()/output() should not use their _checked() variants 2024-12-11 08:59:19 +08:00
SteveLauC
8227890808 refactor(uv step): check self update result if self-update feat is available (#1000)
refactor: check self update result if self-update feat is available
2024-12-10 20:55:32 +08:00
befanyt
a0963fe3fc fix: dont ignore rpm-ostree when bootc is found (#999) 2024-12-10 13:00:37 +08:00
SteveLauC
4df30c2587 chore: release v16.0.2 (#995) 2024-12-07 15:21:19 +08:00
Andre Toerien
305a5fbcae fix(poetry): skip if not installed with official script (#989)
* fix(poetry): skip if not installed with official script

* feat(poetry): add poetry_force_self_update config option

* docs: give this config a more detailed explanation

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
2024-12-07 15:09:52 +08:00
Tulip Blossom
4f4dcbb643 feat: add bootc support to Fedora atomic distros
* feat(bootc): add Bootc support + docs

Co-authored-by: Steve Lau <stevelauc@outlook.com>

* docs(bootc): specify that itll supercede rpm-ostree if enabled :p

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
2024-11-19 11:07:12 +08:00
Laura Demkowicz-Duffy
202897ba35 refactor: disable julia startup file for julia package update (#983)
* refactor(julia): disable julia startup file for julia package update

* feat(julia): add configuration option for julia startup file

* fix: deny unknown fields on JuliaConfig deserialisation

Co-authored-by: SteveLauC <stevelauc@outlook.com>

* doc(julia): clarify startup_file option purpose

---------

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2024-11-19 09:17:51 +08:00
Youn Mélois
444689c899 feat: allow version specification for deno (#970)
* feat: allow version specification for deno

* fix: missing quotes for string in toml file

Co-authored-by: SteveLauC <stevelauc@outlook.com>

* fix: deno upgrade for different executable versions

* fix: tell apart the two cases for v1.x in SkipStep reason

* docs: add comments and documentation on version method for deno

* chore: add explanatory comment on stable channel that does nothing

Co-authored-by: SteveLauC <stevelauc@outlook.com>

---------

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2024-10-29 18:09:47 +08:00
Gudsfile
98ec13f8db i18n(app.yml): new language fr (#969)
Apply suggestions from code review

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2024-10-29 16:34:44 +08:00
Lucas Parzianello
39f76a3a71 uv step: checking self subcommand exits; fixes #942 (#971)
* uv step: checking self subcommand exits; fixes #942

* uv: fixing return behavior

---------

Co-authored-by: Lucas Parzianello <lucaspar@users.noreply.github.com>
2024-10-29 15:40:31 +08:00
Ricardo Torres
f181a795a6 refactor: flip order of mise upgrade and mise plugins update (#968)
flip order of mise plugins update and mise upgrade to attempt updating plugins first.
2024-10-28 09:59:22 +08:00
Andreas02-dev
ea2f3e07e9 feat(microsoft_store): Add Microsoft Store step for Windows (#963)
* feat(microsoft_store): Add Microsoft Store step for Windows

Add Microsoft Store Apps update step for Windows as Winget cannot update all Microsoft Store apps yet.

Closes #912

* style(translation): modify `zh_TW` translation
2024-10-23 08:15:46 +08:00
SteveLauC
8aad6eae0d refactor: add missing i18n for OpenBSD steps (#965) 2024-10-22 08:47:15 +08:00
SteveLauC
e86e5fe3e7 docs: document that we need to translate user-facing texts (#966) 2024-10-22 08:46:59 +08:00
λP.(P izzy)
2c2569c4f8 Improve OpenBSD -CURRENT detection and Dry-run feedback (#954)
* Improve OpenBSD -CURRENT detection and Dry-run feedback

This commit improves the -CURRENT detection by way of parsing `/etc/motd`. This change is more future-proof as when OpenBSD nears a stable release, `uname` will temporarily report like -STABLE.

This commit *also* adds feedback if -CURRENT is found to make debugging this feature easier with `--dry-run`, or, just a regular run as well.

* Make OpenBSD step less talky and improve verbiage.

This commit removes the command flag feedback. This commit also swaps the output "update", for "upgrade", making this step closer to other steps for consistency.
2024-10-18 08:26:27 +08:00
Rebecca Turner
9ffdc9649e Add support for Lix (Nix fork) (#952)
Add support for Lix

Lix is a fork of Nix 2.18 focused on maintainability and user
experience. It has a different format for the version, to distinguish it
from CppNix:

    $ nix --version
    nix (Lix, like Nix) 2.91.0

See: <https://lix.systems/>
2024-10-18 08:23:25 +08:00
Rikiub%
a5d4f2eec9 i18n (app.yml): Add Spanish localization (es) (#955)
* Update app.yml

* "es" localization added

* Grammar fixes

* Fix YAML syntax errors

* Fix YAML syntax errors

* Fix duplicated

* Fix duplicate

* Grammar fix

* Grammar fix

* Fix duplicate

* Improve grammar

* Update locales/app.yml

Co-authored-by: SteveLauC <stevelauc@outlook.com>

* Improve Grammar

* Improve Grammar

* Improve Grammar

* Improve Grammar

* Improve Grammar

---------

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2024-10-17 08:04:49 +08:00
Nils
a5df40e01d Refactor config.rs and vagrant.rs files (#949)
* Refactor config.rs and vagrant.rs files

* Refactor config.rs and vagrant.rs files
2024-10-15 17:56:03 +08:00
SteveLauC
0573fc97c6 docs: update release procedure that SECURITY.md should be updated in major release (#946)
docs: update release procedure that SECURITY.md should be updated in major releases
2024-10-14 17:01:22 +08:00
Nils
1ae95f41a1 Update SECURITY.md (#945) 2024-10-14 16:37:15 +08:00
λP.(P izzy)
8a7af2e14d [FIXES #922] properly check for -CURRENT in OpenBSD steps and pass the correct flags to the respective commands (#923)
* [FIXES #922] properly check for -CURRENT in openbsd steps and pass the correct flags

* un-break ctx.config().dry_run() on OpenBSD Step
2024-10-14 08:29:51 +08:00
Nicolas Lorin
c36da89933 ci: add bin pkg to aur (#944) 2024-10-13 21:14:28 +08:00
42 changed files with 2214 additions and 335 deletions

View File

@@ -3,9 +3,10 @@
## Standards checklist
- [ ] The PR title is descriptive.
- [ ] The PR title is descriptive
- [ ] I have read `CONTRIBUTING.md`
- [ ] *Optional:* I have tested the code myself
- [ ] If this PR introduces new user-facing messages they are translated
## For new steps

View File

@@ -6,20 +6,21 @@ on:
# types:
# - completed
release:
types: [ created ]
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",
]
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
@@ -29,9 +30,14 @@ jobs:
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' }}
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
shell: bash
- name: install targets
@@ -75,14 +81,14 @@ jobs:
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' }}
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' }}
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
shell: bash
- name: Release

View File

@@ -13,7 +13,7 @@ jobs:
aur-publish:
runs-on: ubuntu-latest
steps:
- name: Publish AUR package
- name: Publish source AUR package
uses: aksh1618/update-aur-package@v1.0.5
with:
tag_version_prefix: v
@@ -21,3 +21,11 @@ jobs:
commit_username: "Thomas Schönauer"
commit_email: t.schoenauer@hgs-wt.at
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
- name: Publish binary AUR package
uses: aksh1618/update-aur-package@v1.0.5
with:
tag_version_prefix: v
package_name: topgrade-bin
commit_username: "Thomas Schönauer"
commit_email: t.schoenauer@hgs-wt.at
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}

View File

@@ -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".

View File

@@ -129,6 +129,24 @@ $ cargo test
Don't worry about other platforms, we have most of them covered in our CI.
## I18n
If your PR introduces user-facing messages, we need to ensure they are translated.
Please add the translations to [`locales/app.yml`][app_yml]. For simple messages
without arguments (e.g., "hello world"), we can simply translate them according
(Tip: ChatGPT or similar LLMs is good at translation). If a message contains
arguments, e.g., "hello <NAME>", please follow this convention:
```yml
"hello {name}": # key
en: "hello %{name}" # translation
```
Arguments in the key should be in format `{argument_name}`, and they will have
a preceeding `%` when used in translations.
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
## Some tips
1. Locale

300
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -202,7 +202,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -237,7 +237,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -355,9 +355,12 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.99"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
@@ -388,7 +391,7 @@ dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -431,7 +434,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -533,9 +536,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
@@ -620,7 +623,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -658,7 +661,16 @@ version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys 0.5.0",
]
[[package]]
@@ -679,10 +691,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"redox_users 0.4.5",
"windows-sys 0.48.0",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users 0.5.0",
"windows-sys 0.59.0",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@@ -690,7 +714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"redox_users 0.4.5",
"winapi",
]
@@ -702,7 +726,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -775,7 +799,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -963,7 +987,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -1326,7 +1350,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -1423,6 +1447,17 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jetbrains-toolbox-updater"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d86b38fee698b3f63c9772fe2832d03a84e0724e409d499517c8a102949717"
dependencies = [
"dirs 6.0.0",
"json",
"sysinfo",
]
[[package]]
name = "js-sys"
version = "0.3.69"
@@ -1432,6 +1467,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -1440,9 +1481,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "libredox"
@@ -1619,6 +1660,15 @@ dependencies = [
"zbus",
]
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -1757,7 +1807,7 @@ dependencies = [
"bitflags 1.3.2",
"byteorder",
"chrono",
"thiserror",
"thiserror 1.0.61",
"widestring",
]
@@ -1784,7 +1834,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -1888,9 +1938,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.85"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -1952,6 +2002,26 @@ dependencies = [
"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]]
name = "redox_syscall"
version = "0.4.1"
@@ -1969,7 +2039,18 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror",
"thiserror 1.0.61",
]
[[package]]
name = "redox_users"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom",
"libredox",
"thiserror 2.0.12",
]
[[package]]
@@ -2069,15 +2150,14 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.8"
version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
@@ -2116,7 +2196,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -2298,7 +2378,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -2320,7 +2400,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -2399,9 +2479,15 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
dependencies = [
"dirs",
"dirs 5.0.1",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@@ -2446,12 +2532,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.3"
@@ -2499,7 +2579,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -2521,9 +2601,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.66"
version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [
"proc-macro2",
"quote",
@@ -2544,7 +2624,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -2556,6 +2636,20 @@ dependencies = [
"libc",
]
[[package]]
name = "sysinfo"
version = "0.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
dependencies = [
"core-foundation-sys",
"libc",
"memchr",
"ntapi",
"rayon",
"windows",
]
[[package]]
name = "tar"
version = "0.4.41"
@@ -2596,7 +2690,16 @@ version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.61",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@@ -2607,7 +2710,18 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.99",
]
[[package]]
@@ -2770,7 +2884,7 @@ dependencies = [
[[package]]
name = "topgrade"
version = "16.0.1"
version = "16.0.3"
dependencies = [
"cfg-if",
"chrono",
@@ -2783,6 +2897,7 @@ dependencies = [
"futures",
"glob",
"home",
"jetbrains-toolbox-updater",
"lazy_static",
"merge",
"nix 0.29.0",
@@ -2801,7 +2916,7 @@ dependencies = [
"strum",
"sys-locale",
"tempfile",
"thiserror",
"thiserror 1.0.61",
"tokio",
"toml 0.8.14",
"tracing",
@@ -2859,7 +2974,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -3063,7 +3178,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"wasm-bindgen-shared",
]
@@ -3097,7 +3212,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3189,7 +3304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core 0.56.0",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3198,7 +3313,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3210,7 +3325,7 @@ dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3221,7 +3336,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -3232,7 +3347,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -3241,7 +3356,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3259,7 +3374,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
@@ -3279,18 +3403,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@@ -3299,7 +3423,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3310,9 +3434,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@@ -3322,9 +3446,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@@ -3334,15 +3458,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@@ -3352,9 +3476,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@@ -3364,9 +3488,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -3376,9 +3500,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@@ -3388,9 +3512,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
@@ -3488,7 +3612,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"synstructure",
]
@@ -3539,7 +3663,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"zvariant_utils",
]
@@ -3571,7 +3695,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"synstructure",
]
@@ -3600,7 +3724,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]
[[package]]
@@ -3624,7 +3748,7 @@ checksum = "2ba5aa1827d6b1a35a29b3413ec69ce5f796e4d897e3e5b38f461bef41d225ea"
dependencies = [
"base64 0.21.7",
"ed25519-dalek",
"thiserror",
"thiserror 1.0.61",
]
[[package]]
@@ -3649,7 +3773,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
"zvariant_utils",
]
@@ -3661,5 +3785,5 @@ checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.99",
]

View File

@@ -5,8 +5,8 @@ categories = ["os"]
keywords = ["upgrade", "update"]
license = "GPL-3.0"
repository = "https://github.com/topgrade-rs/topgrade"
rust-version = "1.76.0"
version = "16.0.1"
rust-version = "1.84.1"
version = "16.0.3"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
edition = "2021"
@@ -54,6 +54,7 @@ notify-rust = "~4.11"
wildmatch = "2.3.0"
rust-i18n = "3.0.1"
sys-locale = "0.3.1"
jetbrains-toolbox-updater = "1.1.0"
[package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]

View File

@@ -31,6 +31,7 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- 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/)
- Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
[choco]: https://community.chocolatey.org/packages/topgrade
[scoop]: https://scoop.sh/#/apps?q=topgrade

View File

@@ -9,7 +9,11 @@
> If there are breaking changes, the major version number should be increased.
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
2. If the major versioin number gets bumped, update [SECURITY.md][SECURITY_file_link].
[SECURITY_file_link]: https://github.com/topgrade-rs/topgrade/blob/main/SECURITY.md
3. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
```sh'

View File

@@ -6,6 +6,6 @@ We only support the latest major version and each subversion.
| Version | Supported |
| -------- | ------------------ |
| 15.0.x | :white_check_mark: |
| < 15.0 | :x: |
| 16.0.x | :white_check_mark: |
| < 16.0 | :x: |

View File

@@ -103,6 +103,13 @@
# enable_pipupgrade = true ###disabled by default
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
# For the poetry step, by default, Topgrade skips its update if poetry is not
# installed with the official script. This configuration entry forces Topgrade
# to run the update in this case.
#
# (default: false)
# poetry_force_self_update = true
[composer]
# self_update = true
@@ -172,6 +179,11 @@
# rpm_ostree = false
# For Fedora/CentOS/RHEL Atomic variants, if `bootc` is available and this configuration entry is set to true, use
# it to do the update - Will also supercede rpm-ostree if enabled
# (default: false)
# bootc = false
# nix_arguments = "--flake"
# nix_env_arguments = "--prebuilt-only"
@@ -207,6 +219,10 @@
# wsl_update_use_web_download = true
# The default for winget_install_silently is true,
# this example turns off silent install.
# winget_install_silently = false
# 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
@@ -223,6 +239,11 @@
# use_sudo = true
[deno]
# Upgrade deno executable to the given version.
# version = "stable"
[vim]
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
# force_plug_update = true
@@ -265,3 +286,42 @@
# and the update will be installed system-wide, i.e., available to all users.
# (default: false)
# use_sudo = false
[julia]
# If disabled, Topgrade invokes julia with the --startup-file=no CLI option.
#
# This may be desirable to avoid loading outdated packages with "using" directives
# in the startup file, which might cause the update run to fail.
# (default: true)
# startup_file = true
[zigup]
# Version strings passed to zigup.
# These may be pinned versions such as "0.13.0" or branches such as "master".
# Each one will be updated in its own zigup invocation.
# (default: ["master"])
# target_versions = ["master", "0.13.0"]
# Specifies the directory that the zig files will be installed to.
# If defined, passed with the --install-dir command line flag.
# If not defined, zigup will use its default behaviour.
# (default: not defined)
# install_dir = "~/.zig"
# Specifies the path of the symlink which will be set to point at the default compiler version.
# If defined, passed with the --path-link command line flag.
# If not defined, zigup will use its default behaviour.
# This is not meaningful if set_default is not enabled.
# (default: not defined)
# path_link = "~/.bin/zig"
# If enabled, run `zigup clean` after updating all versions.
# If enabled, each updated version above will be marked with `zigup keep`.
# (default: false)
# cleanup = false
[vscode]
# If this is set and is a non-empty string, it specifies the profile the
# extensions should be updated for.
# (default: this won't be set by default)
# profile = ""

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "1.76.0"
channel = "1.84.1"

View File

@@ -45,13 +45,13 @@ 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| {
let stdout = String::from_utf8(stdout.clone()).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| {
let stderr = String::from_utf8(stderr.clone()).map_err(|err| {
eyre!(
"Stderr contained invalid UTF-8: {}",
String::from_utf8_lossy(err.as_bytes())
@@ -149,6 +149,7 @@ pub trait CommandExt {
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
/// execute.
#[track_caller]
#[allow(dead_code)]
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
}

View File

@@ -69,6 +69,7 @@ pub enum Step {
Chezmoi,
Chocolatey,
Choosenim,
CinnamonSpices,
ClamAvDb,
Composer,
Conda,
@@ -97,6 +98,7 @@ pub enum Step {
Haxelib,
Helm,
HomeManager,
JetBrainsToolbox,
Jetpack,
Julia,
Juliaup,
@@ -111,6 +113,7 @@ pub enum Step {
Mas,
Maza,
Micro,
MicrosoftStore,
Mise,
Myrepos,
Nix,
@@ -124,6 +127,7 @@ pub enum Step {
PipReviewLocal,
Pipupgrade,
Pipx,
Pipxu,
Pixi,
Pkg,
Pkgin,
@@ -162,6 +166,7 @@ pub enum Step {
Vim,
VoltaPackages,
Vscode,
Vscodium,
Waydroid,
Winget,
Wsl,
@@ -169,6 +174,7 @@ pub enum Step {
Xcodes,
Yadm,
Yarn,
Zigup,
Zvm,
}
@@ -219,6 +225,7 @@ pub struct Windows {
open_remotes_in_new_terminal: Option<bool>,
wsl_update_pre_release: Option<bool>,
wsl_update_use_web_download: Option<bool>,
winget_silent_install: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
@@ -228,6 +235,7 @@ pub struct Python {
enable_pip_review_local: Option<bool>,
enable_pipupgrade: Option<bool>,
pipupgrade_arguments: Option<String>,
poetry_force_self_update: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
@@ -254,6 +262,13 @@ pub struct NPM {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Deno {
version: Option<String>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
@@ -350,6 +365,7 @@ pub struct Linux {
redhat_distro_sync: Option<bool>,
suse_dup: Option<bool>,
rpm_ostree: Option<bool>,
bootc: Option<bool>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
emerge_sync_flags: Option<String>,
@@ -444,6 +460,27 @@ pub struct Lensfun {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct JuliaConfig {
startup_file: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Zigup {
target_versions: Option<Vec<String>>,
install_dir: Option<String>,
path_link: Option<String>,
cleanup: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct VscodeConfig {
profile: Option<String>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
/// Configuration file
@@ -490,6 +527,9 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
yarn: Option<Yarn>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
deno: Option<Deno>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
vim: Option<Vim>,
@@ -507,6 +547,15 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
lensfun: Option<Lensfun>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
julia: Option<JuliaConfig>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
zigup: Option<Zigup>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
vscode: Option<VscodeConfig>,
}
fn config_directory() -> PathBuf {
@@ -538,7 +587,7 @@ impl ConfigFile {
];
// Search for the main config file
for path in possible_config_paths.iter() {
for path in &possible_config_paths {
if path.exists() {
debug!("Configuration at {}", path.display());
res.0.clone_from(path);
@@ -802,7 +851,7 @@ pub struct CommandLineArgs {
/// Tracing filter directives.
///
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
#[arg(long, default_value = DEFAULT_LOG_LEVEL)]
pub log_filter: String,
@@ -1437,14 +1486,22 @@ impl Config {
.unwrap_or(false)
}
/// Use bootc in *when bootc is detected* (default: false)
pub fn bootc(&self) -> bool {
self.config_file
.linux
.as_ref()
.and_then(|linux| linux.bootc)
.unwrap_or(false)
}
/// Determine if we should ignore failures for this step
pub fn ignore_failure(&self, step: Step) -> bool {
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.ignore_failures.as_ref())
.map(|v| v.contains(&step))
.unwrap_or(false)
.is_some_and(|v| v.contains(&step))
}
pub fn use_predefined_git_repos(&self) -> bool {
@@ -1494,6 +1551,14 @@ impl Config {
.unwrap_or(false)
}
pub fn winget_silent_install(&self) -> bool {
self.config_file
.windows
.as_ref()
.and_then(|windows| windows.winget_silent_install)
.unwrap_or(true)
}
pub fn sudo_command(&self) -> Option<SudoKind> {
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
}
@@ -1525,6 +1590,10 @@ impl Config {
.unwrap_or(false)
}
pub fn deno_version(&self) -> Option<&str> {
self.config_file.deno.as_ref().and_then(|deno| deno.version.as_deref())
}
#[cfg(target_os = "linux")]
pub fn firmware_upgrade(&self) -> bool {
self.config_file
@@ -1566,12 +1635,11 @@ impl Config {
}
pub fn enable_pipupgrade(&self) -> bool {
return self
.config_file
self.config_file
.python
.as_ref()
.and_then(|python| python.enable_pipupgrade)
.unwrap_or(false);
.unwrap_or(false)
}
pub fn pipupgrade_arguments(&self) -> &str {
self.config_file
@@ -1581,20 +1649,25 @@ impl Config {
.unwrap_or("")
}
pub fn enable_pip_review(&self) -> bool {
return self
.config_file
self.config_file
.python
.as_ref()
.and_then(|python| python.enable_pip_review)
.unwrap_or(false);
.unwrap_or(false)
}
pub fn enable_pip_review_local(&self) -> bool {
return self
.config_file
self.config_file
.python
.as_ref()
.and_then(|python| python.enable_pip_review_local)
.unwrap_or(false);
.unwrap_or(false)
}
pub fn poetry_force_self_update(&self) -> bool {
self.config_file
.python
.as_ref()
.and_then(|python| python.poetry_force_self_update)
.unwrap_or(false)
}
pub fn display_time(&self) -> bool {
@@ -1620,6 +1693,55 @@ impl Config {
.and_then(|lensfun| lensfun.use_sudo)
.unwrap_or(false)
}
pub fn julia_use_startup_file(&self) -> bool {
self.config_file
.julia
.as_ref()
.and_then(|julia| julia.startup_file)
.unwrap_or(true)
}
pub fn zigup_target_versions(&self) -> Vec<String> {
self.config_file
.zigup
.as_ref()
.and_then(|zigup| zigup.target_versions.clone())
.unwrap_or(vec!["master".to_owned()])
}
pub fn zigup_install_dir(&self) -> Option<&str> {
self.config_file
.zigup
.as_ref()
.and_then(|zigup| zigup.install_dir.as_deref())
}
pub fn zigup_path_link(&self) -> Option<&str> {
self.config_file
.zigup
.as_ref()
.and_then(|zigup| zigup.path_link.as_deref())
}
pub fn zigup_cleanup(&self) -> bool {
self.config_file
.zigup
.as_ref()
.and_then(|zigup| zigup.cleanup)
.unwrap_or(false)
}
pub fn vscode_profile(&self) -> Option<&str> {
let vscode_cfg = self.config_file.vscode.as_ref()?;
let profile = vscode_cfg.profile.as_ref()?;
if profile.is_empty() {
None
} else {
Some(profile.as_str())
}
}
}
#[cfg(test)]
@@ -1646,40 +1768,40 @@ mod test {
#[test]
fn test_should_execute_remote_different_hostname() {
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"))
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"));
}
#[test]
fn test_should_execute_remote_different_hostname_with_user() {
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
}
#[test]
fn test_should_execute_remote_unknown_hostname() {
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"))
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"));
}
#[test]
fn test_should_not_execute_remote_same_hostname() {
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"))
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"));
}
#[test]
fn test_should_not_execute_remote_same_hostname_with_user() {
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"))
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"));
}
#[test]
fn test_should_execute_remote_matching_limit() {
let mut config = config();
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
}
#[test]
fn test_should_not_execute_remote_not_matching_limit() {
let mut config = config();
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
}
}

View File

@@ -11,9 +11,9 @@ pub fn interrupted() -> bool {
/// Clears the interrupted flag
pub fn unset_interrupted() {
debug_assert!(INTERRUPTED.load(Ordering::SeqCst));
INTERRUPTED.store(false, Ordering::SeqCst)
INTERRUPTED.store(false, Ordering::SeqCst);
}
pub fn set_interrupted() {
INTERRUPTED.store(true, Ordering::SeqCst)
INTERRUPTED.store(true, Ordering::SeqCst);
}

View File

@@ -4,7 +4,7 @@ use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal
/// Handle SIGINT. Set the interruption flag.
extern "C" fn handle_sigint(_: i32) {
set_interrupted()
set_interrupted();
}
/// Set the necessary signal handlers.

View File

@@ -152,7 +152,10 @@ impl Executor {
let result = match self {
Executor::Wet(c) => {
debug!("Running {:?}", c);
c.spawn_checked().map(ExecutorChild::Wet)?
// We should use `spawn()` here rather than `spawn_checked()` since
// their semantics and behaviors are different.
#[allow(clippy::disallowed_methods)]
c.spawn().map(ExecutorChild::Wet)?
}
Executor::Dry(c) => {
c.dry_run();
@@ -166,7 +169,12 @@ 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) => {
// We should use `output()` here rather than `output_checked()` since
// their semantics and behaviors are different.
#[allow(clippy::disallowed_methods)]
Ok(ExecutorOutput::Wet(c.output()?))
}
Executor::Dry(c) => {
c.dry_run();
Ok(ExecutorOutput::Dry)
@@ -180,7 +188,7 @@ impl Executor {
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) {
if status.success() || status.code().as_ref().is_some_and(|c| codes.contains(c)) {
Ok(())
} else {
Err(())

View File

@@ -25,7 +25,9 @@ use self::config::{CommandLineArgs, Config, Step};
use self::error::StepFailed;
#[cfg(all(windows, feature = "self-update"))]
use self::error::Upgraded;
#[allow(clippy::wildcard_imports)]
use self::steps::{remote::*, *};
#[allow(clippy::wildcard_imports)]
use self::terminal::*;
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
@@ -58,6 +60,7 @@ pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expe
// Init and load the i18n files
i18n!("locales", fallback = "en");
#[allow(clippy::too_many_lines)]
fn run() -> Result<()> {
install_color_eyre()?;
ctrlc::set_handler();
@@ -206,6 +209,9 @@ fn run() -> Result<()> {
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))?;
runner.execute(Step::MicrosoftStore, "Microsoft Store", || {
windows::microsoft_store(&ctx)
})?;
}
#[cfg(target_os = "linux")]
@@ -245,6 +251,9 @@ fn run() -> Result<()> {
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))?;
runner.execute(Step::CinnamonSpices, "Cinnamon spices", || {
linux::run_cinnamon_spices_updater(&ctx)
})?;
}
#[cfg(target_os = "macos")]
@@ -366,9 +375,13 @@ fn run() -> Result<()> {
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::Pipxu, "pipxu", || generic::run_pipxu_update(&ctx))?;
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
generic::run_vscode_extensions_update(&ctx)
})?;
runner.execute(Step::Vscodium, "VSCodium extensions", || {
generic::run_vscodium_extensions_update(&ctx)
})?;
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))?;
@@ -432,6 +445,10 @@ fn run() -> Result<()> {
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))?;
runner.execute(Step::Zigup, "zigup", || generic::run_zigup(&ctx))?;
runner.execute(Step::JetBrainsToolbox, "JetBrains Toolbox", || {
generic::run_jetbrains_toolbox(&ctx)
})?;
if should_run_powershell {
runner.execute(Step::Powershell, "Powershell Modules Update", || {
@@ -488,13 +505,13 @@ fn run() -> Result<()> {
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
loop {
match get_key() {
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
Ok(Key::Char('s' | 'S')) => {
run_shell().context("Failed to execute shell")?;
}
Ok(Key::Char('r')) | Ok(Key::Char('R')) => {
Ok(Key::Char('r' | 'R')) => {
reboot().context("Failed to reboot")?;
}
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => (),
Ok(Key::Char('q' | 'Q')) => (),
_ => {
continue;
}
@@ -513,7 +530,7 @@ fn run() -> Result<()> {
t!("Topgrade finished successfully")
},
Some(Duration::from_secs(10)),
)
);
}
if failed {

View File

@@ -9,7 +9,7 @@ use rust_i18n::t;
use self_update_crate::backends::github::Update;
use self_update_crate::update::UpdateStatus;
use super::terminal::*;
use super::terminal::{print_info, print_separator};
#[cfg(windows)]
use crate::error::Upgraded;

View File

@@ -140,7 +140,7 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
debug!("Containers to inspect: {:?}", containers);
for container in containers.iter() {
for container in &containers {
debug!("Pulling container '{}'", container);
let args = vec![
"pull",

View File

@@ -1,9 +1,4 @@
(when (fboundp 'paradox-upgrade-packages)
(progn
(unless (boundp 'paradox-github-token)
(setq paradox-github-token t))
(paradox-upgrade-packages)
(princ
(if (get-buffer "*Paradox Report*")
(with-current-buffer "*Paradox Report*" (buffer-string))
"\nNothing to upgrade\n"))))
(when (featurep 'package)
(if (fboundp 'package-upgrade-all)
(package-upgrade-all nil)
(message "Your Emacs version doesn't support unattended packages upgrade")))

View File

@@ -1,5 +1,6 @@
#![allow(unused_imports)]
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::process::Command;
use std::{env, path::Path};
@@ -8,6 +9,9 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use jetbrains_toolbox_updater::{find_jetbrains_toolbox, update_jetbrains_toolbox, FindError};
use lazy_static::lazy_static;
use regex::bytes::Regex;
use rust_i18n::t;
use semver::Version;
use tempfile::tempfile_in;
@@ -39,8 +43,7 @@ pub fn is_wsl() -> Result<bool> {
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| HOME_DIR.join(".cargo"))
.map_or_else(|| HOME_DIR.join(".cargo"), PathBuf::from)
.require()?;
require("cargo").or_else(|_| {
require_option(
@@ -59,13 +62,11 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_update = require("cargo-install-update")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
let cargo_update = match cargo_update {
Some(e) => e,
None => {
let message = String::from("cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`");
print_warning(&message);
return Err(SkipStep(message).into());
}
let Some(cargo_update) = cargo_update else {
let message = String::from("cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`");
print_warning(&message);
return Err(SkipStep(message).into());
};
ctx.run_type()
@@ -77,14 +78,11 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_cache = require("cargo-cache")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-cache").if_exists());
match cargo_cache {
Some(e) => {
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
}
None => {
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
print_warning(message);
}
if let Some(e) = cargo_cache {
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
} else {
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
print_warning(message);
}
}
@@ -228,6 +226,13 @@ pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
let aqua = require("aqua")?;
// Check if `aqua --help` mentions "aqua". JetBrains aqua does not, aqua CLI does.
let output = ctx.run_type().execute(&aqua).arg("--help").output_checked()?;
if !String::from_utf8(output.stdout)?.contains("aqua") {
return Err(SkipStep("Command aqua probably points to JetBrains Aqua".to_string()).into());
}
print_separator("Aqua");
if ctx.run_type().dry() {
println!("{}", t!("Updating aqua ..."));
@@ -257,10 +262,35 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
let elan = require("elan")?;
print_separator("elan");
ctx.run_type()
.execute(&elan)
.args(["self", "update"])
.status_checked()?;
let disabled_error_msg = "self-update is disabled";
let executor_output = ctx.run_type().execute(&elan).args(["self", "update"]).output()?;
match executor_output {
ExecutorOutput::Wet(command_output) => {
if command_output.status.success() {
// Flush the captured output
std::io::stdout().lock().write_all(&command_output.stdout).unwrap();
std::io::stderr().lock().write_all(&command_output.stderr).unwrap();
} else {
let stderr_as_str = std::str::from_utf8(&command_output.stderr).unwrap();
if stderr_as_str.contains(disabled_error_msg) {
// `elan` is externally managed, we cannot do the update. Users
// won't see any error message because Topgrade captures them
// all.
} else {
// `elan` is NOT externally managed, `elan self update` can
// be performed, but the invocation failed, so we report the
// error to the user and error out.
std::io::stdout().lock().write_all(&command_output.stdout).unwrap();
std::io::stderr().lock().write_all(&command_output.stderr).unwrap();
return Err(StepFailed.into());
}
}
}
ExecutorOutput::Dry => { /* nothing needed because in a dry run */ }
}
ctx.run_type().execute(&elan).arg("update").status_checked()
}
@@ -276,7 +306,13 @@ pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
.status_checked()?;
}
ctx.run_type().execute(&juliaup).arg("update").status_checked()
ctx.run_type().execute(&juliaup).arg("update").status_checked()?;
if ctx.config().cleanup() {
ctx.run_type().execute(&juliaup).arg("gc").status_checked()?;
}
Ok(())
}
pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
@@ -374,6 +410,48 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
command.args(["upgrade", "--no-dry-run"]).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<()> {
// 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()? {
return Err(SkipStep(String::from("Should not run in WSL")).into());
}
let vscodium = require("codium")?;
// 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
//
// This should apply to VSCodium as well.
let version: Result<Version> = match Command::new(&vscodium)
.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 vscodium version")).into()),
};
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
return Err(SkipStep(String::from(
"Too old vscodium version to have update extensions command",
))
.into());
}
print_separator("VSCodium extensions");
ctx.run_type()
.execute(vscodium)
.arg("--update-extensions")
.status_checked()
}
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)
if is_wsl()? {
@@ -391,7 +469,7 @@ pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
.lines()
.next()
{
Some(item) => Version::parse(item).map_err(|err| err.into()),
Some(item) => Version::parse(item).map_err(std::convert::Into::into),
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
};
@@ -401,10 +479,19 @@ pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Visual Studio Code extensions");
ctx.run_type()
.execute(vscode)
.arg("--update-extensions")
.status_checked()
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<()> {
@@ -421,12 +508,22 @@ pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
.map(|s| s.stdout.trim().to_owned());
let version = Version::parse(&version_str?);
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
command_args.push("--quiet")
command_args.push("--quiet");
}
ctx.run_type().execute(pipx).args(command_args).status_checked()
}
pub fn run_pipxu_update(ctx: &ExecutionContext) -> Result<()> {
let pipxu = require("pipxu")?;
print_separator("pipxu");
ctx.run_type()
.execute(pipxu)
.args(["upgrade", "--all"])
.status_checked()
}
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
let conda = require("conda")?;
@@ -440,19 +537,45 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Conda");
let mut command = ctx.run_type().execute(conda);
let mut command = ctx.run_type().execute(&conda);
command.args(["update", "--all", "-n", "base"]);
if ctx.config().yes(Step::Conda) {
command.arg("--yes");
}
command.status_checked()
command.status_checked()?;
if ctx.config().cleanup() {
let mut command = ctx.run_type().execute(conda);
command.args(["clean", "--all"]);
if ctx.config().yes(Step::Conda) {
command.arg("--yes");
}
command.status_checked()?;
}
Ok(())
}
pub fn run_pixi_update(ctx: &ExecutionContext) -> Result<()> {
let pixi = require("pixi")?;
print_separator("Pixi");
ctx.run_type().execute(pixi).args(["self-update"]).status_checked()
// Check if `pixi --help` mentions self-update, if yes, self-update must be enabled.
// pixi self-update --help works regardless of whether the feature is enabled.
let output = ctx.run_type().execute(&pixi).arg("--help").output_checked()?;
if String::from_utf8(output.stdout)?.contains("self-update") {
ctx.run_type()
.execute(&pixi)
.args(["self-update"])
.status_checked()
.ok();
}
ctx.run_type()
.execute(&pixi)
.args(["global", "update"])
.status_checked()
}
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
@@ -460,12 +583,23 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Mamba");
let mut command = ctx.run_type().execute(mamba);
let mut command = ctx.run_type().execute(&mamba);
command.args(["update", "--all", "-n", "base"]);
if ctx.config().yes(Step::Mamba) {
command.arg("--yes");
}
command.status_checked()
command.status_checked()?;
if ctx.config().cleanup() {
let mut command = ctx.run_type().execute(&mamba);
command.args(["clean", "--all"]);
if ctx.config().yes(Step::Mamba) {
command.arg("--yes");
}
command.status_checked()?;
}
Ok(())
}
pub fn run_miktex_packages_update(ctx: &ExecutionContext) -> Result<()> {
@@ -487,7 +621,7 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
(Ok(py), _) => py,
(Err(_), Ok(py3)) => py3,
(Err(py_err), Err(py3_err)) => {
return Err(SkipStep(format!("Skip due to following reasons: {} {}", py_err, py3_err)).into());
return Err(SkipStep(format!("Skip due to following reasons: {py_err} {py3_err}")).into());
}
};
@@ -836,7 +970,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
.execute(&dotnet)
.args(["tool", "update", package_name, "--global"])
.status_checked()
.with_context(|| format!("Failed to update .NET package {:?}", package_name))?;
.with_context(|| format!("Failed to update .NET package {package_name:?}"))?;
}
Ok(())
@@ -908,10 +1042,15 @@ pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
print_separator(t!("Julia Packages"));
ctx.run_type()
.execute(julia)
.args(["-e", "using Pkg; Pkg.update()"])
.status_checked()
let mut executor = ctx.run_type().execute(julia);
executor.arg(if ctx.config().julia_use_startup_file() {
"--startup-file=yes"
} else {
"--startup-file=no"
});
executor.args(["-e", "using Pkg; Pkg.update()"]).status_checked()
}
pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
@@ -1024,23 +1163,133 @@ pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
let poetry = require("poetry")?;
#[cfg(unix)]
fn get_interpreter(poetry: &PathBuf) -> Result<(PathBuf, Option<OsString>)> {
// Parse the standard Unix shebang line: #!interpreter [optional-arg]
// Spaces and tabs on either side of interpreter are ignored.
use std::os::unix::ffi::OsStrExt;
lazy_static! {
static ref SHEBANG_REGEX: Regex = Regex::new(r"^#![ \t]*([^ \t\n]+)(?:[ \t]+([^\n]+)?)?").unwrap();
}
let script = fs::read(poetry)?;
if let Some(c) = SHEBANG_REGEX.captures(&script) {
let interpreter = OsStr::from_bytes(&c[1]).into();
let args = c.get(2).map(|args| OsStr::from_bytes(args.as_bytes()).into());
return Ok((interpreter, args));
}
Err(eyre!("Could not find shebang"))
}
#[cfg(windows)]
fn get_interpreter(poetry: &PathBuf) -> Result<(PathBuf, Option<OsString>)> {
// Parse the shebang line from scripts using https://bitbucket.org/vinay.sajip/simple_launcher,
// such as those created by pip. In contrast to Unix shebang lines, interpreter paths can
// contain spaces, if they are double-quoted.
use std::str;
lazy_static! {
static ref SHEBANG_REGEX: Regex =
Regex::new(r#"^#![ \t]*(?:"([^"\n]+)"|([^" \t\n]+))(?:[ \t]+([^\n]+)?)?"#).unwrap();
}
let data = fs::read(poetry)?;
let pos = match data.windows(4).rposition(|b| b == b"PK\x05\x06") {
Some(i) => i,
None => return Err(eyre!("Not a ZIP archive")),
};
let cdr_size = match data.get(pos + 12..pos + 16) {
Some(b) => u32::from_le_bytes(b.try_into().unwrap()) as usize,
None => return Err(eyre!("Invalid CDR size")),
};
let cdr_offset = match data.get(pos + 16..pos + 20) {
Some(b) => u32::from_le_bytes(b.try_into().unwrap()) as usize,
None => return Err(eyre!("Invalid CDR offset")),
};
if pos < cdr_size + cdr_offset {
return Err(eyre!("Invalid ZIP archive"));
}
let arc_pos = pos - cdr_size - cdr_offset;
match data[..arc_pos].windows(2).rposition(|b| b == b"#!") {
Some(l) => {
let line = &data[l..arc_pos - 1];
if let Some(c) = SHEBANG_REGEX.captures(line) {
let interpreter = c.get(1).or_else(|| c.get(2)).unwrap();
// shebang line should be valid utf8
let interpreter = str::from_utf8(interpreter.as_bytes())?.into();
let args = match c.get(3) {
Some(args) => Some(str::from_utf8(args.as_bytes())?.into()),
None => None,
};
Ok((interpreter, args))
} else {
Err(eyre!("Invalid shebang line"))
}
}
None => Err(eyre!("Could not find shebang")),
}
}
if ctx.config().poetry_force_self_update() {
debug!("forcing poetry self update");
} else {
let (interp, interp_args) = get_interpreter(&poetry)
.map_err(|e| SkipStep(format!("Could not find interpreter for {}: {}", poetry.display(), e)))?;
debug!("poetry interpreter: {:?}, args: {:?}", interp, interp_args);
let check_official_install_script =
"import sys; from os import path; print('Y') if path.isfile(path.join(sys.prefix, 'poetry_env')) else print('N')";
let mut command = Command::new(&interp);
if let Some(args) = interp_args {
command.arg(args);
}
let output = command
.args(["-c", check_official_install_script])
.output_checked_utf8()?;
let stdout = output.stdout.trim();
let official_install = match stdout {
"N" => false,
"Y" => true,
_ => unreachable!("unexpected output from `check_official_install_script`"),
};
debug!("poetry is official install: {}", official_install);
if !official_install {
return Err(SkipStep("Not installed with the official script".to_string()).into());
}
}
print_separator("Poetry");
ctx.run_type().execute(poetry).args(["self", "update"]).status_checked()
ctx.run_type()
.execute(&poetry)
.args(["self", "update"])
.status_checked()
}
pub fn run_uv(ctx: &ExecutionContext) -> Result<()> {
let uv_exec = require("uv")?;
print_separator("uv");
ctx.run_type()
// try uv self --help first - if it succeeds, we call uv self update
let result = ctx
.run_type()
.execute(&uv_exec)
.args(["self", "update"])
.status_checked()
.ok();
.args(["self", "--help"])
.output_checked();
// ignoring self-update errors, because they are likely due to uv's
// installation being managed by another package manager, in which
// case another step will handle the update.
if result.is_ok() {
ctx.run_type()
.execute(&uv_exec)
.args(["self", "update"])
.status_checked()?;
}
ctx.run_type()
.execute(&uv_exec)
@@ -1064,3 +1313,87 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(bun).arg("upgrade").status_checked()
}
pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {
let zigup = require("zigup")?;
let config = ctx.config();
print_separator("zigup");
let mut path_args = Vec::new();
if let Some(path) = config.zigup_path_link() {
path_args.push("--path-link".to_owned());
path_args.push(shellexpand::tilde(path).into_owned());
}
if let Some(path) = config.zigup_install_dir() {
path_args.push("--install-dir".to_owned());
path_args.push(shellexpand::tilde(path).into_owned());
}
for zig_version in config.zigup_target_versions() {
ctx.run_type()
.execute(&zigup)
.args(&path_args)
.arg("fetch")
.arg(&zig_version)
.status_checked()?;
if config.zigup_cleanup() {
ctx.run_type()
.execute(&zigup)
.args(&path_args)
.arg("keep")
.arg(&zig_version)
.status_checked()?;
}
}
if config.zigup_cleanup() {
ctx.run_type()
.execute(zigup)
.args(&path_args)
.arg("clean")
.status_checked()?;
}
Ok(())
}
pub fn run_jetbrains_toolbox(_ctx: &ExecutionContext) -> Result<()> {
let installation = find_jetbrains_toolbox();
match installation {
Err(FindError::NotFound) => {
// Skip
Err(SkipStep(format!("{}", t!("No JetBrains Toolbox installation found"))).into())
}
Err(FindError::UnsupportedOS(os)) => {
// Skip
Err(SkipStep(format!("{}", t!("Unsupported operating system {os}", os = os))).into())
}
Err(e) => {
// Unexpected error
println!(
"{}",
t!("jetbrains-toolbox-updater encountered an unexpected error during finding:")
);
println!("{e:?}");
Err(StepFailed.into())
}
Ok(installation) => {
print_separator("JetBrains Toolbox");
match update_jetbrains_toolbox(installation) {
Err(e) => {
// Unexpected error
println!(
"{}",
t!("jetbrains-toolbox-updater encountered an unexpected error during updating:")
);
println!("{e:?}");
Err(StepFailed.into())
}
Ok(()) => Ok(()),
}
}
}
}

View File

@@ -105,7 +105,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
print_warning(t!(
"Path {pattern} did not contain any git repositories",
pattern = pattern
))
));
});
if repos.is_repos_empty() {
@@ -207,10 +207,13 @@ impl RepoStep {
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(),),
},
Err(e) => {
if e.kind() == io::ErrorKind::NotFound {
debug!("{} does not exist", path.as_ref().display());
} else {
error!("Error looking for {}: {e}", path.as_ref().display());
}
}
}
None
@@ -321,7 +324,7 @@ impl RepoStep {
.output()
.await?;
let result = output_checked_utf8(pull_output)
.and_then(|_| output_checked_utf8(submodule_output))
.and_then(|()| output_checked_utf8(submodule_output))
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
if result.is_err() {
@@ -359,7 +362,7 @@ impl RepoStep {
}
}
result.map(|_| ())
result
}
/// Pull the repositories specified in `self.repos`.
@@ -410,7 +413,7 @@ impl RepoStep {
let basic_rt = runtime::Runtime::new()?;
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
let error = results.into_iter().find(|r| r.is_err());
let error = results.into_iter().find(std::result::Result::is_err);
error.unwrap_or(Ok(()))
}
}

View File

@@ -87,7 +87,7 @@ impl NPM {
.args(["--version"])
.output_checked_utf8()
.map(|s| s.stdout.trim().to_owned());
Version::parse(&version_str?).map_err(|err| err.into())
Version::parse(&version_str?).map_err(std::convert::Into::into)
}
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
@@ -184,6 +184,92 @@ impl Yarn {
}
}
struct Deno {
command: PathBuf,
}
impl Deno {
fn new(command: PathBuf) -> Self {
Self { command }
}
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let mut args = vec![];
let version = ctx.config().deno_version();
if let Some(version) = version {
let bin_version = self.version()?;
if bin_version >= Version::new(2, 0, 0) {
args.push(version);
} else if bin_version >= Version::new(1, 6, 0) {
match version {
"stable" => { /* do nothing, as stable is the default channel to upgrade */ }
"rc" => {
return Err(SkipStep(
"Deno (1.6.0-2.0.0) cannot be upgraded to a release candidate".to_string(),
)
.into());
}
"canary" => args.push("--canary"),
_ => {
if Version::parse(version).is_err() {
return Err(SkipStep("Invalid Deno version".to_string()).into());
}
args.push("--version");
args.push(version);
}
}
} else if bin_version >= Version::new(1, 0, 0) {
match version {
"stable" | "rc" | "canary" => {
// Prior to v1.6.0, `deno upgrade` is not able fetch the latest tag version.
return Err(
SkipStep("Deno (1.0.0-1.6.0) cannot be upgraded to a named channel".to_string()).into(),
);
}
_ => {
if Version::parse(version).is_err() {
return Err(SkipStep("Invalid Deno version".to_string()).into());
}
args.push("--version");
args.push(version);
}
}
} else {
// v0.x cannot be upgraded with `deno upgrade` to v1.x or v2.x
// nor can be upgraded to a specific version.
return Err(SkipStep("Unsupported Deno version".to_string()).into());
}
}
ctx.run_type()
.execute(&self.command)
.arg("upgrade")
.args(args)
.status_checked()?;
Ok(())
}
/// Get the version of Deno.
///
/// This function will return the version of Deno installed on the system.
/// The version is parsed from the output of `deno -V`.
///
/// ```sh
/// deno -V # deno 1.6.0
/// ```
fn version(&self) -> Result<Version> {
let version_str = Command::new(&self.command)
.args(["-V"])
.output_checked_utf8()
.map(|s| s.stdout.trim().to_owned().split_off(5)); // remove "deno " prefix
Version::parse(&version_str?).map_err(std::convert::Into::into)
}
}
#[cfg(target_os = "linux")]
fn should_use_sudo(npm: &NPM, ctx: &ExecutionContext) -> Result<bool> {
if npm.should_use_sudo()? {
@@ -266,16 +352,16 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
}
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
let deno = require("deno")?;
let deno = require("deno").map(Deno::new)?;
let deno_dir = HOME_DIR.join(".deno");
if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
if !deno.command.canonicalize()?.is_descendant_of(&deno_dir) {
let skip_reason = SkipStep(t!("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()
deno.upgrade(ctx)
}
/// There is no `volta upgrade` command, so we need to upgrade each package
@@ -313,7 +399,7 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
return Ok(());
}
for package in installed_packages.iter() {
for package in &installed_packages {
ctx.run_type()
.execute(&volta)
.args(["install", package])

View File

@@ -60,19 +60,7 @@ impl Distribution {
Some("wolfi") => Distribution::Wolfi,
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
Some("clear-linux-os") => Distribution::ClearLinux,
Some("fedora") => {
return if let Some(variant) = variant {
match variant {
"Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" => {
Ok(Distribution::FedoraImmutable)
}
_ => Ok(Distribution::Fedora),
}
} else {
Ok(Distribution::Fedora)
};
}
Some("fedora") => Distribution::match_fedora_variant(&variant),
Some("nilrt") => Distribution::NILRT,
Some("nobara") => Distribution::Nobara,
Some("void") => Distribution::Void,
@@ -109,7 +97,7 @@ impl Distribution {
} else if id_like.contains(&"alpine") {
return Ok(Distribution::Alpine);
} else if id_like.contains(&"fedora") {
return Ok(Distribution::Fedora);
return Ok(Distribution::match_fedora_variant(&variant));
}
}
return Err(TopgradeError::UnknownLinuxDistribution.into());
@@ -117,6 +105,15 @@ impl Distribution {
})
}
fn match_fedora_variant(variant: &Option<&str>) -> Self {
if let Some("Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" | "CoreOS") = variant
{
Distribution::FedoraImmutable
} else {
Distribution::Fedora
}
}
pub fn detect() -> Result<Self> {
if PathBuf::from("/bedrock").exists() {
return Ok(Distribution::Bedrock);
@@ -225,6 +222,13 @@ fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
}
fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
if let Some(bootc) = which("bootc") {
if ctx.config().bootc() {
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
return ctx.run_type().execute(sudo).arg(&bootc).arg("upgrade").status_checked();
}
}
if let Some(ostree) = which("rpm-ostree") {
if ctx.config().rpm_ostree() {
let mut command = ctx.run_type().execute(ostree);
@@ -298,6 +302,13 @@ fn upgrade_nilrt(ctx: &ExecutionContext) -> Result<()> {
}
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
if let Some(bootc) = which("bootc") {
if ctx.config().bootc() {
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
return ctx.run_type().execute(sudo).arg(&bootc).arg("upgrade").status_checked();
}
}
let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree);
command.arg("upgrade");
@@ -1106,6 +1117,17 @@ pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_cinnamon_spices_updater(ctx: &ExecutionContext) -> Result<()> {
let cinnamon_spice_updater = require("cinnamon-spice-updater")?;
print_separator("Cinnamon spices");
ctx.run_type()
.execute(cinnamon_spice_updater)
.arg("--update-all")
.status_checked()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1274,4 +1296,24 @@ mod tests {
fn test_nilrt() {
test_template(include_str!("os_release/nilrt"), Distribution::NILRT);
}
#[test]
fn test_coreos() {
test_template(include_str!("os_release/coreos"), Distribution::FedoraImmutable);
}
#[test]
fn test_aurora() {
test_template(include_str!("os_release/aurora"), Distribution::FedoraImmutable);
}
#[test]
fn test_bluefin() {
test_template(include_str!("os_release/bluefin"), Distribution::FedoraImmutable);
}
#[test]
fn test_bazzite() {
test_template(include_str!("os_release/bazzite"), Distribution::FedoraImmutable);
}
}

View File

@@ -203,7 +203,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
.execute(&xcodes)
.args([
"uninstall",
releases_new_installed.iter().next().cloned().unwrap_or_default(),
releases_new_installed.iter().next().copied().unwrap_or_default(),
])
.status_checked();
}
@@ -216,12 +216,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
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()
{
if releases_filtered.last().map_or(true, |s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
println!(
"{} {}",
t!("New Xcode release detected:"),

View File

@@ -3,20 +3,51 @@ use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator;
use crate::utils::{get_require_sudo_string, require_option};
use color_eyre::eyre::Result;
use rust_i18n::t;
use std::fs;
fn is_openbsd_current(ctx: &ExecutionContext) -> Result<bool> {
let motd_content = fs::read_to_string("/etc/motd")?;
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
if ctx.config().dry_run() {
println!("{}", t!("Would check if OpenBSD is -current"));
Ok(is_current)
} else {
Ok(is_current)
}
}
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()
let is_current = is_openbsd_current(ctx)?;
if ctx.config().dry_run() {
println!("{}", t!("Would upgrade the OpenBSD system"));
return Ok(());
}
let args = if is_current {
vec!["/usr/sbin/sysupgrade", "-sn"]
} else {
vec!["/usr/sbin/syspatch"]
};
ctx.run_type().execute(sudo).args(&args).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"));
let is_current = is_openbsd_current(ctx)?;
if ctx.config().dry_run() {
println!("{}", t!("Would upgrade OpenBSD packages"));
return Ok(());
}
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
@@ -24,10 +55,12 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
.status_checked()?;
}
ctx.run_type()
.execute(sudo)
.args(["/usr/sbin/pkg_add", "-u"])
.status_checked()?;
let mut args = vec!["/usr/sbin/pkg_add", "-u"];
if is_current {
args.push("-Dsnap");
}
ctx.run_type().execute(sudo).args(&args).status_checked()?;
Ok(())
}

View File

@@ -0,0 +1,23 @@
NAME="Aurora"
VERSION="latest-41.20250210.4 (Kinoite)"
RELEASE_TYPE=stable
ID=aurora
ID_LIKE="fedora"
VERSION_ID=41
VERSION_CODENAME=""
PLATFORM_ID="platform:f41"
PRETTY_NAME="Aurora (Version: latest-41.20250210.4 / FROM Fedora Kinoite 41)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:universal-blue:aurora:41"
DEFAULT_HOSTNAME="aurora"
HOME_URL="https://getaurora.dev/"
DOCUMENTATION_URL="https://docs.getaurora.dev"
SUPPORT_URL="https://github.com/ublue-os/aurora/issues/"
BUG_REPORT_URL="https://github.com/ublue-os/aurora/issues/"
SUPPORT_END=2025-12-15
VARIANT="Kinoite"
VARIANT_ID=aurora
OSTREE_VERSION='latest-41.20250210.4'
BUILD_ID="fc1570c"
IMAGE_ID="aurora"

View File

@@ -0,0 +1,25 @@
NAME="Bazzite"
VERSION="41.20250208.0 (Kinoite)"
RELEASE_TYPE=stable
ID=bazzite
ID_LIKE="fedora"
VERSION_ID=41
VERSION_CODENAME="Holographic"
PLATFORM_ID="platform:f41"
PRETTY_NAME="Bazzite 41 (FROM Fedora Kinoite)"
ANSI_COLOR="0;38;2;138;43;226"
LOGO=bazzite-logo-icon
CPE_NAME="cpe:/o:universal-blue:bazzite:41"
DEFAULT_HOSTNAME="bazzite"
HOME_URL="https://bazzite.gg"
DOCUMENTATION_URL="https://docs.bazzite.gg"
SUPPORT_URL="https://discord.bazzite.gg"
BUG_REPORT_URL="https://github.com/ublue-os/bazzite/issues/"
SUPPORT_END=2025-12-15
VARIANT="Kinoite"
VARIANT_ID=bazzite-nvidia-open
OSTREE_VERSION='41.20250208.0'
BUILD_ID="Stable (F41.20250208)"
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
BUILD_ID="Stable (F41.20250208)"
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"

View File

@@ -0,0 +1,24 @@
NAME="Bluefin"
VERSION="41.20250216.1 (Silverblue)"
RELEASE_TYPE=stable
ID=bluefin
ID_LIKE="fedora"
VERSION_ID=41
VERSION_CODENAME="Archaeopteryx"
PLATFORM_ID="platform:f41"
PRETTY_NAME="Bluefin (Version: 41.20250216.1 / FROM Fedora Silverblue 41)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:universal-blue:bluefin:41"
DEFAULT_HOSTNAME="bluefin"
HOME_URL="https://projectbluefin.io"
DOCUMENTATION_URL="https://docs.projectbluefin.io"
SUPPORT_URL="https://github.com/ublue-os/bluefin/issues/"
BUG_REPORT_URL="https://github.com/ublue-os/bluefin/issues/"
SUPPORT_END=2025-12-15
VARIANT="Silverblue"
VARIANT_ID=bluefin
OSTREE_VERSION='41.20250216.1'
BUILD_ID="185146a"
IMAGE_ID="bluefin"
IMAGE_VERSION="41.20250216.1"

View File

@@ -0,0 +1,23 @@
NAME="Fedora Linux"
VERSION="41.20250117.3.0 (CoreOS)"
RELEASE_TYPE=stable
ID=fedora
VERSION_ID=41
VERSION_CODENAME=""
PLATFORM_ID="platform:f41"
PRETTY_NAME="Fedora CoreOS 41.20250117.3.0 (uCore)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:41"
HOME_URL="https://getfedora.org/coreos/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-coreos/"
SUPPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
BUG_REPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=41
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=41
SUPPORT_END=2025-12-15
VARIANT="CoreOS"
VARIANT_ID=coreos
OSTREE_VERSION='41.20250117.3.0'

View File

@@ -13,8 +13,10 @@ 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;
@@ -449,18 +451,44 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
.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!")
.ok_or_else(|| eyre!("`nix --version` output is empty"))?;
let is_lix = get_version_cmd_first_line_stdout.contains("Lix");
debug!(
output=%get_version_cmd_output,
?is_lix,
"`nix --version` output"
);
lazy_static! {
static ref NIX_VERSION_REGEX: Regex =
Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles");
}
if get_version_cmd_first_line_stdout.is_empty() {
return Err(eyre!("`nix --version` output was empty"));
}
let captures = NIX_VERSION_REGEX.captures(get_version_cmd_first_line_stdout);
let raw_version = match &captures {
None => {
return Err(eyre!(
"`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"
));
}
Some(captures) => &captures[1],
};
let version =
Version::parse(raw_version).wrap_err_with(|| format!("Unable to parse Nix version: {raw_version:?}"))?;
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) {
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages.
// Lix is based on Nix 2.18, so it doesn't!
let packages = if version >= Version::new(2, 21, 0) && !is_lix {
vec!["--all", "--impure"]
} else {
vec![".*"]
@@ -583,8 +611,7 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
if user_env
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.ends_with("user-environment"))
.unwrap_or(false)
.is_some_and(|name| name.ends_with("user-environment"))
{
Some(profile_dir)
} else {
@@ -609,10 +636,33 @@ pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
let asdf = require("asdf")?;
print_separator("asdf");
ctx.run_type()
.execute(&asdf)
.arg("update")
.status_checked_with_codes(&[42])?;
// asdf (>= 0.15.0) won't support the self-update command
//
// https://github.com/topgrade-rs/topgrade/issues/1007
let version_output = Command::new(&asdf).arg("version").output_checked_utf8()?;
// Example output
//
// ```
// $ asdf version
// v0.15.0-31e8c93
//
// ```
let version_stdout = version_output.stdout.trim();
// trim the starting 'v'
let mut remaining = version_stdout.trim_start_matches('v');
let idx = remaining
.find('-')
.expect("the output of `asdf version` changed, please file an issue to Topgrade");
// remove the hash part
remaining = &remaining[..idx];
let version = Version::parse(remaining).expect("should be a valid version");
if version < Version::new(0, 15, 0) {
ctx.run_type()
.execute(&asdf)
.arg("update")
.status_checked_with_codes(&[42])?;
}
ctx.run_type()
.execute(&asdf)
@@ -625,12 +675,12 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
print_separator("mise");
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
ctx.run_type()
.execute(&mise)
.args(["plugins", "update"])
.status_checked()
.status_checked()?;
ctx.run_type().execute(&mise).arg("upgrade").status_checked()
}
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
@@ -666,9 +716,7 @@ 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"));
let pyenv_dir = var("PYENV_ROOT").map_or_else(|_| HOME_DIR.join(".pyenv"), PathBuf::from);
if !pyenv_dir.exists() {
return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
@@ -689,8 +737,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
let bash = require("bash")?;
let sdkman_init_path = var("SDKMAN_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
.join("bin")
.join("sdkman-init.sh")
.require()
@@ -699,8 +746,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
print_separator("SDKMAN!");
let sdkman_config_path = var("SDKMAN_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
.join("etc")
.join("config")
.require()?;
@@ -753,9 +799,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
print_separator(t!("Bun Packages"));
let mut package_json: PathBuf = var("BUN_INSTALL")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
let mut package_json: PathBuf = var("BUN_INSTALL").map_or_else(|_| HOME_DIR.join(".bun"), PathBuf::from);
package_json.push("install/global/package.json");
if !package_json.exists() {

View File

@@ -42,10 +42,12 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
print_separator("winget");
ctx.run_type()
.execute(winget)
.args(["upgrade", "--all"])
.status_checked()
let mut args = vec!["upgrade", "--all"];
if ctx.config().winget_silent_install() {
args.push("--silent");
}
ctx.run_type().execute(winget).args(args).status_checked()
}
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
@@ -221,6 +223,14 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
}
}
pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> {
let powershell = powershell::Powershell::windows_powershell();
print_separator(t!("Microsoft Store"));
powershell.microsoft_store(ctx)
}
pub fn reboot() -> Result<()> {
// If this works, it won't return, but if it doesn't work, it may return a useful error
// message.

View File

@@ -9,7 +9,7 @@ use rust_i18n::t;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::terminal::{is_dumb, print_separator};
use crate::utils::{require_option, which, PathExt};
use crate::utils::{require_option, which};
use crate::Step;
pub struct Powershell {
@@ -30,7 +30,7 @@ impl Powershell {
.args(["-NoProfile", "-Command", "Split-Path $profile"])
.output_checked_utf8()
.map(|output| PathBuf::from(output.stdout.trim()))
.and_then(|p| p.require())
.and_then(super::super::utils::PathExt::require)
.ok()
});
@@ -70,11 +70,11 @@ impl Powershell {
let mut cmd = vec!["Update-Module"];
if ctx.config().verbose() {
cmd.push("-Verbose")
cmd.push("-Verbose");
}
if ctx.config().yes(Step::Powershell) {
cmd.push("-Force")
cmd.push("-Force");
}
println!("{}", t!("Updating modules..."));
@@ -119,4 +119,43 @@ impl Powershell {
.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(|_| ())
}
}

View File

@@ -126,7 +126,7 @@ impl<'a> TemporaryPowerOn<'a> {
}
}
impl<'a> Drop for TemporaryPowerOn<'a> {
impl Drop for TemporaryPowerOn<'_> {
fn drop(&mut self) {
let subcommand = if self.ctx.config().vagrant_always_suspend().unwrap_or(false) {
"suspend"
@@ -232,7 +232,7 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
}
if !found {
println!("{}", t!("No outdated boxes"))
println!("{}", t!("No outdated boxes"));
} else {
ctx.run_type()
.execute(&vagrant)

View File

@@ -128,7 +128,7 @@ impl Tmux {
.output_checked_utf8()?
.stdout
.lines()
.map(|l| l.parse())
.map(str::parse)
.collect::<Result<Vec<usize>, _>>()
.context("Failed to compute tmux windows")
}
@@ -181,19 +181,16 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
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 let Some(session_name) = ctx.get_tmux_session() {
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)?;
} else {
let name = tmux.new_unique_session("topgrade", window_name, command)?;
ctx.set_tmux_session(name);
}
Ok(())
}

View File

@@ -65,7 +65,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
if !status.success() {
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
} else {
println!("{}", t!("Plugins upgraded"))
println!("{}", t!("Plugins upgraded"));
}
}

View File

@@ -30,9 +30,7 @@ pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
}
fn zdotdir() -> PathBuf {
env::var("ZDOTDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.clone())
env::var("ZDOTDIR").map_or_else(|_| HOME_DIR.clone(), PathBuf::from)
}
pub fn zshrc() -> PathBuf {
@@ -66,8 +64,7 @@ pub fn run_antigen(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
let zshrc = zshrc().require()?;
env::var("ADOTDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
.map_or_else(|_| HOME_DIR.join("antigen.zsh"), PathBuf::from)
.require()?;
print_separator("antigen");
@@ -83,8 +80,7 @@ pub fn run_zgenom(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
let zshrc = zshrc().require()?;
env::var("ZGEN_SOURCE")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
.map_or_else(|_| HOME_DIR.join(".zgenom"), PathBuf::from)
.require()?;
print_separator("zgenom");
@@ -101,8 +97,7 @@ pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
zshrc().require()?;
env::var("ZPLUG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".zplug"))
.map_or_else(|_| HOME_DIR.join(".zplug"), PathBuf::from)
.require()?;
print_separator("zplug");
@@ -118,8 +113,7 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
let zshrc = zshrc().require()?;
env::var("ZINIT_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| XDG_DIRS.data_dir().join("zinit"))
.map_or_else(|_| XDG_DIRS.data_dir().join("zinit"), PathBuf::from)
.require()?;
print_separator("zinit");
@@ -153,8 +147,7 @@ pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
.output_checked_utf8()
.map(|o| o.stdout)
})
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".zim"))
.map_or_else(|_| HOME_DIR.join(".zim"), PathBuf::from)
.require()?;
print_separator("zim");

View File

@@ -22,13 +22,28 @@ pub struct Sudo {
}
impl Sudo {
/// Get the `sudo` binary or the `gsudo` binary in the case of `gsudo`
/// masquerading as the `sudo` binary.
fn determine_sudo_variant(sudo_p: PathBuf) -> (PathBuf, SudoKind) {
match which("gsudo") {
Some(gsudo_p) => {
match std::fs::canonicalize(&gsudo_p).unwrap() == std::fs::canonicalize(&sudo_p).unwrap() {
true => (gsudo_p, SudoKind::Gsudo),
false => (sudo_p, SudoKind::Sudo),
}
}
None => (sudo_p, SudoKind::Sudo),
}
}
/// Get the `sudo` binary for this platform.
pub fn detect() -> Option<Self> {
which("doas")
.map(|p| (p, SudoKind::Doas))
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
.or_else(|| which("sudo").map(Self::determine_sudo_variant))
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
.or_else(|| which("run0").map(|p| (p, SudoKind::Run0)))
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
.map(|(path, kind)| Self { path, kind })
}
@@ -65,9 +80,11 @@ impl Sudo {
cmd.arg("-v");
}
SudoKind::Gsudo => {
// Shows current user, cache and console status.
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
// so we just execute a dummy `echo` command so we have something
// unobtrusive to run.
// See: https://gerardog.github.io/gsudo/docs/usage
cmd.arg("status");
cmd.arg("echo");
}
SudoKind::Pkexec => {
// I don't think this does anything; `pkexec` usually asks for
@@ -79,6 +96,13 @@ impl Sudo {
// See: https://linux.die.net/man/1/pkexec
cmd.arg("echo");
}
SudoKind::Run0 => {
// `run0` uses polkit for authentication
// and thus has the same issues as `pkexec`.
//
// See: https://www.freedesktop.org/software/systemd/man/devel/run0.html
cmd.arg("echo");
}
SudoKind::Please => {
// From `man please`
// -w, --warm
@@ -115,6 +139,7 @@ pub enum SudoKind {
Sudo,
Gsudo,
Pkexec,
Run0,
Please,
}

View File

@@ -52,9 +52,7 @@ impl Terminal {
Self {
width: term.size_checked().map(|(_, w)| w),
term,
prefix: env::var("TOPGRADE_PREFIX")
.map(|prefix| format!("({prefix}) "))
.unwrap_or_else(|_| String::new()),
prefix: env::var("TOPGRADE_PREFIX").map_or_else(|_| String::new(), |prefix| format!("({prefix}) ")),
set_title: true,
display_time: true,
desktop_notification: false,
@@ -62,15 +60,15 @@ impl Terminal {
}
fn set_desktop_notifications(&mut self, desktop_notifications: bool) {
self.desktop_notification = desktop_notifications
self.desktop_notification = desktop_notifications;
}
fn set_title(&mut self, set_title: bool) {
self.set_title = set_title
self.set_title = set_title;
}
fn display_time(&mut self, display_time: bool) {
self.display_time = display_time
self.display_time = display_time;
}
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
@@ -223,8 +221,8 @@ impl Terminal {
let answer = loop {
match self.term.read_key() {
Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true),
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
Ok(Key::Char('y' | 'Y')) => break Ok(true),
Ok(Key::Char('s' | 'S')) => {
println!(
"\n\n{}\n",
t!("Dropping you to shell. Fix what you need and then exit the shell.")
@@ -235,12 +233,12 @@ impl Terminal {
break Ok(true);
}
}
Ok(Key::Char('n')) | Ok(Key::Char('N')) | Ok(Key::Enter) => break Ok(false),
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(false),
Err(e) => {
error!("Error reading from terminal: {}", e);
break Ok(false);
}
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => {
Ok(Key::Char('q' | 'Q')) => {
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input")
}
_ => (),
@@ -268,26 +266,26 @@ pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> {
}
pub fn print_separator<P: AsRef<str>>(message: P) {
TERMINAL.lock().unwrap().print_separator(message)
TERMINAL.lock().unwrap().print_separator(message);
}
#[allow(dead_code)]
pub fn print_error<P: AsRef<str>, Q: AsRef<str>>(key: Q, message: P) {
TERMINAL.lock().unwrap().print_error(key, message)
TERMINAL.lock().unwrap().print_error(key, message);
}
#[allow(dead_code)]
pub fn print_warning<P: AsRef<str>>(message: P) {
TERMINAL.lock().unwrap().print_warning(message)
TERMINAL.lock().unwrap().print_warning(message);
}
#[allow(dead_code)]
pub fn print_info<P: AsRef<str>>(message: P) {
TERMINAL.lock().unwrap().print_info(message)
TERMINAL.lock().unwrap().print_info(message);
}
pub fn print_result<P: AsRef<str>>(key: P, result: &StepResult) {
TERMINAL.lock().unwrap().print_result(key, result)
TERMINAL.lock().unwrap().print_result(key, result);
}
/// Tells whether the terminal is dumb.
@@ -316,7 +314,7 @@ pub fn prompt_yesno(question: &str) -> Result<bool, io::Error> {
}
pub fn notify_desktop<P: AsRef<str>>(message: P, timeout: Option<Duration>) {
TERMINAL.lock().unwrap().notify_desktop(message, timeout)
TERMINAL.lock().unwrap().notify_desktop(message, timeout);
}
pub fn display_time(display_time: bool) {

View File

@@ -86,7 +86,7 @@ pub fn editor() -> Vec<String> {
env::var("EDITOR")
.unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" }))
.split_whitespace()
.map(|s| s.to_owned())
.map(std::borrow::ToOwned::to_owned)
.collect()
}