mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 13:58:29 +00:00
Add prerelease compatibility check (#8020)
## Summary Closes #7977. Makes `PythonDownloadRequest` account for the prerelease part if allowed. Also stores the prerelease in `PythonInstallationKey` directly as a `Prerelease` rather than a string. ## Test Plan Correctly picks the relevant prerelease (rather than picking the most recent one): ``` λ cargo run python install 3.13.0rc2 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s Running `target/debug/uv python install 3.13.0rc2` Searching for Python versions matching: Python 3.13rc2 cpython-3.13.0rc2-macos-aarch64-none ------------------------------ 457.81 KiB/14.73 MiB ^C λ cargo run python install 3.13.0rc3 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s Running `target/debug/uv python install 3.13.0rc3` Searching for Python versions matching: Python 3.13rc3 Found existing installation for Python 3.13rc3: cpython-3.13.0rc3-macos-aarch64-none ```
This commit is contained in:
parent
cfaa834dee
commit
37273cb4bc
7 changed files with 794 additions and 768 deletions
|
@ -1869,7 +1869,13 @@ impl VersionRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn matches_major_minor_patch(&self, major: u8, minor: u8, patch: u8) -> bool {
|
pub(crate) fn matches_major_minor_patch_prerelease(
|
||||||
|
&self,
|
||||||
|
major: u8,
|
||||||
|
minor: u8,
|
||||||
|
patch: u8,
|
||||||
|
prerelease: Option<Prerelease>,
|
||||||
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => true,
|
Self::Any | Self::Default => true,
|
||||||
Self::Major(self_major, _) => *self_major == major,
|
Self::Major(self_major, _) => *self_major == major,
|
||||||
|
@ -1879,14 +1885,14 @@ impl VersionRequest {
|
||||||
Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => {
|
Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => {
|
||||||
(*self_major, *self_minor, *self_patch) == (major, minor, patch)
|
(*self_major, *self_minor, *self_patch) == (major, minor, patch)
|
||||||
}
|
}
|
||||||
Self::Range(specifiers, _) => specifiers.contains(&Version::new([
|
Self::Range(specifiers, _) => specifiers.contains(
|
||||||
u64::from(major),
|
&Version::new([u64::from(major), u64::from(minor), u64::from(patch)])
|
||||||
u64::from(minor),
|
.with_pre(prerelease),
|
||||||
u64::from(patch),
|
),
|
||||||
])),
|
Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
|
||||||
Self::MajorMinorPrerelease(self_major, self_minor, _, _) => {
|
|
||||||
// Pre-releases of Python versions are always for the zero patch version
|
// Pre-releases of Python versions are always for the zero patch version
|
||||||
(*self_major, *self_minor, 0) == (major, minor, patch)
|
(*self_major, *self_minor, 0) == (major, minor, patch)
|
||||||
|
&& prerelease.map_or(true, |pre| *self_prerelease == pre)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@
|
||||||
// Generated with `{{generated_with}}`
|
// Generated with `{{generated_with}}`
|
||||||
// From template at `{{generated_from}}`
|
// From template at `{{generated_from}}`
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use uv_pep440::{Prerelease, PrereleaseKind};
|
||||||
|
|
||||||
pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
|
pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
|
||||||
{{#versions}}
|
{{#versions}}
|
||||||
|
@ -12,7 +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}}"),
|
prerelease: {{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}}),
|
||||||
|
|
|
@ -260,8 +260,17 @@ impl PythonDownloadRequest {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If we don't allow pre-releases, don't match a key with a pre-release tag
|
||||||
|
if !self.allows_prereleases() && key.prerelease.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if let Some(version) = &self.version {
|
if let Some(version) = &self.version {
|
||||||
if !version.matches_major_minor_patch(key.major, key.minor, key.patch) {
|
if !version.matches_major_minor_patch_prerelease(
|
||||||
|
key.major,
|
||||||
|
key.minor,
|
||||||
|
key.patch,
|
||||||
|
key.prerelease,
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if version.is_freethreaded() {
|
if version.is_freethreaded() {
|
||||||
|
@ -269,10 +278,6 @@ impl PythonDownloadRequest {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we don't allow pre-releases, don't match a key with a pre-release tag
|
|
||||||
if !self.allows_prereleases() && !key.prerelease.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -6,7 +5,7 @@ use tracing::{debug, info};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::{Prerelease, Version};
|
||||||
|
|
||||||
use crate::discovery::{
|
use crate::discovery::{
|
||||||
find_best_python_installation, find_python_installation, EnvironmentPreference, PythonRequest,
|
find_best_python_installation, find_python_installation, EnvironmentPreference, PythonRequest,
|
||||||
|
@ -224,7 +223,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) prerelease: Option<Prerelease>,
|
||||||
pub(crate) os: Os,
|
pub(crate) os: Os,
|
||||||
pub(crate) arch: Arch,
|
pub(crate) arch: Arch,
|
||||||
pub(crate) libc: Libc,
|
pub(crate) libc: Libc,
|
||||||
|
@ -236,7 +235,7 @@ impl PythonInstallationKey {
|
||||||
major: u8,
|
major: u8,
|
||||||
minor: u8,
|
minor: u8,
|
||||||
patch: u8,
|
patch: u8,
|
||||||
prerelease: String,
|
prerelease: Option<Prerelease>,
|
||||||
os: Os,
|
os: Os,
|
||||||
arch: Arch,
|
arch: Arch,
|
||||||
libc: Libc,
|
libc: Libc,
|
||||||
|
@ -246,7 +245,7 @@ impl PythonInstallationKey {
|
||||||
major,
|
major,
|
||||||
minor,
|
minor,
|
||||||
patch,
|
patch,
|
||||||
prerelease: Cow::Owned(prerelease),
|
prerelease,
|
||||||
os,
|
os,
|
||||||
arch,
|
arch,
|
||||||
libc,
|
libc,
|
||||||
|
@ -265,7 +264,7 @@ impl PythonInstallationKey {
|
||||||
major: version.major(),
|
major: version.major(),
|
||||||
minor: version.minor(),
|
minor: version.minor(),
|
||||||
patch: version.patch().unwrap_or_default(),
|
patch: version.patch().unwrap_or_default(),
|
||||||
prerelease: Cow::Owned(version.pre().map(|pre| pre.to_string()).unwrap_or_default()),
|
prerelease: version.pre(),
|
||||||
os,
|
os,
|
||||||
arch,
|
arch,
|
||||||
libc,
|
libc,
|
||||||
|
@ -279,7 +278,12 @@ impl PythonInstallationKey {
|
||||||
pub fn version(&self) -> PythonVersion {
|
pub fn version(&self) -> PythonVersion {
|
||||||
PythonVersion::from_str(&format!(
|
PythonVersion::from_str(&format!(
|
||||||
"{}.{}.{}{}",
|
"{}.{}.{}{}",
|
||||||
self.major, self.minor, self.patch, self.prerelease
|
self.major,
|
||||||
|
self.minor,
|
||||||
|
self.patch,
|
||||||
|
self.prerelease
|
||||||
|
.map(|pre| pre.to_string())
|
||||||
|
.unwrap_or_default()
|
||||||
))
|
))
|
||||||
.expect("Python installation keys must have valid Python versions")
|
.expect("Python installation keys must have valid Python versions")
|
||||||
}
|
}
|
||||||
|
@ -306,7 +310,9 @@ impl fmt::Display for PythonInstallationKey {
|
||||||
self.major,
|
self.major,
|
||||||
self.minor,
|
self.minor,
|
||||||
self.patch,
|
self.patch,
|
||||||
self.prerelease,
|
self.prerelease
|
||||||
|
.map(|pre| pre.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
self.os,
|
self.os,
|
||||||
self.arch,
|
self.arch,
|
||||||
self.libc
|
self.libc
|
||||||
|
|
|
@ -157,10 +157,7 @@ impl Interpreter {
|
||||||
self.python_major(),
|
self.python_major(),
|
||||||
self.python_minor(),
|
self.python_minor(),
|
||||||
self.python_patch(),
|
self.python_patch(),
|
||||||
self.python_version()
|
self.python_version().pre(),
|
||||||
.pre()
|
|
||||||
.map(|pre| pre.to_string())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
self.os(),
|
self.os(),
|
||||||
self.arch(),
|
self.arch(),
|
||||||
self.libc(),
|
self.libc(),
|
||||||
|
|
|
@ -17,6 +17,7 @@ Usage:
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -29,6 +30,7 @@ WORKSPACE_ROOT = CRATE_ROOT.parent.parent
|
||||||
VERSION_METADATA = CRATE_ROOT / "download-metadata.json"
|
VERSION_METADATA = CRATE_ROOT / "download-metadata.json"
|
||||||
TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache"
|
TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache"
|
||||||
TARGET = TEMPLATE.with_suffix("")
|
TARGET = TEMPLATE.with_suffix("")
|
||||||
|
PRERELEASE_PATTERN = re.compile(r"(a|b|rc)(\d+)")
|
||||||
|
|
||||||
|
|
||||||
def prepare_name(name: str) -> str:
|
def prepare_name(name: str) -> str:
|
||||||
|
@ -61,11 +63,21 @@ def prepare_arch(arch: str) -> str:
|
||||||
return arch.capitalize()
|
return arch.capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_prerelease(prerelease: str) -> str:
|
||||||
|
if not prerelease:
|
||||||
|
return "None"
|
||||||
|
if not (match := PRERELEASE_PATTERN.match(prerelease)):
|
||||||
|
raise ValueError(f"Invalid prerelease: {prerelease!r}")
|
||||||
|
kind, number = match.groups()
|
||||||
|
return f"Some(Prerelease {{ kind: PrereleaseKind::{kind.capitalize()}, number: {number} }})"
|
||||||
|
|
||||||
|
|
||||||
def prepare_value(value: dict) -> dict:
|
def prepare_value(value: dict) -> dict:
|
||||||
value["os"] = value["os"].title()
|
value["os"] = value["os"].title()
|
||||||
value["arch"] = prepare_arch(value["arch"])
|
value["arch"] = prepare_arch(value["arch"])
|
||||||
value["name"] = prepare_name(value["name"])
|
value["name"] = prepare_name(value["name"])
|
||||||
value["libc"] = prepare_libc(value["libc"])
|
value["libc"] = prepare_libc(value["libc"])
|
||||||
|
value["prerelease"] = prepare_prerelease(value["prerelease"])
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue