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:
trag1c 2024-10-08 23:20:58 +02:00 committed by GitHub
parent cfaa834dee
commit 37273cb4bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 794 additions and 768 deletions

View file

@ -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,
@ -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)
}
}
}