Fix handling of python-preference = system when managed interpreters are on the PATH (#15059)

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
2 changed files with 72 additions and 20 deletions
+51 -19
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));
}