mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
uv-tool/install: ignore existing environments on interpreter mismatch (#7451)
This changes `uv tool install` behavior with regards to re-using existing environments. In particular, this replaces the existing version-matching logic with a tighter one, enforcing a same-interpreter match. This allows to properly switch between system and managed interpreter, at the cost of more eagerly invalidating existing environments every time there is an interpreter change. Closes: https://github.com/astral-sh/uv/issues/7320
This commit is contained in:
parent
fda227616c
commit
969b4a2222
3 changed files with 170 additions and 18 deletions
|
@ -204,7 +204,7 @@ impl InstalledTools {
|
|||
match PythonEnvironment::from_root(&environment_path, cache) {
|
||||
Ok(venv) => {
|
||||
debug!(
|
||||
"Using existing environment for tool `{name}`: {}",
|
||||
"Found existing environment for tool `{name}`: {}",
|
||||
environment_path.user_display()
|
||||
);
|
||||
Ok(Some(venv))
|
||||
|
|
|
@ -7,7 +7,7 @@ use owo_colors::OwoColorize;
|
|||
use pep440_rs::{VersionSpecifier, VersionSpecifiers};
|
||||
use pep508_rs::MarkerTree;
|
||||
use pypi_types::{Requirement, RequirementSource};
|
||||
use tracing::debug;
|
||||
use tracing::trace;
|
||||
use uv_cache::{Cache, Refresh};
|
||||
use uv_cache_info::Timestamp;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
|
@ -276,19 +276,27 @@ pub(crate) async fn install(
|
|||
installed_tools
|
||||
.get_environment(&from.name, &cache)?
|
||||
.filter(|environment| {
|
||||
python_request.as_ref().map_or(true, |python_request| {
|
||||
if python_request.satisfied(environment.interpreter(), &cache) {
|
||||
debug!("Found existing environment for `{from}`", from = from.name.cyan());
|
||||
true
|
||||
} else {
|
||||
let _ = writeln!(
|
||||
printer.stderr(),
|
||||
"Existing environment for `{from}` does not satisfy the requested Python interpreter",
|
||||
from = from.name.cyan(),
|
||||
);
|
||||
false
|
||||
}
|
||||
})
|
||||
// NOTE(lucab): this compares `base_prefix` paths as a proxy for
|
||||
// detecting interpreters mismatches. Directly comparing interpreters
|
||||
// (by paths or binaries on-disk) would result in several false
|
||||
// positives on Windows due to file-copying and shims.
|
||||
let old_base_prefix = environment.interpreter().sys_base_prefix();
|
||||
let selected_base_prefix = interpreter.sys_base_prefix();
|
||||
if old_base_prefix == selected_base_prefix {
|
||||
trace!(
|
||||
"Existing interpreter matches the requested interpreter for `{}`: {}",
|
||||
from.name,
|
||||
environment.interpreter().sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
let _ = writeln!(
|
||||
printer.stderr(),
|
||||
"Ignoring existing environment for `{from}`: the requested Python interpreter does not match the environment interpreter",
|
||||
from = from.name.cyan(),
|
||||
);
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// If the requested and receipt requirements are the same...
|
||||
|
|
|
@ -2074,7 +2074,7 @@ fn tool_install_upgrade() {
|
|||
|
||||
/// Test reinstalling tools with varying `--python` requests.
|
||||
#[test]
|
||||
fn tool_install_python_request() {
|
||||
fn tool_install_python_requests() {
|
||||
let context = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
|
@ -2122,7 +2122,7 @@ fn tool_install_python_request() {
|
|||
`black` is already installed
|
||||
"###);
|
||||
|
||||
// Install with Python 3.11 (incompatible).
|
||||
// // Install with Python 3.11 (incompatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.11")
|
||||
|
@ -2135,7 +2135,7 @@ fn tool_install_python_request() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Existing environment for `black` does not satisfy the requested Python interpreter
|
||||
Ignoring existing environment for `black`: the requested Python interpreter does not match the environment interpreter
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
|
@ -2149,6 +2149,150 @@ fn tool_install_python_request() {
|
|||
"###);
|
||||
}
|
||||
|
||||
/// Test reinstalling tools with varying `--python` and
|
||||
/// `--python-preference` parameters.
|
||||
#[ignore = "https://github.com/astral-sh/uv/issues/7473"]
|
||||
#[test]
|
||||
fn tool_install_python_preference() {
|
||||
let context = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
|
||||
// Install `black`.
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.12")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ black==24.3.0
|
||||
+ click==8.1.7
|
||||
+ mypy-extensions==1.0.0
|
||||
+ packaging==24.0
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.2.0
|
||||
Installed 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
// Install with Python 3.12 (compatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.12")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
`black` is already installed
|
||||
"###);
|
||||
|
||||
// Install with system Python 3.11 (different version, incompatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.11")
|
||||
.arg("--python-preference")
|
||||
.arg("only-system")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Ignoring existing environment for `black`: the requested Python interpreter does not match the environment interpreter
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ black==24.3.0
|
||||
+ click==8.1.7
|
||||
+ mypy-extensions==1.0.0
|
||||
+ packaging==24.0
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.2.0
|
||||
Installed 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
// Install with system Python 3.11 (compatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.11")
|
||||
.arg("--python-preference")
|
||||
.arg("only-system")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
`black` is already installed
|
||||
"###);
|
||||
|
||||
// Install with managed Python 3.11 (different source, incompatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.11")
|
||||
.arg("--python-preference")
|
||||
.arg("only-managed")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Ignoring existing environment for `black`: the requested Python interpreter does not match the environment interpreter
|
||||
Resolved [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ black==24.3.0
|
||||
+ click==8.1.7
|
||||
+ mypy-extensions==1.0.0
|
||||
+ packaging==24.0
|
||||
+ pathspec==0.12.1
|
||||
+ platformdirs==4.2.0
|
||||
Installed 2 executables: black, blackd
|
||||
"###);
|
||||
|
||||
// Install with managed Python 3.11 (compatible).
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("-p")
|
||||
.arg("3.11")
|
||||
.arg("--python-preference")
|
||||
.arg("only-managed")
|
||||
.arg("black")
|
||||
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
`black` is already installed
|
||||
"###);
|
||||
}
|
||||
|
||||
/// Test preserving a tool environment when new but incompatible requirements are requested.
|
||||
#[test]
|
||||
fn tool_install_preserve_environment() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue