diff --git a/Cargo.lock b/Cargo.lock index 928499604..6daf5c894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4249,6 +4249,7 @@ dependencies = [ "pixi_default_versions", "pixi_manifest", "rattler_conda_types", + "regex", "uv-pep508", "uv-platform-tags", ] diff --git a/crates/pypi_modifiers/Cargo.toml b/crates/pypi_modifiers/Cargo.toml index 5b9f4deb6..ed76c3e27 100644 --- a/crates/pypi_modifiers/Cargo.toml +++ b/crates/pypi_modifiers/Cargo.toml @@ -14,5 +14,6 @@ miette = { workspace = true } pixi_default_versions = { workspace = true } pixi_manifest = { workspace = true } rattler_conda_types = { workspace = true } +regex = { workspace = true } uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } diff --git a/crates/pypi_modifiers/src/pypi_tags.rs b/crates/pypi_modifiers/src/pypi_tags.rs index abfee469a..5ff8db001 100644 --- a/crates/pypi_modifiers/src/pypi_tags.rs +++ b/crates/pypi_modifiers/src/pypi_tags.rs @@ -1,7 +1,11 @@ +use std::sync::OnceLock; + use miette::{Context, IntoDiagnostic}; use pixi_default_versions::{default_glibc_version, default_mac_os_version}; use pixi_manifest::{LibCSystemRequirement, SystemRequirements}; +use rattler_conda_types::MatchSpec; use rattler_conda_types::{Arch, PackageRecord, Platform}; +use regex::Regex; use uv_platform_tags::Os; use uv_platform_tags::Tags; @@ -25,7 +29,8 @@ pub fn get_pypi_tags( let platform = get_platform_tags(platform, system_requirements)?; let python_version = get_python_version(python_record)?; let implementation_name = get_implementation_name(python_record)?; - create_tags(platform, python_version, implementation_name) + let gil_disabled = gil_disabled(python_record)?; + create_tags(platform, python_version, implementation_name, gil_disabled) } /// Create a uv platform tag for the specified platform @@ -159,10 +164,40 @@ fn get_implementation_name(python_record: &PackageRecord) -> miette::Result<&'st } } +/// Return whether the specified record has gil disabled (by being a free-threaded python interpreter) +fn gil_disabled(python_record: &PackageRecord) -> miette::Result { + // In order to detect if the python interpreter is free-threaded, we look at the depends + // field of the record. If the record has a dependency on `python_abi`, then + // look at the build string to detect cpXXXt (free-threaded python interpreter). + static REGEX: OnceLock = OnceLock::new(); + + let regex = REGEX.get_or_init(|| { + Regex::new(r"cp\d{3}t").expect("regex for free-threaded python interpreter should compile") + }); + + let deps = python_record + .depends + .iter() + .map(|dep| MatchSpec::from_str(dep, rattler_conda_types::ParseStrictness::Lenient)) + .collect::, _>>() + .into_diagnostic()?; + + Ok(deps.iter().any(|spec| { + spec.name + .as_ref() + .is_some_and(|name| name.as_source() == "python_abi") + && spec.build.as_ref().is_some_and(|build| { + let raw_str = format!("{}", build); + regex.is_match(&raw_str) + }) + })) +} + fn create_tags( platform: uv_platform_tags::Platform, python_version: (u8, u8), implementation_name: &str, + gil_disabled: bool, ) -> miette::Result { // Build the wheel tags based on the interpreter, the target platform, and the python version. let tags = Tags::from_env( @@ -172,8 +207,7 @@ fn create_tags( // TODO: This might not be entirely correct.. python_version, true, - // Should revisit this when this lands: https://github.com/conda-forge/python-feedstock/pull/679 - false, + gil_disabled, ) .into_diagnostic() .context("failed to determine the python wheel tags for the target platform")?; diff --git a/docs/tutorials/python.md b/docs/tutorials/python.md index 868d020a8..c9624a27f 100644 --- a/docs/tutorials/python.md +++ b/docs/tutorials/python.md @@ -215,6 +215,11 @@ xz 5.2.6 h57fd34a_0 230.2 KiB conda xz-5.2.6-h5 This is used to determine the Python version to install in the environment. This way, pixi automatically manages/bootstraps the Python interpreter for you, so no more `brew`, `apt` or other system install steps. +!!! Free-threaded interpreters + If you want to use a free-threaded Python interpreter, you can add `python-freethreading = "*"` to the dependencies in your `pixi` configuration. + This ensures that a free-threaded version of Python is installed in the environment. + You can read more about free-threaded Python [here](https://docs.python.org/3/howto/free-threading-python.html). + Here, you can see the different conda and Pypi packages listed. As you can see, the `pixi-py` package that we are working on is installed in editable mode. Every environment in pixi is isolated but reuses files that are hard-linked from a central cache directory.