Add support for managed Python 3.13 and update CPython versions (#7263)

Adds support for CPython 3.13.0rc2

Also bumps to the latest patch version of all the other CPython minor
versions we support.
This commit is contained in:
Zanie Blue 2024-09-10 14:36:16 -05:00 committed by GitHub
parent 0dc1f5db21
commit 0e9870078e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2989 additions and 70 deletions

View file

@ -1409,6 +1409,12 @@ impl std::fmt::Display for PrereleaseKind {
} }
} }
impl std::fmt::Display for Prerelease {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.kind, self.number)
}
}
/// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>) /// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>)
/// ///
/// Local versions are a mess: /// Local versions are a mess:

File diff suppressed because it is too large Load diff

View file

@ -82,14 +82,23 @@ class Version(NamedTuple):
major: int major: int
minor: int minor: int
patch: int patch: int
prerelease: str = ""
@classmethod @classmethod
def from_str(cls, version: str) -> Self: def from_str(cls, version: str) -> Self:
major, minor, patch = version.split(".", 3) major, minor, patch = version.split(".", 3)
return cls(int(major), int(minor), int(patch)) prerelease = ""
for prerelease_kind in ("a", "b", "rc"):
parts = patch.split(prerelease_kind, 1)
if len(parts) == 2:
patch = parts[0]
prerelease = prerelease_kind + parts[1]
break
return cls(int(major), int(minor), int(patch), prerelease)
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.major}.{self.minor}.{self.patch}" return f"{self.major}.{self.minor}.{self.patch}{self.prerelease}"
class ImplementationName(StrEnum): class ImplementationName(StrEnum):
@ -158,7 +167,7 @@ class CPythonFinder(Finder):
_filename_re = re.compile( _filename_re = re.compile(
r"""(?x) r"""(?x)
^ ^
cpython-(?P<ver>\d+\.\d+\.\d+?) cpython-(?P<ver>\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?)
(?:\+\d+)? (?:\+\d+)?
-(?P<triple>.*?) -(?P<triple>.*?)
(?:-[\dT]+)?\.tar\.(?:gz|zst) (?:-[\dT]+)?\.tar\.(?:gz|zst)
@ -455,7 +464,9 @@ def render(downloads: list[PythonDownload]) -> None:
results = {} results = {}
for download in downloads: for download in downloads:
key = download.key() key = download.key()
logging.info("Found %s (%s)", key, download.flavor) logging.info(
"Found %s%s", key, (" (%s)" % download.flavor) if download.flavor else ""
)
results[key] = { results[key] = {
"name": download.implementation, "name": download.implementation,
"arch": download.triple.arch, "arch": download.triple.arch,
@ -464,6 +475,7 @@ def render(downloads: list[PythonDownload]) -> None:
"major": download.version.major, "major": download.version.major,
"minor": download.version.minor, "minor": download.version.minor,
"patch": download.version.patch, "patch": download.version.patch,
"prerelease": download.version.prerelease,
"url": download.url, "url": download.url,
"sha256": download.sha256, "sha256": download.sha256,
} }

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,8 @@
// Generated with `{{generated_with}}` // Generated with `{{generated_with}}`
// From template at `{{generated_from}}` // From template at `{{generated_from}}`
use std::borrow::Cow;
pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
{{#versions}} {{#versions}}
ManagedPythonDownload { ManagedPythonDownload {
@ -10,6 +12,7 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
major: {{value.major}}, major: {{value.major}},
minor: {{value.minor}}, minor: {{value.minor}},
patch: {{value.patch}}, patch: {{value.patch}},
prerelease: Cow::Borrowed("{{value.prerelease}}"),
implementation: LenientImplementationName::Known(ImplementationName::{{value.name}}), implementation: LenientImplementationName::Known(ImplementationName::{{value.name}}),
arch: Arch(target_lexicon::Architecture::{{value.arch}}), arch: Arch(target_lexicon::Architecture::{{value.arch}}),
os: Os(target_lexicon::OperatingSystem::{{value.os}}), os: Os(target_lexicon::OperatingSystem::{{value.os}}),

View file

@ -529,9 +529,7 @@ impl ManagedPythonDownload {
} }
pub fn python_version(&self) -> PythonVersion { pub fn python_version(&self) -> PythonVersion {
self.key self.key.version()
.version()
.expect("Managed Python downloads should always have valid versions")
} }
/// Return the [`Url`] to use when downloading the distribution. If a mirror is set via the /// Return the [`Url`] to use when downloading the distribution. If a mirror is set via the

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
@ -212,6 +213,7 @@ pub struct PythonInstallationKey {
pub(crate) major: u8, pub(crate) major: u8,
pub(crate) minor: u8, pub(crate) minor: u8,
pub(crate) patch: u8, pub(crate) patch: u8,
pub(crate) prerelease: Cow<'static, str>,
pub(crate) os: Os, pub(crate) os: Os,
pub(crate) arch: Arch, pub(crate) arch: Arch,
pub(crate) libc: Libc, pub(crate) libc: Libc,
@ -223,6 +225,7 @@ impl PythonInstallationKey {
major: u8, major: u8,
minor: u8, minor: u8,
patch: u8, patch: u8,
prerelease: String,
os: Os, os: Os,
arch: Arch, arch: Arch,
libc: Libc, libc: Libc,
@ -232,6 +235,26 @@ impl PythonInstallationKey {
major, major,
minor, minor,
patch, patch,
prerelease: Cow::Owned(prerelease),
os,
arch,
libc,
}
}
pub fn new_from_version(
implementation: LenientImplementationName,
version: &PythonVersion,
os: Os,
arch: Arch,
libc: Libc,
) -> Self {
Self {
implementation,
major: version.major(),
minor: version.minor(),
patch: version.patch().unwrap_or_default(),
prerelease: Cow::Owned(version.pre().map(|pre| pre.to_string()).unwrap_or_default()),
os, os,
arch, arch,
libc, libc,
@ -242,8 +265,12 @@ impl PythonInstallationKey {
&self.implementation &self.implementation
} }
pub fn version(&self) -> Result<PythonVersion, String> { pub fn version(&self) -> PythonVersion {
PythonVersion::from_str(&format!("{}.{}.{}", self.major, self.minor, self.patch)) PythonVersion::from_str(&format!(
"{}.{}.{}{}",
self.major, self.minor, self.patch, self.prerelease
))
.expect("Python installation keys must have valid Python versions")
} }
pub fn arch(&self) -> &Arch { pub fn arch(&self) -> &Arch {
@ -263,8 +290,15 @@ impl fmt::Display for PythonInstallationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"{}-{}.{}.{}-{}-{}-{}", "{}-{}.{}.{}{}-{}-{}-{}",
self.implementation, self.major, self.minor, self.patch, self.os, self.arch, self.libc self.implementation,
self.major,
self.minor,
self.patch,
self.prerelease,
self.os,
self.arch,
self.libc
) )
} }
} }
@ -298,28 +332,16 @@ impl FromStr for PythonInstallationKey {
PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid libc: {err}")) PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid libc: {err}"))
})?; })?;
let [major, minor, patch] = version let version = PythonVersion::from_str(version).map_err(|err| {
.splitn(3, '.') PythonInstallationKeyError::ParseError(
.map(str::parse::<u8>)
.collect::<Result<Vec<_>, _>>()
.map_err(|err| {
PythonInstallationKeyError::ParseError(
key.to_string(),
format!("invalid Python version: {err}"),
)
})?[..]
else {
return Err(PythonInstallationKeyError::ParseError(
key.to_string(), key.to_string(),
"invalid Python version: expected `<major>.<minor>.<patch>`".to_string(), format!("invalid Python version: {err}"),
)); )
}; })?;
Ok(Self::new( Ok(Self::new_from_version(
implementation, implementation,
major, &version,
minor,
patch,
os, os,
arch, arch,
libc, libc,
@ -337,12 +359,7 @@ impl Ord for PythonInstallationKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.implementation self.implementation
.cmp(&other.implementation) .cmp(&other.implementation)
.then_with(|| { .then_with(|| self.version().cmp(&other.version()))
self.major
.cmp(&other.major)
.then_with(|| self.minor.cmp(&other.minor))
.then_with(|| self.patch.cmp(&other.patch))
})
.then_with(|| self.os.to_string().cmp(&other.os.to_string())) .then_with(|| self.os.to_string().cmp(&other.os.to_string()))
.then_with(|| self.arch.to_string().cmp(&other.arch.to_string())) .then_with(|| self.arch.to_string().cmp(&other.arch.to_string()))
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string())) .then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))

View file

@ -155,6 +155,10 @@ impl Interpreter {
self.python_major(), self.python_major(),
self.python_minor(), self.python_minor(),
self.python_patch(), self.python_patch(),
self.python_version()
.pre()
.map(|pre| pre.to_string())
.unwrap_or_default(),
self.os(), self.os(),
self.arch(), self.arch(),
self.libc(), self.libc(),

View file

@ -282,9 +282,7 @@ impl ManagedPythonInstallation {
/// The [`PythonVersion`] of the toolchain. /// The [`PythonVersion`] of the toolchain.
pub fn version(&self) -> PythonVersion { pub fn version(&self) -> PythonVersion {
self.key self.key.version()
.version()
.expect("Managed Python installations should always have valid versions")
} }
pub fn implementation(&self) -> &ImplementationName { pub fn implementation(&self) -> &ImplementationName {
@ -331,17 +329,13 @@ impl ManagedPythonInstallation {
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) { let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
self.python_dir().join("Lib") self.python_dir().join("Lib")
} else { } else {
let version = self
.key
.version()
.expect("Managed Python installations should always have valid versions");
let python = if matches!( let python = if matches!(
self.key.implementation, self.key.implementation,
LenientImplementationName::Known(ImplementationName::PyPy) LenientImplementationName::Known(ImplementationName::PyPy)
) { ) {
format!("pypy{}", version.python_version()) format!("pypy{}", self.key.version().python_version())
} else { } else {
format!("python{}", version.python_version()) format!("python{}", self.key.version().python_version())
}; };
self.python_dir().join("lib").join(python) self.python_dir().join("lib").join(python)
}; };

View file

@ -178,13 +178,7 @@ pub(crate) async fn install(
"{}", "{}",
format!( format!(
"Installed {} {}", "Installed {} {}",
format!( format!("Python {}", installed.version()).bold(),
"Python {}",
installed.version().expect(
"Managed Python installations should always have valid versions"
)
)
.bold(),
format!("in {}", elapsed(start.elapsed())).dimmed() format!("in {}", elapsed(start.elapsed())).dimmed()
) )
.dimmed() .dimmed()

View file

@ -4,7 +4,6 @@ use std::fmt::Write;
use anyhow::Result; use anyhow::Result;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use tracing::warn;
use uv_cache::Cache; use uv_cache::Cache;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_python::downloads::PythonDownloadRequest; use uv_python::downloads::PythonDownloadRequest;
@ -107,17 +106,9 @@ pub(crate) async fn list(
} }
} }
let version = match key.version() {
Err(err) => {
warn!("Excluding {key} due to invalid Python version: {err}");
continue;
}
Ok(version) => version,
};
// Only show the latest patch version for each download unless all were requested // Only show the latest patch version for each download unless all were requested
if !matches!(kind, Kind::System) { if !matches!(kind, Kind::System) {
if let [major, minor, ..] = version.release() { if let [major, minor, ..] = key.version().release() {
if !seen_minor.insert(( if !seen_minor.insert((
*key.os(), *key.os(),
*major, *major,
@ -131,7 +122,7 @@ pub(crate) async fn list(
} }
} }
} }
if let [major, minor, patch] = version.release() { if let [major, minor, patch] = key.version().release() {
if !seen_patch.insert(( if !seen_patch.insert((
*key.os(), *key.os(),
*major, *major,

View file

@ -148,13 +148,7 @@ async fn do_uninstall(
"{}", "{}",
format!( format!(
"Uninstalled {} {}", "Uninstalled {} {}",
format!( format!("Python {}", uninstalled.version()).bold(),
"Python {}",
uninstalled.version().expect(
"Managed Python installations should always have valid versions"
)
)
.bold(),
format!("in {}", elapsed(start.elapsed())).dimmed() format!("in {}", elapsed(start.elapsed())).dimmed()
) )
.dimmed() .dimmed()