Ensure virtual environment is compatible with interpreter on sync (#12884)
It was possible that a virtual environment became out of sync with the interpreter it pointed to (for example, if a symlink was changed to an updated Python version). In such a case, `pyvenv.cfg` and `activate_this.py` would no longer be correct. This PR detects when the `version` (`venv` module) or `version_info` (uv and `virtualenv`) field in `pyvenv.cfg` is out of sync with the interpreter. In such a case, uv recreates the virtual environment. Closes #12461
This commit is contained in:
@@ -355,4 +355,13 @@ impl PythonEnvironment {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
env, io,
|
||||
path::{Path, PathBuf},
|
||||
@@ -10,6 +11,8 @@ use thiserror::Error;
|
||||
use uv_pypi_types::Scheme;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
use crate::{Interpreter, PythonVersion};
|
||||
|
||||
/// The layout of a virtual environment.
|
||||
#[derive(Debug)]
|
||||
pub struct VirtualEnvironment {
|
||||
@@ -41,6 +44,8 @@ pub struct PyVenvConfiguration {
|
||||
pub(crate) seed: bool,
|
||||
/// Should the virtual environment include system site packages?
|
||||
pub(crate) include_system_site_packages: bool,
|
||||
/// The Python version the virtual environment was created with
|
||||
pub(crate) version: Option<PythonVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -193,6 +198,7 @@ impl PyVenvConfiguration {
|
||||
let mut relocatable = false;
|
||||
let mut seed = false;
|
||||
let mut include_system_site_packages = true;
|
||||
let mut version = None;
|
||||
|
||||
// Per https://snarky.ca/how-virtual-environments-work/, the `pyvenv.cfg` file is not a
|
||||
// valid INI file, and is instead expected to be parsed by partitioning each line on the
|
||||
@@ -219,6 +225,12 @@ impl PyVenvConfiguration {
|
||||
"include-system-site-packages" => {
|
||||
include_system_site_packages = value.trim().to_lowercase() == "true";
|
||||
}
|
||||
"version" | "version_info" => {
|
||||
version = Some(
|
||||
PythonVersion::from_str(value.trim())
|
||||
.map_err(|e| io::Error::new(std::io::ErrorKind::InvalidData, e))?,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +241,7 @@ impl PyVenvConfiguration {
|
||||
relocatable,
|
||||
seed,
|
||||
include_system_site_packages,
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,6 +270,18 @@ 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<_>>();
|
||||
|
||||
Reference in New Issue
Block a user