Skip to content

Commit

Permalink
Support freethreading python (#2805)
Browse files Browse the repository at this point in the history
freethreaded python reintroduces abiflags since it is incompatible with
regular native modules and abi3.

Tests: None yet! We're lacking cpython 3.13 no-gil builds we can use in
ci.

My test setup:

```
PYTHON_CONFIGURE_OPTS="--enable-shared --disable-gil" pyenv install 3.13.0a5
cargo run -q -- venv -q -p python3.13 .venv3.13 --no-cache-dir && cargo run -q -- pip install -v psutil --no-cache-dir && .venv3.13/bin/python -c "import psutil"
```

Fixes #2429
  • Loading branch information
konstin committed Apr 12, 2024
1 parent 98fd9d7 commit 7f70849
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 32 deletions.
71 changes: 43 additions & 28 deletions crates/platform-tags/src/tags.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::collections::BTreeSet;
use std::fmt::Formatter;
use std::str::FromStr;
use std::sync::Arc;
use std::{cmp, num::NonZeroU32};

Expand All @@ -18,6 +17,8 @@ pub enum TagsError {
UnknownImplementation(String),
#[error("Invalid priority: {0}")]
InvalidPriority(usize, #[source] std::num::TryFromIntError),
#[error("Only CPython can be freethreading, not: {0}")]
GilIsACpythonProblem(String),
}

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Clone)]
Expand Down Expand Up @@ -93,8 +94,9 @@ impl Tags {
python_version: (u8, u8),
implementation_name: &str,
implementation_version: (u8, u8),
gil_disabled: bool,
) -> Result<Self, TagsError> {
let implementation = Implementation::from_str(implementation_name)?;
let implementation = Implementation::parse(implementation_name, gil_disabled)?;
let platform_tags = compatible_tags(platform)?;

let mut tags = Vec::with_capacity(5 * platform_tags.len());
Expand All @@ -108,15 +110,18 @@ impl Tags {
));
}
// 2. abi3 and no abi (e.g. executable binary)
if matches!(implementation, Implementation::CPython) {
if let Implementation::CPython { gil_disabled } = implementation {
// For some reason 3.2 is the minimum python for the cp abi
for minor in (2..=python_version.1).rev() {
for platform_tag in &platform_tags {
tags.push((
implementation.language_tag((python_version.0, minor)),
"abi3".to_string(),
platform_tag.clone(),
));
// No abi3 for freethreading python
if !gil_disabled {
for platform_tag in &platform_tags {
tags.push((
implementation.language_tag((python_version.0, minor)),
"abi3".to_string(),
platform_tag.clone(),
));
}
}
// Only include `none` tags for the current CPython version
if minor == python_version.1 {
Expand Down Expand Up @@ -151,9 +156,9 @@ impl Tags {
}
}
// 4. no binary
if matches!(implementation, Implementation::CPython) {
if matches!(implementation, Implementation::CPython { .. }) {
tags.push((
format!("cp{}{}", python_version.0, python_version.1),
implementation.language_tag(python_version),
"none".to_string(),
"any".to_string(),
));
Expand Down Expand Up @@ -291,7 +296,7 @@ impl std::fmt::Display for Tags {

#[derive(Debug, Clone, Copy)]
enum Implementation {
CPython,
CPython { gil_disabled: bool },
PyPy,
Pyston,
}
Expand All @@ -302,7 +307,7 @@ impl Implementation {
fn language_tag(self, python_version: (u8, u8)) -> String {
match self {
// Ex) `cp39`
Self::CPython => format!("cp{}{}", python_version.0, python_version.1),
Self::CPython { .. } => format!("cp{}{}", python_version.0, python_version.1),
// Ex) `pp39`
Self::PyPy => format!("pp{}{}", python_version.0, python_version.1),
// Ex) `pt38``
Expand All @@ -313,11 +318,20 @@ impl Implementation {
fn abi_tag(self, python_version: (u8, u8), implementation_version: (u8, u8)) -> String {
match self {
// Ex) `cp39`
Self::CPython => {
Self::CPython { gil_disabled } => {
if python_version.1 <= 7 {
format!("cp{}{}m", python_version.0, python_version.1)
} else if gil_disabled {
// https://peps.python.org/pep-0703/#build-configuration-changes
// Python 3.13+ only, but it makes more sense to just rely on the sysconfig var.
format!("cp{}{}t", python_version.0, python_version.1,)
} else {
format!("cp{}{}", python_version.0, python_version.1)
format!(
"cp{}{}{}",
python_version.0,
python_version.1,
if gil_disabled { "t" } else { "" }
)
}
}
// Ex) `pypy39_pp73`
Expand All @@ -338,23 +352,22 @@ impl Implementation {
),
}
}
}

impl FromStr for Implementation {
type Err = TagsError;

fn from_str(s: &str) -> Result<Self, TagsError> {
match s {
fn parse(name: &str, gil_disabled: bool) -> Result<Self, TagsError> {
if gil_disabled && name != "cpython" {
return Err(TagsError::GilIsACpythonProblem(name.to_string()));
}
match name {
// Known and supported implementations.
"cpython" => Ok(Self::CPython),
"cpython" => Ok(Self::CPython { gil_disabled }),
"pypy" => Ok(Self::PyPy),
"pyston" => Ok(Self::Pyston),
// Known but unsupported implementations.
"python" => Err(TagsError::UnsupportedImplementation(s.to_string())),
"ironpython" => Err(TagsError::UnsupportedImplementation(s.to_string())),
"jython" => Err(TagsError::UnsupportedImplementation(s.to_string())),
"python" => Err(TagsError::UnsupportedImplementation(name.to_string())),
"ironpython" => Err(TagsError::UnsupportedImplementation(name.to_string())),
"jython" => Err(TagsError::UnsupportedImplementation(name.to_string())),
// Unknown implementations.
_ => Err(TagsError::UnknownImplementation(s.to_string())),
_ => Err(TagsError::UnknownImplementation(name.to_string())),
}
}
}
Expand Down Expand Up @@ -547,7 +560,7 @@ mod tests {
/// The list is displayed in decreasing priority.
///
/// A reference list can be generated with:
/// ```
/// ```text
/// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"`
/// ````
#[test]
Expand Down Expand Up @@ -908,7 +921,7 @@ mod tests {
/// The list is displayed in decreasing priority.
///
/// A reference list can be generated with:
/// ```
/// ```text
/// $ python -c "from packaging import tags; [print(tag) for tag in tags.sys_tags()]"`
/// ```
#[test]
Expand All @@ -924,6 +937,7 @@ mod tests {
(3, 9),
"cpython",
(3, 9),
false,
)
.unwrap();
assert_snapshot!(
Expand Down Expand Up @@ -1546,6 +1560,7 @@ mod tests {
(3, 9),
"cpython",
(3, 9),
false,
)
.unwrap();
assert_snapshot!(
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ workspace = true
cache-key = { workspace = true }
distribution-types = { workspace = true }
pypi-types = { workspace = true }
uv-fs = { workspace = true }
uv-fs = { workspace = true, features = ["tokio"] }
uv-normalize = { workspace = true }

cachedir = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ impl CacheBucket {
Self::BuiltWheels => "built-wheels-v3",
Self::FlatIndex => "flat-index-v0",
Self::Git => "git-v0",
Self::Interpreter => "interpreter-v0",
Self::Interpreter => "interpreter-v1",
Self::Simple => "simple-v7",
Self::Wheels => "wheels-v1",
Self::Archive => "archive-v0",
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ winapi = { workspace = true }
[dev-dependencies]
anyhow = { version = "1.0.80" }
indoc = { version = "2.0.4" }
insta = { version = "1.36.1" }
insta = { version = "1.36.1", features = ["filters"] }
itertools = { version = "0.12.1" }
tempfile = { version = "3.9.0" }
3 changes: 3 additions & 0 deletions crates/uv-interpreter/python/get_interpreter_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@ def main() -> None:
"scheme": get_scheme(),
"virtualenv": get_virtualenv(),
"platform": get_operating_system_and_architecture(),
# The `t` abiflag for freethreading python
# https://peps.python.org/pep-0703/#build-configuration-changes
"gil_disabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
}
print(json.dumps(interpreter_info))

Expand Down
17 changes: 16 additions & 1 deletion crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct Interpreter {
sys_executable: PathBuf,
stdlib: PathBuf,
tags: OnceCell<Tags>,
gil_disabled: bool,
}

impl Interpreter {
Expand All @@ -55,6 +56,7 @@ impl Interpreter {
virtualenv: info.virtualenv,
prefix: info.prefix,
base_exec_prefix: info.base_exec_prefix,
gil_disabled: info.gil_disabled,
base_prefix: info.base_prefix,
base_executable: info.base_executable,
sys_executable: info.sys_executable,
Expand Down Expand Up @@ -89,6 +91,7 @@ impl Interpreter {
sys_executable: PathBuf::from("/dev/null"),
stdlib: PathBuf::from("/dev/null"),
tags: OnceCell::new(),
gil_disabled: false,
}
}

Expand Down Expand Up @@ -123,6 +126,7 @@ impl Interpreter {
self.python_tuple(),
self.implementation_name(),
self.implementation_tuple(),
self.gil_disabled,
)
})
}
Expand Down Expand Up @@ -290,6 +294,15 @@ impl Interpreter {
&self.virtualenv
}

/// Return whether this is a Python 3.13+ freethreading Python, as specified by the sysconfig var
/// `Py_GIL_DISABLED`.
///
/// freethreading Python is incompatible with earlier native modules, re-introducing
/// abiflags with a `t` flag. <https://peps.python.org/pep-0703/#build-configuration-changes>
pub fn gil_disabled(&self) -> bool {
self.gil_disabled
}

/// Return the [`Layout`] environment used to install wheels into this interpreter.
pub fn layout(&self) -> Layout {
Layout {
Expand Down Expand Up @@ -374,6 +387,7 @@ struct InterpreterInfo {
base_executable: Option<PathBuf>,
sys_executable: PathBuf,
stdlib: PathBuf,
gil_disabled: bool,
}

impl InterpreterInfo {
Expand Down Expand Up @@ -603,7 +617,8 @@ mod tests {
"platlib": "lib/python3.12/site-packages",
"purelib": "lib/python3.12/site-packages",
"scripts": "bin"
}
},
"gil_disabled": true
}
"##};

Expand Down
2 changes: 2 additions & 0 deletions crates/uv-resolver/tests/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ static TAGS_311: Lazy<Tags> = Lazy::new(|| {
(3, 11),
"cpython",
(3, 11),
false,
)
.unwrap()
});
Expand Down Expand Up @@ -732,6 +733,7 @@ static TAGS_310: Lazy<Tags> = Lazy::new(|| {
(3, 10),
"cpython",
(3, 10),
false,
)
.unwrap()
});
1 change: 1 addition & 0 deletions crates/uv/src/commands/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ pub(crate) async fn pip_compile(
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.gil_disabled(),
)?)
} else {
Cow::Borrowed(interpreter.tags()?)
Expand Down

0 comments on commit 7f70849

Please sign in to comment.