Report Python versions in pyvenv.cfg version mismatch (#13027)
Some checks are pending
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 shear (push) Waiting to run
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 system | alpine (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 / check system | python on macos aarch64 (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | linux libc (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 linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (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 | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy 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 / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (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 | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python on opensuse (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 | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (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 / cargo build (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 / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (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 / 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 | x86-64 python3.13 on windows aarch64 (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 (push) Blocked by required conditions

When working on #13025 I noticed this message was lacking versions,
which seems frustrating if you're debugging things.

I refactored the general `matches_interpreter` utilities that were added
in https://github.com/astral-sh/uv/pull/12884 into a more purpose-fit
function that returns an `Option` with the versions if there's a
mismatch.
This commit is contained in:
Zanie Blue 2025-04-25 13:06:46 -05:00 committed by GitHub
parent 8414e9f3dd
commit fb08116800
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 43 additions and 27 deletions

View file

@ -10,6 +10,7 @@ use tracing::debug;
use uv_cache::Cache;
use uv_cache_key::cache_digest;
use uv_fs::{LockedFile, Simplified};
use uv_pep440::Version;
use crate::discovery::find_python_installation;
use crate::installation::PythonInstallation;
@ -356,12 +357,22 @@ impl PythonEnvironment {
}
}
/// If this is a virtual environment (indicated by the presence of
/// a `pyvenv.cfg` file), this returns true if the `pyvenv.cfg` version
/// is the same as the interpreter Python version. Also returns true
/// if this is not a virtual environment.
pub fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
let Ok(cfg) = self.cfg() else { return true };
cfg.matches_interpreter(interpreter)
/// Check if the `pyvenv.cfg` version is the same as the interpreter's Python version.
///
/// Returns [`None`] if the versions are the consistent or there is no `pyvenv.cfg`. If the
/// versions do not match, returns a tuple of the `pyvenv.cfg` and interpreter's Python versions
/// for display.
pub fn get_pyvenv_version_conflict(&self) -> Option<(Version, Version)> {
let cfg = self.cfg().ok()?;
let cfg_version = cfg.version?.into_version();
// Determine if we should be checking for patch-level equality
let exe_version = if cfg_version.release().get(2).is_none() {
self.interpreter().python_minor_version()
} else {
self.interpreter().python_patch_version()
};
(cfg_version != exe_version).then_some((cfg_version, exe_version))
}
}

View file

@ -318,31 +318,37 @@ impl Interpreter {
&self.markers.python_full_version().version
}
/// Returns the full minor Python version.
/// Returns the Python version up to the minor component.
#[inline]
pub fn python_minor_version(&self) -> Version {
Version::new(self.python_version().release().iter().take(2).copied())
}
/// Return the major version of this Python version.
/// Returns the Python version up to the patch component.
#[inline]
pub fn python_patch_version(&self) -> Version {
Version::new(self.python_version().release().iter().take(3).copied())
}
/// Return the major version component of this Python version.
pub fn python_major(&self) -> u8 {
let major = self.markers.python_full_version().version.release()[0];
u8::try_from(major).expect("invalid major version")
}
/// Return the minor version of this Python version.
/// Return the minor version component of this Python version.
pub fn python_minor(&self) -> u8 {
let minor = self.markers.python_full_version().version.release()[1];
u8::try_from(minor).expect("invalid minor version")
}
/// Return the patch version of this Python version.
/// Return the patch version component of this Python version.
pub fn python_patch(&self) -> u8 {
let minor = self.markers.python_full_version().version.release()[2];
u8::try_from(minor).expect("invalid patch version")
}
/// Returns the Python version as a simple tuple.
/// Returns the Python version as a simple tuple, e.g., `(3, 12)`.
pub fn python_tuple(&self) -> (u8, u8) {
(self.python_major(), self.python_minor())
}

View file

@ -8,6 +8,12 @@ use uv_pep508::{MarkerEnvironment, StringVersion};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PythonVersion(StringVersion);
impl From<StringVersion> for PythonVersion {
fn from(version: StringVersion) -> Self {
Self(version)
}
}
impl Deref for PythonVersion {
type Target = StringVersion;
@ -176,6 +182,11 @@ impl PythonVersion {
&self.0.version
}
/// Return the full parsed Python version.
pub fn into_version(self) -> Version {
self.0.version
}
/// Return the major version of this Python version.
pub fn major(&self) -> u8 {
u8::try_from(self.0.release().first().copied().unwrap_or(0)).expect("invalid major version")

View file

@ -11,7 +11,7 @@ use thiserror::Error;
use uv_pypi_types::Scheme;
use uv_static::EnvVars;
use crate::{Interpreter, PythonVersion};
use crate::PythonVersion;
/// The layout of a virtual environment.
#[derive(Debug)]
@ -270,18 +270,6 @@ impl PyVenvConfiguration {
self.include_system_site_packages
}
/// Returns true if the virtual environment has the same `pyvenv.cfg` version
/// as the interpreter Python version. Also returns true if there is no version.
pub fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
self.version.as_ref().is_none_or(|version| {
interpreter.python_major() == version.major()
&& interpreter.python_minor() == version.minor()
&& version
.patch()
.is_none_or(|patch| patch == interpreter.python_patch())
})
}
/// Set the key-value pair in the `pyvenv.cfg` file.
pub fn set(content: &str, key: &str, value: &str) -> String {
let mut lines = content.lines().map(Cow::Borrowed).collect::<Vec<_>>();