mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 04:17:37 +00:00
Add a uv self update command (#2228)
## Summary Powered by Axo: https://github.com/axodotdev/axoupdater. Closes https://github.com/astral-sh/uv/issues/1591. ## Test Plan To test locally: - `rm -f ~/.config/uv/uv-receipt.json /Users/crmarsh/.cargo/bin/uv` - `curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.1.14/uv-installer.sh | sh` - `cargo run self update` Up-to-date:  Updated:  No receipt: 
This commit is contained in:
parent
c4107f9c40
commit
1bf48c91f2
6 changed files with 331 additions and 14 deletions
|
|
@ -35,6 +35,7 @@ uv-warnings = { path = "../uv-warnings" }
|
|||
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
axoupdater = { workspace = true, features = ["github_releases", "tokio"] }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "string"] }
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ pub(crate) use pip_list::pip_list;
|
|||
pub(crate) use pip_show::pip_show;
|
||||
pub(crate) use pip_sync::pip_sync;
|
||||
pub(crate) use pip_uninstall::pip_uninstall;
|
||||
pub(crate) use self_update::self_update;
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::compile_tree;
|
||||
|
|
@ -36,6 +37,7 @@ mod pip_show;
|
|||
mod pip_sync;
|
||||
mod pip_uninstall;
|
||||
mod reporters;
|
||||
mod self_update;
|
||||
mod venv;
|
||||
mod version;
|
||||
|
||||
|
|
|
|||
114
crates/uv/src/commands/self_update.rs
Normal file
114
crates/uv/src/commands/self_update.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use axoupdater::{AxoUpdater, AxoupdateError};
|
||||
use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
use uv_client::BetterReqwestError;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
/// Attempt to update the `uv` binary.
|
||||
pub(crate) async fn self_update(printer: Printer) -> Result<ExitStatus> {
|
||||
let mut updater = AxoUpdater::new_for("uv");
|
||||
updater.disable_installer_output();
|
||||
|
||||
// Load the "install receipt" for the current binary. If the receipt is not found, then
|
||||
// `uv` was likely installed via a package manager.
|
||||
let Ok(updater) = updater.load_receipt() else {
|
||||
debug!("no receipt found; assuming `uv` was installed via a package manager");
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format_args!(
|
||||
concat!(
|
||||
"{}{} Self-update is only available for `uv` binaries installed via the standalone installation scripts.",
|
||||
"\n",
|
||||
"\n",
|
||||
"If you installed `uv` with `pip`, `brew`, or another package manager, update `uv` with `pip install --upgrade`, `brew upgrade`, or similar."
|
||||
),
|
||||
"warning".yellow().bold(),
|
||||
":".bold()
|
||||
)
|
||||
)?;
|
||||
return Ok(ExitStatus::Error);
|
||||
};
|
||||
|
||||
// Ensure the receipt is for the current binary. If it's not, then the user likely has multiple
|
||||
// `uv` binaries installed, and the current binary was _not_ installed via the standalone
|
||||
// installation scripts.
|
||||
if !updater.check_receipt_is_for_this_executable()? {
|
||||
debug!(
|
||||
"receipt is not for this executable; assuming `uv` was installed via a package manager"
|
||||
);
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format_args!(
|
||||
concat!(
|
||||
"{}{} Self-update is only available for `uv` binaries installed via the standalone installation scripts.",
|
||||
"\n",
|
||||
"\n",
|
||||
"If you installed `uv` with `pip`, `brew`, or another package manager, update `uv` with `pip install --upgrade`, `brew upgrade`, or similar."
|
||||
),
|
||||
"warning".yellow().bold(),
|
||||
":".bold()
|
||||
)
|
||||
)?;
|
||||
return Ok(ExitStatus::Error);
|
||||
}
|
||||
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format_args!(
|
||||
"{}{} Checking for updates...",
|
||||
"info".cyan().bold(),
|
||||
":".bold()
|
||||
)
|
||||
)?;
|
||||
|
||||
// Run the updater. This involves a network request, since we need to determine the latest
|
||||
// available version of `uv`.
|
||||
match updater.run().await {
|
||||
Ok(Some(result)) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format_args!(
|
||||
"{}{} Upgraded `uv` to {}! {}",
|
||||
"success".green().bold(),
|
||||
":".bold(),
|
||||
format!("v{}", result.new_version).bold().white(),
|
||||
format!(
|
||||
"https://github.com/astral-sh/uv/releases/tag/{}",
|
||||
result.new_version_tag
|
||||
)
|
||||
.cyan()
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(None) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{}",
|
||||
format_args!(
|
||||
"{}{} You're on the latest version of `uv` ({}).",
|
||||
"success".green().bold(),
|
||||
":".bold(),
|
||||
format!("v{}", env!("CARGO_PKG_VERSION")).bold().white()
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(if let AxoupdateError::Reqwest(err) = err {
|
||||
BetterReqwestError::from(err).into()
|
||||
} else {
|
||||
err.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
@ -137,6 +137,9 @@ enum Commands {
|
|||
Venv(VenvArgs),
|
||||
/// Manage the cache.
|
||||
Cache(CacheNamespace),
|
||||
/// Manage the `uv` executable.
|
||||
#[clap(name = "self")]
|
||||
Self_(SelfNamespace),
|
||||
/// Remove all items from the cache.
|
||||
#[clap(hide = true)]
|
||||
Clean(CleanArgs),
|
||||
|
|
@ -150,6 +153,18 @@ enum Commands {
|
|||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct SelfNamespace {
|
||||
#[clap(subcommand)]
|
||||
command: SelfCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum SelfCommand {
|
||||
/// Update `uv` to the latest version.
|
||||
Update,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct CacheNamespace {
|
||||
#[clap(subcommand)]
|
||||
|
|
@ -171,13 +186,6 @@ struct CleanArgs {
|
|||
package: Vec<PackageName>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
struct DirArgs {
|
||||
/// The packages to remove from the cache.
|
||||
package: Vec<PackageName>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct PipNamespace {
|
||||
#[clap(subcommand)]
|
||||
|
|
@ -1794,6 +1802,9 @@ async fn run() -> Result<ExitStatus> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
Commands::Self_(SelfNamespace {
|
||||
command: SelfCommand::Update,
|
||||
}) => commands::self_update(printer).await,
|
||||
Commands::Version { output_format } => {
|
||||
commands::version(output_format, &mut stdout())?;
|
||||
Ok(ExitStatus::Success)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue