Skip to content

Commit b26e08f

Browse files
committed
uv-tool: ignore existing environment on interpreter mismatch
1 parent c87ce7a commit b26e08f

File tree

3 files changed

+92
-18
lines changed

3 files changed

+92
-18
lines changed

crates/uv-tool/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ impl InstalledTools {
204204
match PythonEnvironment::from_root(&environment_path, cache) {
205205
Ok(venv) => {
206206
debug!(
207-
"Using existing environment for tool `{name}`: {}",
207+
"Found existing environment for tool `{name}`: {}",
208208
environment_path.user_display()
209209
);
210210
Ok(Some(venv))

crates/uv/src/commands/tool/install.rs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -276,19 +276,27 @@ pub(crate) async fn install(
276276
installed_tools
277277
.get_environment(&from.name, &cache)?
278278
.filter(|environment| {
279-
python_request.as_ref().map_or(true, |python_request| {
280-
if python_request.satisfied(environment.interpreter(), &cache) {
281-
debug!("Found existing environment for `{from}`", from = from.name.cyan());
282-
true
283-
} else {
284-
let _ = writeln!(
285-
printer.stderr(),
286-
"Existing environment for `{from}` does not satisfy the requested Python interpreter",
287-
from = from.name.cyan(),
288-
);
289-
false
290-
}
291-
})
279+
// NOTE(lucab): this compares `base_prefix` paths as a proxy for
280+
// detecting interpreters mismatches. Directly comparing interpreters
281+
// (by paths or binaries on-disk) would result in several false
282+
// positives on Windows due to file-copying and shims.
283+
let old_base_prefix = environment.interpreter().sys_base_prefix();
284+
let selected_base_prefix = interpreter.sys_base_prefix();
285+
if old_base_prefix == selected_base_prefix {
286+
debug!(
287+
"Found existing interpreter for tool `{}`: {}",
288+
from.name,
289+
environment.interpreter().sys_executable().display()
290+
);
291+
true
292+
} else {
293+
let _ = writeln!(
294+
printer.stderr(),
295+
"Ignored existing environment for `{from}` due to stale Python interpreter",
296+
from = from.name.cyan(),
297+
);
298+
false
299+
}
292300
});
293301

294302
// If the requested and receipt requirements are the same...

crates/uv/tests/tool_install.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,9 +2072,10 @@ fn tool_install_upgrade() {
20722072
});
20732073
}
20742074

2075-
/// Test reinstalling tools with varying `--python` requests.
2075+
/// Test reinstalling tools with varying `--python` and
2076+
/// `--python-preference` parameters.
20762077
#[test]
2077-
fn tool_install_python_request() {
2078+
fn tool_install_python_params() {
20782079
let context = TestContext::new_with_versions(&["3.11", "3.12"])
20792080
.with_filtered_counts()
20802081
.with_filtered_exe_suffix();
@@ -2122,10 +2123,12 @@ fn tool_install_python_request() {
21222123
`black` is already installed
21232124
"###);
21242125

2125-
// Install with Python 3.11 (incompatible).
2126+
// Install with system Python 3.11 (different version, incompatible).
21262127
uv_snapshot!(context.filters(), context.tool_install()
21272128
.arg("-p")
21282129
.arg("3.11")
2130+
.arg("--python-preference")
2131+
.arg("only-system")
21292132
.arg("black")
21302133
.env("UV_TOOL_DIR", tool_dir.as_os_str())
21312134
.env("XDG_BIN_HOME", bin_dir.as_os_str())
@@ -2135,7 +2138,7 @@ fn tool_install_python_request() {
21352138
----- stdout -----
21362139
21372140
----- stderr -----
2138-
Existing environment for `black` does not satisfy the requested Python interpreter
2141+
Ignored existing environment for `black` due to stale Python interpreter
21392142
Resolved [N] packages in [TIME]
21402143
Prepared [N] packages in [TIME]
21412144
Installed [N] packages in [TIME]
@@ -2147,6 +2150,69 @@ fn tool_install_python_request() {
21472150
+ platformdirs==4.2.0
21482151
Installed 2 executables: black, blackd
21492152
"###);
2153+
2154+
// Install with system Python 3.11 (compatible).
2155+
uv_snapshot!(context.filters(), context.tool_install()
2156+
.arg("-p")
2157+
.arg("3.11")
2158+
.arg("--python-preference")
2159+
.arg("only-system")
2160+
.arg("black")
2161+
.env("UV_TOOL_DIR", tool_dir.as_os_str())
2162+
.env("XDG_BIN_HOME", bin_dir.as_os_str())
2163+
.env("PATH", bin_dir.as_os_str()), @r###"
2164+
success: true
2165+
exit_code: 0
2166+
----- stdout -----
2167+
2168+
----- stderr -----
2169+
`black` is already installed
2170+
"###);
2171+
2172+
// Install with managed Python 3.11 (different source, incompatible).
2173+
uv_snapshot!(context.filters(), context.tool_install()
2174+
.arg("-p")
2175+
.arg("3.11")
2176+
.arg("--python-preference")
2177+
.arg("only-managed")
2178+
.arg("black")
2179+
.env("UV_TOOL_DIR", tool_dir.as_os_str())
2180+
.env("XDG_BIN_HOME", bin_dir.as_os_str())
2181+
.env("PATH", bin_dir.as_os_str()), @r###"
2182+
success: true
2183+
exit_code: 0
2184+
----- stdout -----
2185+
2186+
----- stderr -----
2187+
Ignored existing environment for `black` due to stale Python interpreter
2188+
Resolved [N] packages in [TIME]
2189+
Installed [N] packages in [TIME]
2190+
+ black==24.3.0
2191+
+ click==8.1.7
2192+
+ mypy-extensions==1.0.0
2193+
+ packaging==24.0
2194+
+ pathspec==0.12.1
2195+
+ platformdirs==4.2.0
2196+
Installed 2 executables: black, blackd
2197+
"###);
2198+
2199+
// Install with managed Python 3.11 (compatible).
2200+
uv_snapshot!(context.filters(), context.tool_install()
2201+
.arg("-p")
2202+
.arg("3.11")
2203+
.arg("--python-preference")
2204+
.arg("only-managed")
2205+
.arg("black")
2206+
.env("UV_TOOL_DIR", tool_dir.as_os_str())
2207+
.env("XDG_BIN_HOME", bin_dir.as_os_str())
2208+
.env("PATH", bin_dir.as_os_str()), @r###"
2209+
success: true
2210+
exit_code: 0
2211+
----- stdout -----
2212+
2213+
----- stderr -----
2214+
`black` is already installed
2215+
"###);
21502216
}
21512217

21522218
/// Test preserving a tool environment when new but incompatible requirements are requested.

0 commit comments

Comments
 (0)