Allow pinning managed Python versions to specific build versions (#15314)
Allows pinning the Python build version via environment variables, e.g., `UV_PYTHON_CPYTHON_BUILD=...`. Each variable is implementation specific, because they use different versioning schemes. Updates the Python download metadata to include a `build` string, so we can filter downloads by the pin. Writes the build version to a file in the managed install, e.g., `cpython-3.10.18-macos-aarch64-none/BUILD`, so we can filter installed versions by the pin. Some important follow-up here: - Include the build version in not found errors (when pinned) - Automatically use a remote list of Python downloads to satisfy build versions not present in the latest embedded download metadata Some less important follow-ups to consider: - Allow using ranges for build version pins
This commit is contained in:
@@ -1,11 +1,24 @@
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use thiserror::Error;
|
||||
use uv_pep440::Version;
|
||||
use uv_pep508::{MarkerEnvironment, StringVersion};
|
||||
use uv_static::EnvVars;
|
||||
|
||||
use crate::implementation::ImplementationName;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BuildVersionError {
|
||||
#[error("`{0}` is not valid unicode: {1:?}")]
|
||||
NotUnicode(&'static str, OsString),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PythonVersion(StringVersion);
|
||||
@@ -206,6 +219,51 @@ impl PythonVersion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the environment variable name for the build constraint for a given implementation.
|
||||
pub(crate) fn python_build_version_variable(implementation: ImplementationName) -> &'static str {
|
||||
match implementation {
|
||||
ImplementationName::CPython => EnvVars::UV_PYTHON_CPYTHON_BUILD,
|
||||
ImplementationName::PyPy => EnvVars::UV_PYTHON_PYPY_BUILD,
|
||||
ImplementationName::GraalPy => EnvVars::UV_PYTHON_GRAALPY_BUILD,
|
||||
ImplementationName::Pyodide => EnvVars::UV_PYTHON_PYODIDE_BUILD,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the build version number from the environment variable for a given implementation.
|
||||
pub(crate) fn python_build_version_from_env(
|
||||
implementation: ImplementationName,
|
||||
) -> Result<Option<String>, BuildVersionError> {
|
||||
let variable = python_build_version_variable(implementation);
|
||||
|
||||
let Some(build_os) = env::var_os(variable) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let build = build_os
|
||||
.into_string()
|
||||
.map_err(|raw| BuildVersionError::NotUnicode(variable, raw))?;
|
||||
|
||||
let trimmed = build.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(trimmed.to_string()))
|
||||
}
|
||||
|
||||
/// Get the build version numbers for all Python implementations.
|
||||
pub(crate) fn python_build_versions_from_env()
|
||||
-> Result<BTreeMap<ImplementationName, String>, BuildVersionError> {
|
||||
let mut versions = BTreeMap::new();
|
||||
for implementation in ImplementationName::iter_all() {
|
||||
let Some(build) = python_build_version_from_env(implementation)? else {
|
||||
continue;
|
||||
};
|
||||
versions.insert(implementation, build);
|
||||
}
|
||||
Ok(versions)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
Reference in New Issue
Block a user