Add support for managed installs of free-threaded Python (#8100)

Closes https://github.com/astral-sh/uv/issues/7193

```

❯ cargo run -q -- python uninstall 3.13t
Searching for Python versions matching: Python 3.13t
Uninstalled Python 3.13.0 in 231ms
 - cpython-3.13.0+freethreaded-macos-aarch64-none
❯ cargo run -q -- python install 3.13t
Searching for Python versions matching: Python 3.13t
Installed Python 3.13.0 in 3.54s
 + cpython-3.13.0+freethreaded-macos-aarch64-none
❯ cargo run -q -- python install 3.12t
Searching for Python versions matching: Python 3.12t
error: No download found for request: cpython-3.12t-macos-aarch64-none
❯ cargo run -q -- python install 3.13rc3t
Searching for Python versions matching: Python 3.13rc3t
Found existing installation for Python 3.13rc3t: cpython-3.13.0+freethreaded-macos-aarch64-none
❯ cargo run -q -- run -p 3.13t python -c "import sys; print(sys.base_prefix)"
/Users/zb/.local/share/uv/python/cpython-3.13.0+freethreaded-macos-aarch64-none
```
This commit is contained in:
Zanie Blue 2024-10-14 15:18:52 -05:00 committed by GitHub
parent db0f0aec09
commit 5f33915e03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 6319 additions and 4211 deletions

View file

@ -132,7 +132,7 @@ pub enum EnvironmentPreference {
Any,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum PythonVariant {
#[default]
Default,
@ -1975,6 +1975,19 @@ impl VersionRequest {
Self::Range(specifiers, _) => Self::Range(specifiers, PythonVariant::Default),
}
}
/// Return the required [`PythonVariant`] of the request.
pub(crate) fn variant(&self) -> Option<PythonVariant> {
match self {
Self::Any => None,
Self::Default => Some(PythonVariant::Default),
Self::Major(_, variant)
| Self::MajorMinor(_, _, variant)
| Self::MajorMinorPatch(_, _, _, variant)
| Self::MajorMinorPrerelease(_, _, _, variant)
| Self::Range(_, variant) => Some(*variant),
}
}
}
impl FromStr for VersionRequest {
@ -2049,6 +2062,27 @@ impl FromStr for VersionRequest {
}
}
impl FromStr for PythonVariant {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"t" | "freethreaded" => Ok(Self::Freethreaded),
"" => Ok(Self::Default),
_ => Err(()),
}
}
}
impl std::fmt::Display for PythonVariant {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Default => f.write_str("default"),
Self::Freethreaded => f.write_str("freethreaded"),
}
}
}
fn parse_version_specifiers_request(
s: &str,
variant: PythonVariant,

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,8 @@
// DO NOT EDIT
//
// Generated with `{{generated_with}}`
// From template at `{{generated_from}}`
use uv_pep440::{Prerelease, PrereleaseKind};
use crate::PythonVariant;
pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
{{#versions}}
@ -22,6 +21,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[
{{^value.libc}}
libc: Libc::None,
{{/value.libc}}
variant: {{value.variant}}
},
url: "{{value.url}}",
{{#value.sha256}}

View file

@ -273,9 +273,10 @@ impl PythonDownloadRequest {
) {
return false;
}
if version.is_freethreaded() {
debug!("Installing managed free-threaded Python is not yet supported");
return false;
if let Some(variant) = version.variant() {
if variant != key.variant {
return false;
}
}
}
true

View file

@ -16,7 +16,7 @@ use crate::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
use crate::platform::{Arch, Libc, Os};
use crate::{
downloads, Error, ImplementationName, Interpreter, PythonDownloads, PythonPreference,
PythonSource, PythonVersion,
PythonSource, PythonVariant, PythonVersion,
};
/// A Python interpreter and accompanying tools.
@ -227,6 +227,7 @@ pub struct PythonInstallationKey {
pub(crate) os: Os,
pub(crate) arch: Arch,
pub(crate) libc: Libc,
pub(crate) variant: PythonVariant,
}
impl PythonInstallationKey {
@ -239,6 +240,7 @@ impl PythonInstallationKey {
os: Os,
arch: Arch,
libc: Libc,
variant: PythonVariant,
) -> Self {
Self {
implementation,
@ -249,6 +251,7 @@ impl PythonInstallationKey {
os,
arch,
libc,
variant,
}
}
@ -258,6 +261,7 @@ impl PythonInstallationKey {
os: Os,
arch: Arch,
libc: Libc,
variant: PythonVariant,
) -> Self {
Self {
implementation,
@ -268,6 +272,7 @@ impl PythonInstallationKey {
os,
arch,
libc,
variant,
}
}
@ -303,9 +308,13 @@ impl PythonInstallationKey {
impl fmt::Display for PythonInstallationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let variant = match self.variant {
PythonVariant::Default => String::new(),
PythonVariant::Freethreaded => format!("+{}", self.variant),
};
write!(
f,
"{}-{}.{}.{}{}-{}-{}-{}",
"{}-{}.{}.{}{}{}-{}-{}-{}",
self.implementation,
self.major,
self.minor,
@ -313,6 +322,7 @@ impl fmt::Display for PythonInstallationKey {
self.prerelease
.map(|pre| pre.to_string())
.unwrap_or_default(),
variant,
self.os,
self.arch,
self.libc
@ -349,6 +359,19 @@ impl FromStr for PythonInstallationKey {
PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid libc: {err}"))
})?;
let (version, variant) = match version.split_once('+') {
Some((version, variant)) => {
let variant = PythonVariant::from_str(variant).map_err(|()| {
PythonInstallationKeyError::ParseError(
key.to_string(),
format!("invalid Python variant: {variant}"),
)
})?;
(version, variant)
}
None => (*version, PythonVariant::Default),
};
let version = PythonVersion::from_str(version).map_err(|err| {
PythonInstallationKeyError::ParseError(
key.to_string(),
@ -362,6 +385,7 @@ impl FromStr for PythonInstallationKey {
os,
arch,
libc,
variant,
))
}
}

View file

@ -26,7 +26,8 @@ use crate::implementation::LenientImplementationName;
use crate::platform::{Arch, Libc, Os};
use crate::pointer_size::PointerSize;
use crate::{
Prefix, PythonInstallationKey, PythonVersion, Target, VersionRequest, VirtualEnvironment,
Prefix, PythonInstallationKey, PythonVariant, PythonVersion, Target, VersionRequest,
VirtualEnvironment,
};
/// A Python executable and its associated platform markers.
@ -161,9 +162,18 @@ impl Interpreter {
self.os(),
self.arch(),
self.libc(),
self.variant(),
)
}
pub fn variant(&self) -> PythonVariant {
if self.gil_disabled() {
PythonVariant::Freethreaded
} else {
PythonVariant::default()
}
}
/// Return the [`Arch`] reported by the interpreter platform tags.
pub fn arch(&self) -> Arch {
Arch::from(&self.platform().arch())

View file

@ -20,7 +20,7 @@ use crate::libc::LibcDetectionError;
use crate::platform::Error as PlatformError;
use crate::platform::{Arch, Libc, Os};
use crate::python_version::PythonVersion;
use crate::PythonRequest;
use crate::{PythonRequest, PythonVariant};
use uv_fs::{LockedFile, Simplified};
#[derive(Error, Debug)]
@ -329,13 +329,17 @@ impl ManagedPythonInstallation {
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
self.python_dir().join("Lib")
} else {
let lib_suffix = match self.key.variant {
PythonVariant::Default => "",
PythonVariant::Freethreaded => "t",
};
let python = if matches!(
self.key.implementation,
LenientImplementationName::Known(ImplementationName::PyPy)
) {
format!("pypy{}", self.key.version().python_version())
} else {
format!("python{}", self.key.version().python_version())
format!("python{}{lib_suffix}", self.key.version().python_version())
};
self.python_dir().join("lib").join(python)
};