Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prerelease compatibility check #8020

Merged
merged 4 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Self::Any | Self::Default => true,
Self::Major(self_major, _) => *self_major == major,
Expand All @@ -1879,14 +1885,14 @@ impl VersionRequest {
Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => {
(*self_major, *self_minor, *self_patch) == (major, minor, patch)
}
Self::Range(specifiers, _) => specifiers.contains(&Version::new([
u64::from(major),
u64::from(minor),
u64::from(patch),
])),
Self::MajorMinorPrerelease(self_major, self_minor, _, _) => {
Self::Range(specifiers, _) => specifiers.contains(
&Version::new([u64::from(major), u64::from(minor), u64::from(patch)])
.with_pre(prerelease),
),
Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
// Pre-releases of Python versions are always for the zero patch version
(*self_major, *self_minor, 0) == (major, minor, patch)
&& prerelease.map_or(true, |pre| *self_prerelease == pre)
}
}
}
Expand Down
1,484 changes: 742 additions & 742 deletions crates/uv-python/src/downloads.inc

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/uv-python/src/downloads.inc.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Generated with `{{generated_with}}`
// From template at `{{generated_from}}`

use std::borrow::Cow;
use uv_pep440::{Prerelease, PrereleaseKind};

pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
{{#versions}}
Expand All @@ -12,7 +12,7 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
major: {{value.major}},
minor: {{value.minor}},
patch: {{value.patch}},
prerelease: Cow::Borrowed("{{value.prerelease}}"),
prerelease: {{value.prerelease}},
implementation: LenientImplementationName::Known(ImplementationName::{{value.name}}),
arch: Arch(target_lexicon::Architecture::{{value.arch}}),
os: Os(target_lexicon::OperatingSystem::{{value.os}}),
Expand Down
15 changes: 10 additions & 5 deletions crates/uv-python/src/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,24 @@ impl PythonDownloadRequest {
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 !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;
}
if version.is_freethreaded() {
debug!("Installing managed free-threaded Python is not yet supported");
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
}

Expand Down
22 changes: 14 additions & 8 deletions crates/uv-python/src/installation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;

use tracing::{debug, info};

use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_pep440::Version;
use uv_pep440::{Prerelease, Version};

use crate::discovery::{
find_best_python_installation, find_python_installation, EnvironmentPreference, PythonRequest,
Expand Down Expand Up @@ -224,7 +223,7 @@ pub struct PythonInstallationKey {
pub(crate) major: u8,
pub(crate) minor: u8,
pub(crate) patch: u8,
pub(crate) prerelease: Cow<'static, str>,
pub(crate) prerelease: Option<Prerelease>,
pub(crate) os: Os,
pub(crate) arch: Arch,
pub(crate) libc: Libc,
Expand All @@ -236,7 +235,7 @@ impl PythonInstallationKey {
major: u8,
minor: u8,
patch: u8,
prerelease: String,
prerelease: Option<Prerelease>,
os: Os,
arch: Arch,
libc: Libc,
Expand All @@ -246,7 +245,7 @@ impl PythonInstallationKey {
major,
minor,
patch,
prerelease: Cow::Owned(prerelease),
prerelease,
os,
arch,
libc,
Expand All @@ -265,7 +264,7 @@ impl PythonInstallationKey {
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()),
prerelease: version.pre(),
os,
arch,
libc,
Expand All @@ -279,7 +278,12 @@ impl PythonInstallationKey {
pub fn version(&self) -> PythonVersion {
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")
}
Expand All @@ -306,7 +310,9 @@ impl fmt::Display for PythonInstallationKey {
self.major,
self.minor,
self.patch,
self.prerelease,
self.prerelease
.map(|pre| pre.to_string())
.unwrap_or_default(),
self.os,
self.arch,
self.libc
Expand Down
5 changes: 1 addition & 4 deletions crates/uv-python/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,7 @@ impl Interpreter {
self.python_major(),
self.python_minor(),
self.python_patch(),
self.python_version()
.pre()
.map(|pre| pre.to_string())
.unwrap_or_default(),
self.python_version().pre(),
self.os(),
self.arch(),
self.libc(),
Expand Down
12 changes: 12 additions & 0 deletions crates/uv-python/template-download-metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import argparse
import json
import logging
import re
import subprocess
import sys
from pathlib import Path
Expand All @@ -29,6 +30,7 @@
VERSION_METADATA = CRATE_ROOT / "download-metadata.json"
TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache"
TARGET = TEMPLATE.with_suffix("")
PRERELEASE_PATTERN = re.compile(r"(a|b|rc)(\d+)")


def prepare_name(name: str) -> str:
Expand Down Expand Up @@ -61,11 +63,21 @@ def prepare_arch(arch: str) -> str:
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:
value["os"] = value["os"].title()
value["arch"] = prepare_arch(value["arch"])
value["name"] = prepare_name(value["name"])
value["libc"] = prepare_libc(value["libc"])
value["prerelease"] = prepare_prerelease(value["prerelease"])
return value


Expand Down
Loading