mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 04:17:37 +00:00
Validate that discovered interpreters meet the Python preference (#7934)
Closes https://github.com/astral-sh/uv/issues/5144 e.g. ``` ❯ cargo run -q -- sync --python-preference only-system Using CPython 3.12.6 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 9ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ❯ cargo run -q -- sync --python-preference only-managed Using CPython 3.12.1 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 11ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ```
This commit is contained in:
parent
2df06ebfbc
commit
b98ac8c224
12 changed files with 544 additions and 11 deletions
|
|
@ -446,7 +446,16 @@ fn python_executables_from_installed<'a>(
|
|||
.flatten();
|
||||
|
||||
match preference {
|
||||
PythonPreference::OnlyManaged => Box::new(from_managed_installations),
|
||||
PythonPreference::OnlyManaged => {
|
||||
// TODO(zanieb): Ideally, we'd create "fake" managed installation directories for tests,
|
||||
// but for now... we'll just include the test interpreters which are always on the
|
||||
// search path.
|
||||
if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
|
||||
Box::new(from_managed_installations.chain(from_search_path))
|
||||
} else {
|
||||
Box::new(from_managed_installations)
|
||||
}
|
||||
}
|
||||
PythonPreference::Managed => Box::new(
|
||||
from_managed_installations
|
||||
.chain(from_search_path)
|
||||
|
|
@ -730,6 +739,9 @@ fn python_interpreters<'a>(
|
|||
false
|
||||
}
|
||||
})
|
||||
.filter_ok(move |(source, interpreter)| {
|
||||
satisfies_python_preference(*source, interpreter, preference)
|
||||
})
|
||||
}
|
||||
|
||||
/// Lazily convert Python executables into interpreters.
|
||||
|
|
@ -857,6 +869,93 @@ fn source_satisfies_environment_preference(
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if a Python interpreter matches the [`PythonPreference`].
|
||||
pub fn satisfies_python_preference(
|
||||
source: PythonSource,
|
||||
interpreter: &Interpreter,
|
||||
preference: PythonPreference,
|
||||
) -> bool {
|
||||
// If the source is "explicit", we will not apply the Python preference, e.g., if the user has
|
||||
// activated a virtual environment, we should always allow it. We may want to invalidate the
|
||||
// environment in some cases, like in projects, but we can't distinguish between explicit
|
||||
// requests for a different Python preference or a persistent preference in a configuration file
|
||||
// which would result in overly aggressive invalidation.
|
||||
let is_explicit = match source {
|
||||
PythonSource::ProvidedPath
|
||||
| PythonSource::ParentInterpreter
|
||||
| PythonSource::ActiveEnvironment
|
||||
| PythonSource::CondaPrefix => true,
|
||||
PythonSource::Managed
|
||||
| PythonSource::DiscoveredEnvironment
|
||||
| PythonSource::SearchPath
|
||||
| PythonSource::SearchPathFirst
|
||||
| PythonSource::Registry
|
||||
| PythonSource::MicrosoftStore
|
||||
| PythonSource::BaseCondaPrefix => false,
|
||||
};
|
||||
|
||||
match preference {
|
||||
PythonPreference::OnlyManaged => {
|
||||
// Perform a fast check using the source before querying the interpreter
|
||||
if matches!(source, PythonSource::Managed) || interpreter.is_managed() {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only managed interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only system interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
@ -2812,6 +2911,18 @@ impl PythonPreference {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the canonical name.
|
||||
// TODO(zanieb): This should be a `Display` impl and we should have a different view for
|
||||
// the sources
|
||||
pub fn canonical_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::OnlyManaged => "only managed",
|
||||
Self::Managed => "prefer managed",
|
||||
Self::System => "prefer system",
|
||||
Self::OnlySystem => "only system",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PythonPreference {
|
||||
|
|
|
|||
|
|
@ -158,8 +158,7 @@ impl PythonEnvironment {
|
|||
let installation = match find_python_installation(
|
||||
request,
|
||||
preference,
|
||||
// Ignore managed installations when looking for environments
|
||||
PythonPreference::OnlySystem,
|
||||
PythonPreference::default(),
|
||||
cache,
|
||||
preview,
|
||||
)? {
|
||||
|
|
|
|||
|
|
@ -271,15 +271,28 @@ impl Interpreter {
|
|||
///
|
||||
/// Returns `false` if we cannot determine the path of the uv managed Python interpreters.
|
||||
pub fn is_managed(&self) -> bool {
|
||||
if let Ok(test_managed) =
|
||||
std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED)
|
||||
{
|
||||
// During testing, we collect interpreters into an artificial search path and need to
|
||||
// be able to mock whether an interpreter is managed or not.
|
||||
return test_managed.split_ascii_whitespace().any(|item| {
|
||||
let version = <PythonVersion as std::str::FromStr>::from_str(item).expect(
|
||||
"`UV_INTERNAL__TEST_PYTHON_MANAGED` items should be valid Python versions",
|
||||
);
|
||||
if version.patch().is_some() {
|
||||
version.version() == self.python_version()
|
||||
} else {
|
||||
(version.major(), version.minor()) == self.python_tuple()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let Ok(installations) = ManagedPythonInstallations::from_settings(None) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
installations
|
||||
.find_all()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|install| install.path() == self.sys_base_prefix)
|
||||
self.sys_base_prefix.starts_with(installations.root())
|
||||
}
|
||||
|
||||
/// Returns `Some` if the environment is externally managed, optionally including an error
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use uv_static::EnvVars;
|
|||
pub use crate::discovery::{
|
||||
EnvironmentPreference, Error as DiscoveryError, PythonDownloads, PythonNotFound,
|
||||
PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest,
|
||||
find_python_installations,
|
||||
find_python_installations, satisfies_python_preference,
|
||||
};
|
||||
pub use crate::downloads::PlatformRequest;
|
||||
pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue