Fix handling of python-preference = system when managed interpreters are on the PATH (#15059)
Some checks are pending
CI / cargo shear (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions

This is the first part of fixing a 0.8.0 regression from
https://github.com/astral-sh/uv/pull/7934

There, we added handling for skipping managed interpreters on the PATH
when `only-system` is used, but did not update the logic to prefer
system interpreters over managed ones when `system` is used. Here, we
fix that by skipping managed interpreters when `system` is used unless
_only_ managed interpreters are available. While this logic is applied
during in a general discovery method, it's only relevant for the PATH
(and the Windows registry) because we already change the _order_ that we
inspect installations in when `system` is used, so the managed
installation directory is inspected last.

This behavior did not regress in 0.8, it's always been this way,
however, I need this change in order to fix a different bug.
This commit is contained in:
Zanie Blue 2025-08-04 11:02:42 -05:00 committed by GitHub
parent 8186aa963f
commit 64e91a7e87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 20 deletions

View file

@ -918,24 +918,7 @@ pub fn satisfies_python_preference(
// If not "only" a kind, any interpreter is okay
PythonPreference::Managed | PythonPreference::System => true,
PythonPreference::OnlySystem => {
let is_system = match source {
// A managed interpreter is never a system interpreter
PythonSource::Managed => false,
// We can't be sure if this is a system interpreter without checking
PythonSource::ProvidedPath
| PythonSource::ParentInterpreter
| PythonSource::ActiveEnvironment
| PythonSource::CondaPrefix
| PythonSource::DiscoveredEnvironment
| PythonSource::SearchPath
| PythonSource::SearchPathFirst
| PythonSource::Registry
| PythonSource::BaseCondaPrefix => !interpreter.is_managed(),
// Managed interpreters should never be found in the store
PythonSource::MicrosoftStore => true,
};
if is_system {
if is_system_interpreter(source, interpreter) {
true
} else {
if is_explicit {
@ -956,6 +939,25 @@ pub fn satisfies_python_preference(
}
}
pub(crate) fn is_system_interpreter(source: PythonSource, interpreter: &Interpreter) -> bool {
match source {
// A managed interpreter is never a system interpreter
PythonSource::Managed => false,
// We can't be sure if this is a system interpreter without checking
PythonSource::ProvidedPath
| PythonSource::ParentInterpreter
| PythonSource::ActiveEnvironment
| PythonSource::CondaPrefix
| PythonSource::DiscoveredEnvironment
| PythonSource::SearchPath
| PythonSource::SearchPathFirst
| PythonSource::Registry
| PythonSource::BaseCondaPrefix => !interpreter.is_managed(),
// Managed interpreters should never be found in the store
PythonSource::MicrosoftStore => true,
}
}
/// Check if an encountered error is critical and should stop discovery.
///
/// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one.
@ -1259,6 +1261,7 @@ pub(crate) fn find_python_installation(
let installations =
find_python_installations(request, environments, preference, cache, preview);
let mut first_prerelease = None;
let mut first_managed = None;
let mut first_error = None;
for result in installations {
// Iterate until the first critical error or happy result
@ -1295,7 +1298,7 @@ pub(crate) fn find_python_installation(
&& !installation.source.allows_prereleases()
&& !has_default_executable_name
{
debug!("Skipping pre-release {}", installation.key());
debug!("Skipping pre-release installation {}", installation.key());
if first_prerelease.is_none() {
first_prerelease = Some(installation.clone());
}
@ -1315,12 +1318,41 @@ pub(crate) fn find_python_installation(
continue;
}
// If it's a managed Python installation, and system interpreters are preferred, skip it
// for now.
if matches!(preference, PythonPreference::System)
&& !is_system_interpreter(installation.source, installation.interpreter())
{
debug!(
"Skipping managed installation {}: system installation preferred",
installation.key()
);
if first_managed.is_none() {
first_managed = Some(installation.clone());
}
continue;
}
// If we didn't skip it, this is the installation to use
return result;
}
// If we only found managed installations, and the preference allows them, we should return
// the first one.
if let Some(installation) = first_managed {
debug!(
"Allowing managed installation {}: no system installations",
installation.key()
);
return Ok(Ok(installation));
}
// If we only found pre-releases, they're implicitly allowed and we should return the first one.
if let Some(installation) = first_prerelease {
debug!(
"Allowing pre-release installation {}: no stable installations",
installation.key()
);
return Ok(Ok(installation));
}