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:
Zanie Blue
2025-08-25 16:25:05 -05:00
committed by GitHub
parent b6f1fb7d3f
commit 9b8d6989d4
17 changed files with 5518 additions and 2537 deletions
+27
View File
@@ -320,6 +320,10 @@ pub struct ManagedPythonInstallation {
///
/// Empty when self was constructed from a path.
sha256: Option<Cow<'static, str>>,
/// The build version of the Python installation.
///
/// Empty when self was constructed from a path without a BUILD file.
build: Option<Cow<'static, str>>,
}
impl ManagedPythonInstallation {
@@ -329,6 +333,7 @@ impl ManagedPythonInstallation {
key: download.key().clone(),
url: Some(download.url().clone()),
sha256: download.sha256().cloned(),
build: download.build().map(Cow::Borrowed),
}
}
@@ -342,11 +347,19 @@ impl ManagedPythonInstallation {
let path = std::path::absolute(&path).map_err(|err| Error::AbsolutePath(path, err))?;
// Try to read the BUILD file if it exists
let build = match fs::read_to_string(path.join("BUILD")) {
Ok(content) => Some(Cow::Owned(content.trim().to_string())),
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
Err(err) => return Err(err.into()),
};
Ok(Self {
path,
key,
url: None,
sha256: None,
build,
})
}
@@ -455,6 +468,11 @@ impl ManagedPythonInstallation {
self.key.platform()
}
/// The build version of this installation, if available.
pub fn build(&self) -> Option<&str> {
self.build.as_deref()
}
pub fn minor_version_key(&self) -> &PythonInstallationMinorVersionKey {
PythonInstallationMinorVersionKey::ref_cast(&self.key)
}
@@ -618,6 +636,15 @@ impl ManagedPythonInstallation {
Ok(())
}
/// Ensure the build version is written to a BUILD file in the installation directory.
pub fn ensure_build_file(&self) -> Result<(), Error> {
if let Some(ref build) = self.build {
let build_file = self.path.join("BUILD");
fs::write(&build_file, build.as_ref())?;
}
Ok(())
}
/// Returns `true` if the path is a link to this installation's binary, e.g., as created by
/// [`create_bin_link`].
pub fn is_bin_link(&self, path: &Path) -> bool {