Search in the user scheme scripts directory last in `find_uv_bin` (#14191)

We should definitely not pick up user-level installations unless we
can't find uv anywhere else. Otherwise, e.g., we would find a uv
installed with `pipx install uv` before the one matching the uv module.
This commit is contained in:
Zanie Blue 2025-08-08 06:46:32 -05:00 committed by GitHub
parent 9daadbfab0
commit a9302906ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 79 additions and 3 deletions

View File

@ -1,5 +1,5 @@
use assert_cmd::assert::OutputAssertExt;
use assert_fs::prelude::{FileWriteStr, PathChild};
use assert_fs::prelude::{FileTouch, FileWriteStr, PathChild, PathCreateDir};
use indoc::{formatdoc, indoc};
use uv_fs::Simplified;
@ -306,3 +306,79 @@ fn find_uv_bin_in_parent_of_ephemeral_environment() -> anyhow::Result<()> {
Ok(())
}
#[test]
fn find_uv_bin_user_bin() {
let context = TestContext::new("3.12")
.with_filtered_python_names()
.with_filtered_virtualenv_bin()
.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()));
// Add uv to `~/.local/bin`
let bin = if cfg!(unix) {
context.home_dir.child(".local").child("bin")
} else {
context
.user_config_dir
.child("Python")
.child("Python312")
.child("Scripts")
};
bin.create_dir_all().unwrap();
bin.child(format!("uv{}", std::env::consts::EXE_SUFFIX))
.touch()
.unwrap();
// Install in a virtual environment
uv_snapshot!(context.filters(), context.pip_install()
.arg(context.workspace_root.join("scripts/packages/fake-uv")), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ uv==0.1.0 (from file://[WORKSPACE]/scripts/packages/fake-uv)
"
);
// We should find the binary in the virtual environment first
uv_snapshot!(context.filters(), context.python_command()
.arg("-c")
.arg("import uv; print(uv.find_uv_bin())"), @r"
success: true
exit_code: 0
----- stdout -----
[VENV]/[BIN]/uv
----- stderr -----
"
);
// Remove the virtual environment one for some reason
fs_err::remove_file(if cfg!(unix) {
context.venv.child("bin").child("uv")
} else {
context.venv.child("Scripts").child("uv.exe")
})
.unwrap();
// We should find the binary in the bin now
uv_snapshot!(context.filters(), context.python_command()
.arg("-c")
.arg("import uv; print(uv.find_uv_bin())"), @r"
success: true
exit_code: 0
----- stdout -----
[USER_SCHEME]/[BIN]/uv
----- stderr -----
"
);
}

View File

@ -18,8 +18,6 @@ def find_uv_bin() -> str:
sysconfig.get_path("scripts"),
# The scripts directory for the base prefix
sysconfig.get_path("scripts", vars={"base": sys.base_prefix}),
# The user scheme scripts directory, e.g., `~/.local/bin`
sysconfig.get_path("scripts", scheme=_user_scheme()),
# Above the package root, e.g., from `pip install --prefix` or `uv run --with`
(
# On Windows, with module path `<prefix>/Lib/site-packages/uv`
@ -33,6 +31,8 @@ def find_uv_bin() -> str:
# Adjacent to the package root, e.g., from `pip install --target`
# with module path `<target>/uv`
_join(_matching_parents(_module_path(), "uv"), "bin"),
# The user scheme scripts directory, e.g., `~/.local/bin`
sysconfig.get_path("scripts", scheme=_user_scheme()),
]
seen = []