mirror of https://github.com/astral-sh/uv
Add brew specific message for `uv self update` (#16838)
Resolves https://github.com/astral-sh/uv/issues/16833 `uv self update` could error with a better message if it knows the user has installed it with brew. This diff adds an `InstallSource` we can use the detect where a `uv` binary comes from and augment error messages with better context. We're only adding brew for now, but it could easily be extend with other detection heuristics.
This commit is contained in:
parent
d3cd94ecaf
commit
ee6e3be815
|
|
@ -0,0 +1,74 @@
|
|||
#![cfg(not(feature = "self-update"))]
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Known sources for uv installations.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum InstallSource {
|
||||
Homebrew,
|
||||
}
|
||||
|
||||
impl InstallSource {
|
||||
/// Attempt to infer the install source for the given executable path.
|
||||
fn from_path(path: &Path) -> Option<Self> {
|
||||
let canonical = path.canonicalize().unwrap_or_else(|_| PathBuf::from(path));
|
||||
|
||||
let components = canonical
|
||||
.components()
|
||||
.map(|component| component.as_os_str().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cellar = OsStr::new("Cellar");
|
||||
let formula = OsStr::new("uv");
|
||||
|
||||
if components
|
||||
.windows(2)
|
||||
.any(|window| window[0] == cellar && window[1] == formula)
|
||||
{
|
||||
return Some(Self::Homebrew);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Detect how uv was installed by inspecting the current executable path.
|
||||
pub(crate) fn detect() -> Option<Self> {
|
||||
Self::from_path(&std::env::current_exe().ok()?)
|
||||
}
|
||||
|
||||
pub(crate) fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Homebrew => "Homebrew",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_instructions(self) -> &'static str {
|
||||
match self {
|
||||
Self::Homebrew => "brew update && brew upgrade uv",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn detects_homebrew_cellar() {
|
||||
assert_eq!(
|
||||
InstallSource::from_path(Path::new("/opt/homebrew/Cellar/uv/0.9.11/bin/uv")),
|
||||
Some(InstallSource::Homebrew)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_non_cellar_paths() {
|
||||
assert_eq!(
|
||||
InstallSource::from_path(Path::new("/usr/local/bin/uv")),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ use settings::PipTreeSettings;
|
|||
use tokio::task::spawn_blocking;
|
||||
use tracing::{debug, instrument, trace};
|
||||
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
use crate::install_source::InstallSource;
|
||||
use uv_cache::{Cache, Refresh};
|
||||
use uv_cache_info::Timestamp;
|
||||
#[cfg(feature = "self-update")]
|
||||
|
|
@ -59,6 +61,8 @@ use crate::settings::{
|
|||
|
||||
pub(crate) mod child;
|
||||
pub(crate) mod commands;
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
mod install_source;
|
||||
pub(crate) mod logging;
|
||||
pub(crate) mod printer;
|
||||
pub(crate) mod settings;
|
||||
|
|
@ -1249,10 +1253,22 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
}
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
Commands::Self_(_) => {
|
||||
anyhow::bail!(
|
||||
"uv was installed through an external package manager, and self-update \
|
||||
is not available. Please use your package manager to update uv."
|
||||
);
|
||||
const BASE_MESSAGE: &str =
|
||||
"uv was installed through an external package manager and cannot update itself.";
|
||||
|
||||
let message = match InstallSource::detect() {
|
||||
Some(source) => format!(
|
||||
"{base}\n\n{hint}{colon} You installed uv using {}. To update uv, run `{}`",
|
||||
source.description(),
|
||||
source.update_instructions().green(),
|
||||
hint = "hint".bold().cyan(),
|
||||
colon = ":".bold(),
|
||||
base = BASE_MESSAGE
|
||||
),
|
||||
None => format!("{BASE_MESSAGE} Please use your package manager to update uv."),
|
||||
};
|
||||
|
||||
anyhow::bail!(message);
|
||||
}
|
||||
Commands::GenerateShellCompletion(args) => {
|
||||
args.shell.generate(&mut Cli::command(), &mut stdout());
|
||||
|
|
|
|||
Loading…
Reference in New Issue