From 4890f3ef2bbde7a2b36641dc1d8f92e7695f68ec Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 14 Jul 2025 09:07:30 -0500 Subject: [PATCH] Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` (#14606) Closes https://github.com/astral-sh/uv/issues/14604 --- crates/uv/src/commands/tool/common.rs | 22 +++++++++++----------- crates/uv/tests/it/tool_run.rs | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index ffc1b5645..b24a64e25 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -98,14 +98,6 @@ pub(crate) async fn refine_interpreter( return Ok(None); } - // If the user passed a `--python` request, and the refined interpreter is incompatible, we - // can't use it. - if let Some(python_request) = python_request { - if !python_request.satisfied(interpreter, cache) { - return Ok(None); - } - } - // We want an interpreter that's as close to the required version as possible. If we choose the // "latest" Python, we risk choosing a version that lacks wheels for the tool's requirements // (assuming those requirements don't publish source distributions). @@ -135,15 +127,15 @@ pub(crate) async fn refine_interpreter( Bound::Unbounded => unreachable!("`requires-python` should never be unbounded"), }; - let python_request = PythonRequest::Version(VersionRequest::Range( + let requires_python_request = PythonRequest::Version(VersionRequest::Range( VersionSpecifiers::from_iter([lower_bound, upper_bound]), PythonVariant::default(), )); - debug!("Refining interpreter with: {python_request}"); + debug!("Refining interpreter with: {requires_python_request}"); let interpreter = PythonInstallation::find_or_download( - Some(&python_request), + Some(&requires_python_request), EnvironmentPreference::OnlySystem, python_preference, python_downloads, @@ -158,6 +150,14 @@ pub(crate) async fn refine_interpreter( .await? .into_interpreter(); + // If the user passed a `--python` request, and the refined interpreter is incompatible, we + // can't use it. + if let Some(python_request) = python_request { + if !python_request.satisfied(&interpreter, cache) { + return Ok(None); + } + } + Ok(Some(interpreter)) } diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 8bcf5c3d1..90d906fb5 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -3026,6 +3026,7 @@ fn tool_run_reresolve_python() -> anyhow::Result<()> { + foo==1.0.0 (from file://[TEMP_DIR]/foo) "); + // When an incompatible Python version is explicitly requested, we should not re-resolve uv_snapshot!(context.filters(), context.tool_run() .arg("--from") .arg("./foo") @@ -3034,6 +3035,25 @@ fn tool_run_reresolve_python() -> anyhow::Result<()> { .arg("foo") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving tool dependencies: + ╰─▶ Because the current Python version (3.11.[X]) does not satisfy Python>=3.12 and foo==1.0.0 depends on Python>=3.12, we can conclude that foo==1.0.0 cannot be used. + And because only foo==1.0.0 is available and you require foo, we can conclude that your requirements are unsatisfiable. + "); + + // Unless the discovered interpreter is compatible with the request + uv_snapshot!(context.filters(), context.tool_run() + .arg("--from") + .arg("./foo") + .arg("--python") + .arg(">=3.11") + .arg("foo") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" success: true exit_code: 0 ----- stdout -----