diff --git a/crates/uv/src/bin/uvx.rs b/crates/uv/src/bin/uvx.rs index cd00ed9fd..5673d2706 100644 --- a/crates/uv/src/bin/uvx.rs +++ b/crates/uv/src/bin/uvx.rs @@ -1,4 +1,5 @@ use std::convert::Infallible; +use std::path::{Path, PathBuf}; use std::{ ffi::OsString, process::{Command, ExitCode, ExitStatus}, @@ -22,6 +23,57 @@ fn exec_spawn(cmd: &mut Command) -> std::io::Result { } } +/// Assuming the binary is called something like `uvx@1.2.3(.exe)`, compute the `@1.2.3(.exe)` part +/// so that we can preferentially find `uv@1.2.3(.exe)`, for folks who like managing multiple +/// installs in this way. +fn get_uvx_suffix(current_exe: &Path) -> Option<&str> { + let os_file_name = current_exe.file_name()?; + let file_name_str = os_file_name.to_str()?; + file_name_str.strip_prefix("uvx") +} + +/// Gets the path to `uv`, given info about `uvx` +fn get_uv_path(current_exe_parent: &Path, uvx_suffix: Option<&str>) -> std::io::Result { + // First try to find a matching suffixed `uv`, e.g. `uv@1.2.3(.exe)` + let uv_with_suffix = uvx_suffix.map(|suffix| current_exe_parent.join(format!("uv{suffix}"))); + if let Some(uv_with_suffix) = &uv_with_suffix { + #[allow(clippy::print_stderr, reason = "printing a very rare warning")] + match uv_with_suffix.try_exists() { + Ok(true) => return Ok(uv_with_suffix.to_owned()), + Ok(false) => { /* definitely not there, proceed to fallback */ } + Err(err) => { + // We don't know if `uv@1.2.3` exists, something errored when checking. + // We *could* blindly use `uv@1.2.3` in this case, as the code below does, however + // in this extremely narrow corner case it's *probably* better to default to `uv`, + // since we don't want to mess up existing users who weren't using suffixes? + eprintln!( + "warning: failed to determine if `{}` exists, trying `uv` instead: {err}", + uv_with_suffix.display() + ); + } + } + } + + // Then just look for good ol' `uv` + let uv = current_exe_parent.join(format!("uv{}", std::env::consts::EXE_SUFFIX)); + // If we are sure the `uv` binary does not exist, display a clearer error message. + // If we're not certain if uv exists (try_exists == Err), keep going and hope it works. + if matches!(uv.try_exists(), Ok(false)) { + let message = if let Some(uv_with_suffix) = uv_with_suffix { + format!( + "Could not find the `uv` binary at either of:\n {}\n {}", + uv_with_suffix.display(), + uv.display(), + ) + } else { + format!("Could not find the `uv` binary at: {}", uv.display()) + }; + Err(std::io::Error::new(std::io::ErrorKind::NotFound, message)) + } else { + Ok(uv) + } +} + fn run() -> std::io::Result { let current_exe = std::env::current_exe()?; let Some(bin) = current_exe.parent() else { @@ -30,7 +82,8 @@ fn run() -> std::io::Result { "Could not determine the location of the `uvx` binary", )); }; - let uv = bin.join(format!("uv{}", std::env::consts::EXE_SUFFIX)); + let uvx_suffix = get_uvx_suffix(¤t_exe); + let uv = get_uv_path(bin, uvx_suffix)?; let args = ["tool", "uvx"] .iter() .map(OsString::from) @@ -38,14 +91,6 @@ fn run() -> std::io::Result { .chain(std::env::args_os().skip(1)) .collect::>(); - // If we are sure the uv binary does not exist, display a clearer error message - if matches!(uv.try_exists(), Ok(false)) { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Could not find the `uv` binary at: {}", uv.display()), - )); - } - let mut cmd = Command::new(uv); cmd.args(&args); match exec_spawn(&mut cmd)? {}