Refactor find_uv_bin and add a better error message (#14182)

Follows https://github.com/astral-sh/uv/pull/14181

Two goals here

- Remove duplicated logic and make the search order clear
- Resolve user confusion around the searched directories; we previously
only displayed the last attempt, which we rarely expect to be relevant
This commit is contained in:
Zanie Blue 2025-08-07 15:10:38 -05:00 committed by GitHub
parent a7c4634243
commit b1a036ccf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 119 additions and 66 deletions

View file

@ -592,6 +592,59 @@ impl TestContext {
filters.push((r"exit code: ".to_string(), "exit status: ".to_string()));
}
for (version, executable) in &python_versions {
// Add filtering for the interpreter path
filters.extend(
Self::path_patterns(executable)
.into_iter()
.map(|pattern| (pattern.to_string(), format!("[PYTHON-{version}]"))),
);
// Add filtering for the bin directory of the base interpreter path
let bin_dir = if cfg!(windows) {
// On Windows, the Python executable is in the root, not the bin directory
executable
.canonicalize()
.unwrap()
.parent()
.unwrap()
.join("Scripts")
} else {
executable
.canonicalize()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
};
filters.extend(
Self::path_patterns(bin_dir)
.into_iter()
.map(|pattern| (pattern.to_string(), format!("[PYTHON-BIN-{version}]"))),
);
// And for the symlink we created in the test the Python path
filters.extend(
Self::path_patterns(python_dir.join(version.to_string()))
.into_iter()
.map(|pattern| {
(
format!("{pattern}[a-zA-Z0-9]*"),
format!("[PYTHON-{version}]"),
)
}),
);
// Add Python patch version filtering unless explicitly requested to ensure
// snapshots are patch version agnostic when it is not a part of the test.
if version.patch().is_none() {
filters.push((
format!(r"({})\.\d+", regex::escape(version.to_string().as_str())),
"$1.[X]".to_string(),
));
}
}
filters.extend(
Self::path_patterns(&bin_dir)
.into_iter()
@ -634,35 +687,6 @@ impl TestContext {
));
filters.push((r"[\\/]Lib[\\/]".to_string(), "/[PYTHON-LIB]/".to_string()));
for (version, executable) in &python_versions {
// Add filtering for the interpreter path
filters.extend(
Self::path_patterns(executable)
.into_iter()
.map(|pattern| (pattern.to_string(), format!("[PYTHON-{version}]"))),
);
// And for the symlink we created in the test the Python path
filters.extend(
Self::path_patterns(python_dir.join(version.to_string()))
.into_iter()
.map(|pattern| {
(
format!("{pattern}[a-zA-Z0-9]*"),
format!("[PYTHON-{version}]"),
)
}),
);
// Add Python patch version filtering unless explicitly requested to ensure
// snapshots are patch version agnostic when it is not a part of the test.
if version.patch().is_none() {
filters.push((
format!(r"({})\.\d+", regex::escape(version.to_string().as_str())),
"$1.[X]".to_string(),
));
}
}
filters.extend(
Self::path_patterns(&temp_dir)
.into_iter()

View file

@ -25,7 +25,10 @@ fn find_uv_bin_venv() {
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter());
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Install in a virtual environment
uv_snapshot!(context.filters(), context.pip_install()
@ -64,8 +67,8 @@ fn find_uv_bin_target() {
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// with_filtered_virtualenv_bin only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin[\\/]".to_string(), "/[BIN]/".to_string()));
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Install in a target directory
uv_snapshot!(context.filters(), context.pip_install()
@ -106,7 +109,10 @@ fn find_uv_bin_prefix() {
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter());
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Install in a prefix directory
let prefix = context.temp_dir.child("prefix");
@ -143,9 +149,13 @@ fn find_uv_bin_prefix() {
----- stderr -----
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "[TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 37, in find_uv_bin
raise FileNotFoundError(path)
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
File "[TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
raise UvNotFound(
uv._find_uv.UvNotFound: Could not find the uv binary in any of the following locations:
- [VENV]/[BIN]
- [PYTHON-BIN-3.12]
- [USER_SCHEME]/[BIN]
- [TEMP_DIR]/prefix/[PYTHON-LIB]/site-packages/[BIN]
"#
);
}
@ -156,7 +166,10 @@ fn find_uv_bin_base_prefix() {
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter());
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Test base prefix fallback by mutating sys.base_prefix
// First, create a "base" environment with fake-uv installed
@ -204,7 +217,10 @@ fn find_uv_bin_in_ephemeral_environment() -> anyhow::Result<()> {
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter());
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Create a minimal pyproject.toml
let pyproject_toml = context.temp_dir.child("pyproject.toml");
@ -237,9 +253,13 @@ fn find_uv_bin_in_ephemeral_environment() -> anyhow::Result<()> {
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "[CACHE_DIR]/archive-v0/[HASH]/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 37, in find_uv_bin
raise FileNotFoundError(path)
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
File "[CACHE_DIR]/archive-v0/[HASH]/[PYTHON-LIB]/site-packages/uv/_find_uv.py", line 36, in find_uv_bin
raise UvNotFound(
uv._find_uv.UvNotFound: Could not find the uv binary in any of the following locations:
- [CACHE_DIR]/builds-v0/[TMP]/[BIN]
- [PYTHON-BIN-3.12]
- [USER_SCHEME]/[BIN]
- [CACHE_DIR]/archive-v0/[HASH]/[PYTHON-LIB]/site-packages/[BIN]
"#
);
@ -252,7 +272,10 @@ fn find_uv_bin_in_parent_of_ephemeral_environment() -> anyhow::Result<()> {
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.with_filtered_exe_suffix()
.with_filter(user_scheme_bin_filter());
.with_filter(user_scheme_bin_filter())
// Target installs always use "bin" on all platforms. On Windows,
// `with_filtered_virtualenv_bin` only filters "Scripts", not "bin"
.with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string()));
// Add the fake-uv package as a dependency
let pyproject_toml = context.temp_dir.child("pyproject.toml");
@ -295,9 +318,13 @@ fn find_uv_bin_in_parent_of_ephemeral_environment() -> anyhow::Result<()> {
+ sniffio==1.3.1
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "[SITE_PACKAGES]/uv/_find_uv.py", line 37, in find_uv_bin
raise FileNotFoundError(path)
FileNotFoundError: [USER_SCHEME]/[BIN]/uv
File "[SITE_PACKAGES]/uv/_find_uv.py", line 36, in find_uv_bin
raise UvNotFound(
uv._find_uv.UvNotFound: Could not find the uv binary in any of the following locations:
- [CACHE_DIR]/builds-v0/[TMP]/[BIN]
- [PYTHON-BIN-3.12]
- [USER_SCHEME]/[BIN]
- [SITE_PACKAGES]/[BIN]
"#
);