Skip to content

Commit 08ae853

Browse files
committed
Add brew specific message for uv self update
1 parent 4b92f4f commit 08ae853

2 files changed

Lines changed: 97 additions & 4 deletions

File tree

crates/uv/src/install_source.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![cfg(not(feature = "self-update"))]
2+
3+
use std::{
4+
ffi::OsStr,
5+
path::{Path, PathBuf},
6+
};
7+
8+
/// Known sources for uv installations.
9+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10+
pub(crate) enum InstallSource {
11+
Homebrew,
12+
}
13+
14+
impl InstallSource {
15+
/// Attempt to infer the install source for the given executable path.
16+
fn from_path(path: &Path) -> Option<Self> {
17+
let canonical = path.canonicalize().unwrap_or_else(|_| PathBuf::from(path));
18+
19+
let components = canonical
20+
.components()
21+
.map(|component| component.as_os_str().to_owned())
22+
.collect::<Vec<_>>();
23+
24+
let cellar = OsStr::new("Cellar");
25+
let formula = OsStr::new("uv");
26+
27+
if components
28+
.windows(2)
29+
.any(|window| window[0] == cellar && window[1] == formula)
30+
{
31+
return Some(Self::Homebrew);
32+
}
33+
34+
None
35+
}
36+
37+
/// Detect how uv was installed by inspecting the current executable path.
38+
pub(crate) fn detect() -> Option<Self> {
39+
Self::from_path(&std::env::current_exe().ok()?)
40+
}
41+
42+
pub(crate) fn description(self) -> &'static str {
43+
match self {
44+
Self::Homebrew => "Homebrew",
45+
}
46+
}
47+
48+
pub(crate) fn update_instructions(self) -> &'static str {
49+
match self {
50+
Self::Homebrew => "brew update && brew upgrade uv",
51+
}
52+
}
53+
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::*;
58+
59+
#[test]
60+
fn detects_homebrew_cellar() {
61+
assert_eq!(
62+
InstallSource::from_path(Path::new("/opt/homebrew/Cellar/uv/0.9.11/bin/uv")),
63+
Some(InstallSource::Homebrew)
64+
);
65+
}
66+
67+
#[test]
68+
fn ignores_non_cellar_paths() {
69+
assert_eq!(
70+
InstallSource::from_path(Path::new("/usr/local/bin/uv")),
71+
None
72+
);
73+
}
74+
}

crates/uv/src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use settings::PipTreeSettings;
2121
use tokio::task::spawn_blocking;
2222
use tracing::{debug, instrument, trace};
2323

24+
#[cfg(not(feature = "self-update"))]
25+
use crate::install_source::InstallSource;
2426
use uv_cache::{Cache, Refresh};
2527
use uv_cache_info::Timestamp;
2628
#[cfg(feature = "self-update")]
@@ -59,6 +61,8 @@ use crate::settings::{
5961

6062
pub(crate) mod child;
6163
pub(crate) mod commands;
64+
#[cfg(not(feature = "self-update"))]
65+
mod install_source;
6266
pub(crate) mod logging;
6367
pub(crate) mod printer;
6468
pub(crate) mod settings;
@@ -1223,10 +1227,25 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
12231227
}
12241228
#[cfg(not(feature = "self-update"))]
12251229
Commands::Self_(_) => {
1226-
anyhow::bail!(
1227-
"uv was installed through an external package manager, and self-update \
1228-
is not available. Please use your package manager to update uv."
1229-
);
1230+
const BASE_MESSAGE: &str =
1231+
"uv was installed through an external package manager and cannot update itself.";
1232+
1233+
let message = match InstallSource::detect() {
1234+
Some(source) => format!(
1235+
concat!(
1236+
"{base}",
1237+
"\n",
1238+
"\n",
1239+
"hint: You installed uv using {}. To update uv, run:\n `{}`"
1240+
),
1241+
source.description(),
1242+
source.update_instructions().green(),
1243+
base = BASE_MESSAGE
1244+
),
1245+
None => format!("{BASE_MESSAGE} Please use your package manager to update uv.")
1246+
};
1247+
1248+
anyhow::bail!(message);
12301249
}
12311250
Commands::GenerateShellCompletion(args) => {
12321251
args.shell.generate(&mut Cli::command(), &mut stdout());

0 commit comments

Comments
 (0)