diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6e3affe5..53a6cdb11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.action }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: @@ -43,9 +43,10 @@ jobs: - macos-14 - windows-latest python-version: - - '3.9' - - '3.13' - - 'pypy3.10' + - "3.9" + - "3.13" + - "3.13t" + - "pypy3.10" exclude: # Skip PyPy on macOS M1 runner because they are built for x86_64 - os: macos-14 @@ -81,7 +82,7 @@ jobs: auto-activate-base: "false" activate-environment: "" miniconda-version: "latest" - - uses: actions/setup-python@v5 + - uses: Quansight-Labs/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set PYTHON_VERSION env var @@ -96,7 +97,10 @@ jobs: run: | uv tool install virtualenv uv tool install twine - uv pip install --system "ziglang>=0.10.0,<0.13.0" uniffi-bindgen==0.28.0 + uv tool install uniffi-bindgen==0.28.0 + - uses: mlugg/setup-zig@v1 + with: + version: 0.12.1 - uses: dtolnay/rust-toolchain@stable id: rustup with: diff --git a/src/auditwheel/audit.rs b/src/auditwheel/audit.rs index 0256eef4f..21c4f93a4 100644 --- a/src/auditwheel/audit.rs +++ b/src/auditwheel/audit.rs @@ -17,7 +17,7 @@ use std::{fmt, io}; use thiserror::Error; static IS_LIBPYTHON: Lazy = - Lazy::new(|| Regex::new(r"^libpython3\.\d+m?u?\.so\.\d+\.\d+$").unwrap()); + Lazy::new(|| Regex::new(r"^libpython3\.\d+m?u?t?\.so\.\d+\.\d+$").unwrap()); /// Error raised during auditing an elf file for manylinux/musllinux compatibility #[derive(Error, Debug)] diff --git a/src/build_options.rs b/src/build_options.rs index b6c0bb3a5..fd136cbc2 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -274,6 +274,10 @@ impl BuildOptions { .get("ABIFLAGS") .map(ToString::to_string) .unwrap_or_default(); + let gil_disabled = sysconfig_data + .get("Py_GIL_DISABLED") + .map(|x| x == "1") + .unwrap_or_default(); let ext_suffix = sysconfig_data .get("EXT_SUFFIX") .context("syconfig didn't define an `EXT_SUFFIX` ಠ_ಠ")?; @@ -299,6 +303,7 @@ impl BuildOptions { abiflags, ext_suffix: ext_suffix.to_string(), pointer_width: None, + gil_disabled, }, executable: PathBuf::new(), platform: None, @@ -376,6 +381,7 @@ impl BuildOptions { abiflags: "".to_string(), ext_suffix: ".pyd".to_string(), pointer_width: None, + gil_disabled: false, }, executable: PathBuf::new(), platform: None, @@ -402,6 +408,7 @@ impl BuildOptions { abiflags: "".to_string(), ext_suffix: ".pyd".to_string(), pointer_width: None, + gil_disabled: false, }, executable: PathBuf::new(), platform: None, @@ -441,6 +448,7 @@ impl BuildOptions { abiflags: "".to_string(), ext_suffix: "".to_string(), pointer_width: None, + gil_disabled: false, }, executable: PathBuf::new(), platform: None, @@ -1244,18 +1252,27 @@ fn find_interpreter_in_sysconfig( let mut interpreters = Vec::new(); for interp in interpreter { let python = interp.display().to_string(); - let (python_impl, python_ver) = if let Some(ver) = python.strip_prefix("pypy") { - (InterpreterKind::PyPy, ver.strip_prefix('-').unwrap_or(ver)) - } else if let Some(ver) = python.strip_prefix("graalpy") { + let (python_impl, python_ver, abiflags) = if let Some(ver) = python.strip_prefix("pypy") { ( - InterpreterKind::GraalPy, + InterpreterKind::PyPy, ver.strip_prefix('-').unwrap_or(ver), + "", ) - } else if let Some(ver) = python.strip_prefix("python") { + } else if let Some(ver) = python.strip_prefix("graalpy") { ( - InterpreterKind::CPython, + InterpreterKind::GraalPy, ver.strip_prefix('-').unwrap_or(ver), + "", ) + } else if let Some(ver) = python.strip_prefix("python") { + // Also accept things like `python3.13t` for free-threaded python + let (ver, abiflags) = + if let Some(ver) = ver.strip_prefix('-').unwrap_or(ver).strip_suffix('t') { + (ver, "t") + } else { + (ver, "") + }; + (InterpreterKind::CPython, ver, abiflags) } else if python .chars() .next() @@ -1263,7 +1280,7 @@ fn find_interpreter_in_sysconfig( .unwrap_or(false) { // Eg: -i 3.9 without interpreter kind, assume it's CPython - (InterpreterKind::CPython, &*python) + (InterpreterKind::CPython, &*python, "") } else { // if interpreter not known if std::path::Path::new(&python).is_file() { @@ -1284,7 +1301,12 @@ fn find_interpreter_in_sysconfig( let ver_minor = ver_minor.parse::().with_context(|| { format!("Invalid python interpreter minor version '{ver_minor}', expect a digit") })?; - let sysconfig = InterpreterConfig::lookup_one(target, python_impl, (ver_major, ver_minor)) + + if (ver_major, ver_minor) < (3, 13) && abiflags == "t" { + bail!("Free-threaded Python interpreter is only supported on 3.13 and later."); + } + + let sysconfig = InterpreterConfig::lookup_one(target, python_impl, (ver_major, ver_minor), abiflags) .with_context(|| { format!("Failed to find a {python_impl} {ver_major}.{ver_minor} interpreter in known sysconfig") })?; diff --git a/src/cross_compile.rs b/src/cross_compile.rs index ec5ae30b0..57428015b 100644 --- a/src/cross_compile.rs +++ b/src/cross_compile.rs @@ -52,6 +52,7 @@ KEYS = [ "ABIFLAGS", "EXT_SUFFIX", "SOABI", + "Py_GIL_DISABLED", ] for key in KEYS: print(key, build_time_vars.get(key, "")) diff --git a/src/python_interpreter/config.rs b/src/python_interpreter/config.rs index 8de21bd1f..f87192147 100644 --- a/src/python_interpreter/config.rs +++ b/src/python_interpreter/config.rs @@ -22,8 +22,11 @@ pub struct InterpreterConfig { #[serde(rename = "interpreter")] pub interpreter_kind: InterpreterKind, /// For linux and mac, this contains the value of the abiflags, e.g. "m" - /// for python3.7m or "dm" for python3.6dm. Since python3.8, the value is - /// empty. On windows, the value was always None. + /// for python3.7m or "dm" for python3.6dm. + /// + /// * Since python3.8, the value is empty + /// * Since python3.13, the value is "t" for free-threaded builds. + /// * On Windows, the value was always None. /// /// See PEP 261 and PEP 393 for details pub abiflags: String, @@ -31,6 +34,8 @@ pub struct InterpreterConfig { pub ext_suffix: String, /// Pointer width pub pointer_width: Option, + /// Is GIL disabled, i.e. free-threaded build + pub gil_disabled: bool, } impl InterpreterConfig { @@ -39,6 +44,7 @@ impl InterpreterConfig { target: &Target, python_impl: InterpreterKind, python_version: (usize, usize), + abiflags: &str, ) -> Option { use InterpreterKind::*; @@ -49,18 +55,17 @@ impl InterpreterConfig { } let python_ext_arch = target.get_python_ext_arch(python_impl); let target_env = target.get_python_target_env(python_impl, python_version); + let gil_disabled = abiflags == "t"; match (target.target_os(), python_impl) { (Os::Linux, CPython) => { let abiflags = if python_version < (3, 8) { "m".to_string() } else { - "".to_string() + abiflags.to_string() }; - let ldversion = format!("{}{}{}", major, minor, abiflags); - let ext_suffix = format!( - ".cpython-{}-{}-linux-{}.so", - ldversion, python_ext_arch, target_env - ); + let ldversion = format!("{major}{minor}{abiflags}"); + let ext_suffix = + format!(".cpython-{ldversion}-{python_ext_arch}-linux-{target_env}.so"); Some(Self { major, minor, @@ -68,12 +73,12 @@ impl InterpreterConfig { abiflags, ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Linux, PyPy) => { - let abi_tag = format!("pypy{}{}-{}", major, minor, PYPY_ABI_TAG); - let ext_suffix = - format!(".{}-{}-linux-{}.so", abi_tag, python_ext_arch, target_env); + let abi_tag = format!("pypy{major}{minor}-{PYPY_ABI_TAG}"); + let ext_suffix = format!(".{abi_tag}-{python_ext_arch}-linux-{target_env}.so"); Some(Self { major, minor, @@ -81,16 +86,17 @@ impl InterpreterConfig { abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Macos, CPython) => { let abiflags = if python_version < (3, 8) { "m".to_string() } else { - "".to_string() + abiflags.to_string() }; - let ldversion = format!("{}{}{}", major, minor, abiflags); - let ext_suffix = format!(".cpython-{}-darwin.so", ldversion); + let ldversion = format!("{major}{minor}{abiflags}"); + let ext_suffix = format!(".cpython-{ldversion}-darwin.so"); Some(Self { major, minor, @@ -98,10 +104,11 @@ impl InterpreterConfig { abiflags, ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Macos, PyPy) => { - let ext_suffix = format!(".pypy{}{}-{}-darwin.so", major, minor, PYPY_ABI_TAG); + let ext_suffix = format!(".pypy{major}{minor}-{PYPY_ABI_TAG}-darwin.so"); Some(Self { major, minor, @@ -109,6 +116,7 @@ impl InterpreterConfig { abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Windows, CPython) => { @@ -121,15 +129,17 @@ impl InterpreterConfig { Arch::X86_64 => "win_amd64", _ => return None, }; - format!(".cp{}{}-{}.pyd", major, minor, platform) + format!(".cp{major}{minor}{abiflags}-{platform}.pyd") }; Some(Self { major, minor, interpreter_kind: CPython, + // abiflags is always empty on Windows abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Windows, PyPy) => { @@ -137,21 +147,26 @@ impl InterpreterConfig { // PyPy on Windows only supports x86_64 return None; } - let ext_suffix = format!(".pypy{}{}-{}-win_amd64.pyd", major, minor, PYPY_ABI_TAG); + let ext_suffix = format!(".pypy{major}{minor}-{PYPY_ABI_TAG}-win_amd64.pyd"); Some(Self { major, minor, interpreter_kind: PyPy, + // abiflags is always empty on Windows abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::FreeBsd, CPython) => { let (abiflags, ext_suffix) = if python_version < (3, 8) { ("m".to_string(), ".so".to_string()) } else { - ("".to_string(), format!(".cpython-{}{}.so", major, minor)) + ( + abiflags.to_string(), + format!(".cpython-{major}{minor}{abiflags}.so"), + ) }; Some(Self { major, @@ -160,6 +175,7 @@ impl InterpreterConfig { abiflags, ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::NetBsd, CPython) => { @@ -171,11 +187,12 @@ impl InterpreterConfig { abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::OpenBsd, CPython) => { - let ldversion = format!("{}{}", major, minor); - let ext_suffix = format!(".cpython-{}.so", ldversion); + let ldversion = format!("{major}{minor}"); + let ext_suffix = format!(".cpython-{ldversion}{abiflags}.so"); Some(Self { major, minor, @@ -183,12 +200,12 @@ impl InterpreterConfig { abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (Os::Emscripten, CPython) => { - let ldversion = format!("{}{}", major, minor); - let ext_suffix = - format!(".cpython-{}-{}-emscripten.so", ldversion, python_ext_arch); + let ldversion = format!("{major}{minor}"); + let ext_suffix = format!(".cpython-{ldversion}-{python_ext_arch}-emscripten.so"); Some(Self { major, minor, @@ -196,6 +213,7 @@ impl InterpreterConfig { abiflags: String::new(), ext_suffix, pointer_width: Some(target.pointer_width()), + gil_disabled, }) } (_, _) => None, @@ -210,7 +228,12 @@ impl InterpreterConfig { (InterpreterKind::PyPy, MAXIMUM_PYPY_MINOR), ] { for minor in MINIMUM_PYTHON_MINOR..=max_minor_ver { - if let Some(config) = Self::lookup_one(target, python_impl, (3, minor)) { + if let Some(config) = Self::lookup_one(target, python_impl, (3, minor), "") { + configs.push(config); + } + } + for minor in 13..=max_minor_ver { + if let Some(config) = Self::lookup_one(target, python_impl, (3, minor), "t") { configs.push(config); } } @@ -243,6 +266,7 @@ impl InterpreterConfig { let mut ext_suffix = None; let mut abi_tag = None; let mut pointer_width = None; + let mut build_flags: Option = None; for (i, line) in lines.enumerate() { let line = line.context("failed to read line from config")?; @@ -256,6 +280,7 @@ impl InterpreterConfig { "ext_suffix" => parse_value!(ext_suffix, value), "abi_tag" => parse_value!(abi_tag, value), "pointer_width" => parse_value!(pointer_width, value), + "build_flags" => parse_value!(build_flags, value), _ => continue, } } @@ -334,6 +359,9 @@ impl InterpreterConfig { } else { ext_suffix.context("missing value for ext_suffix")? }; + let gil_disabled = build_flags + .map(|flags| flags.contains("Py_GIL_DISABLED")) + .unwrap_or(false); Ok(Self { major, minor, @@ -341,17 +369,23 @@ impl InterpreterConfig { abiflags: abiflags.unwrap_or_default(), ext_suffix, pointer_width, + gil_disabled, }) } /// Generate pyo3 config file content pub fn pyo3_config_file(&self) -> String { + let build_flags = if self.gil_disabled { + "Py_GIL_DISABLED" + } else { + "" + }; let mut content = format!( r#"implementation={implementation} version={major}.{minor} shared=true abi3=false -build_flags=WITH_THREAD +build_flags={build_flags} suppress_build_script_link_lines=false"#, implementation = self.interpreter_kind, major = self.major, @@ -377,6 +411,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-x86_64-linux-gnu.so"); @@ -385,6 +420,7 @@ mod test { &Target::from_target_triple(Some("i686-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-i386-linux-gnu.so"); @@ -393,6 +429,7 @@ mod test { &Target::from_target_triple(Some("aarch64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-aarch64-linux-gnu.so"); @@ -401,6 +438,7 @@ mod test { &Target::from_target_triple(Some("armv7-unknown-linux-gnueabihf".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-arm-linux-gnueabihf.so"); @@ -409,6 +447,7 @@ mod test { &Target::from_target_triple(Some("arm-unknown-linux-gnueabihf".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-arm-linux-gnueabihf.so"); @@ -417,6 +456,7 @@ mod test { &Target::from_target_triple(Some("powerpc64le-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!( @@ -428,6 +468,7 @@ mod test { &Target::from_target_triple(Some("powerpc-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-powerpc-linux-gnu.so"); @@ -436,6 +477,7 @@ mod test { &Target::from_target_triple(Some("mips64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!( @@ -447,6 +489,7 @@ mod test { &Target::from_target_triple(Some("mips-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-mips-linux-gnu.so"); @@ -455,6 +498,7 @@ mod test { &Target::from_target_triple(Some("s390x-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-s390x-linux-gnu.so"); @@ -464,6 +508,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, ""); @@ -473,6 +518,7 @@ mod test { &Target::from_target_triple(Some("i686-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-x86-linux-gnu.so"); @@ -481,6 +527,7 @@ mod test { &Target::from_target_triple(Some("aarch64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-aarch64-linux-gnu.so"); @@ -489,6 +536,7 @@ mod test { &Target::from_target_triple(Some("armv7-unknown-linux-gnueabihf".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-arm-linux-gnu.so"); @@ -497,6 +545,7 @@ mod test { &Target::from_target_triple(Some("arm-unknown-linux-gnueabihf".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-arm-linux-gnu.so"); @@ -505,6 +554,7 @@ mod test { &Target::from_target_triple(Some("powerpc64le-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-ppc_64-linux-gnu.so"); @@ -513,6 +563,7 @@ mod test { &Target::from_target_triple(Some("s390x-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-s390x-linux-gnu.so"); @@ -525,6 +576,7 @@ mod test { &Target::from_target_triple(Some("x86_64-apple-darwin".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-darwin.so"); @@ -533,6 +585,7 @@ mod test { &Target::from_target_triple(Some("aarch64-apple-darwin".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310-darwin.so"); @@ -541,6 +594,7 @@ mod test { &Target::from_target_triple(Some("x86_64-apple-darwin".to_string())).unwrap(), InterpreterKind::CPython, (3, 7), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, "m"); @@ -551,6 +605,7 @@ mod test { &Target::from_target_triple(Some("x86_64-apple-darwin".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, ""); @@ -560,6 +615,7 @@ mod test { &Target::from_target_triple(Some("aarch64-apple-darwin".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-darwin.so"); @@ -572,6 +628,7 @@ mod test { &Target::from_target_triple(Some("x86_64-pc-windows-msvc".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cp310-win_amd64.pyd"); @@ -580,6 +637,7 @@ mod test { &Target::from_target_triple(Some("i686-pc-windows-msvc".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cp310-win32.pyd"); @@ -588,6 +646,7 @@ mod test { &Target::from_target_triple(Some("aarch64-pc-windows-msvc".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cp310-win_arm64.pyd"); @@ -597,6 +656,7 @@ mod test { &Target::from_target_triple(Some("x86_64-pc-windows-msvc".to_string())).unwrap(), InterpreterKind::PyPy, (3, 9), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".pypy39-pp73-win_amd64.pyd"); @@ -609,6 +669,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-freebsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 7), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, "m"); @@ -618,6 +679,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-freebsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, ""); @@ -627,6 +689,7 @@ mod test { &Target::from_target_triple(Some("i686-unknown-freebsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -635,6 +698,7 @@ mod test { &Target::from_target_triple(Some("aarch64-unknown-freebsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -643,6 +707,7 @@ mod test { &Target::from_target_triple(Some("armv7-unknown-freebsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -655,6 +720,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-netbsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 7), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, ""); @@ -664,6 +730,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-netbsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".so"); @@ -676,6 +743,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-openbsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -684,6 +752,7 @@ mod test { &Target::from_target_triple(Some("i686-unknown-openbsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -692,6 +761,7 @@ mod test { &Target::from_target_triple(Some("aarch64-unknown-openbsd".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-310.so"); @@ -704,6 +774,7 @@ mod test { &Target::from_target_triple(Some("wasm32-unknown-emscripten".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); assert_eq!(sysconfig.abiflags, ""); @@ -716,6 +787,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-linux-gnu".to_string())).unwrap(), InterpreterKind::CPython, (3, 10), + "", ) .unwrap(); let config_file = sysconfig.pyo3_config_file(); @@ -724,7 +796,29 @@ mod test { version=3.10 shared=true abi3=false - build_flags=WITH_THREAD + build_flags= + suppress_build_script_link_lines=false + pointer_width=64"#]]; + expected.assert_eq(&config_file); + } + + #[test] + fn test_pyo3_config_file_free_threaded_python_3_13() { + let sysconfig = InterpreterConfig::lookup_one( + &Target::from_target_triple(Some("x86_64-unknown-linux-gnu".to_string())).unwrap(), + InterpreterKind::CPython, + (3, 13), + "t", + ) + .unwrap(); + assert_eq!(sysconfig.ext_suffix, ".cpython-313t-x86_64-linux-gnu.so"); + let config_file = sysconfig.pyo3_config_file(); + let expected = expect![[r#" + implementation=CPython + version=3.13 + shared=true + abi3=false + build_flags=Py_GIL_DISABLED suppress_build_script_link_lines=false pointer_width=64"#]]; expected.assert_eq(&config_file); @@ -736,6 +830,7 @@ mod test { &Target::from_target_triple(Some("x86_64-unknown-linux-musl".to_string())).unwrap(), InterpreterKind::CPython, (3, 11), + "", ) .unwrap(); assert_eq!(sysconfig.ext_suffix, ".cpython-311-x86_64-linux-musl.so"); @@ -745,7 +840,7 @@ mod test { version=3.11 shared=true abi3=false - build_flags=WITH_THREAD + build_flags= suppress_build_script_link_lines=false pointer_width=64"#]]; expected.assert_eq(&config_file); diff --git a/src/python_interpreter/get_interpreter_metadata.py b/src/python_interpreter/get_interpreter_metadata.py index badd65528..4a1e3b894 100644 --- a/src/python_interpreter/get_interpreter_metadata.py +++ b/src/python_interpreter/get_interpreter_metadata.py @@ -35,6 +35,7 @@ "system": platform.system().lower(), # This one is for generating a config file for pyo3 "pointer_width": struct.calcsize("P") * 8, + "gil_disabled": sysconfig.get_config_var("Py_GIL_DISABLED") == 1, } print(json.dumps(metadata)) diff --git a/src/python_interpreter/mod.rs b/src/python_interpreter/mod.rs index 1085c4c01..d41e0547f 100644 --- a/src/python_interpreter/mod.rs +++ b/src/python_interpreter/mod.rs @@ -245,7 +245,13 @@ fn find_all_windows( Ok(interpreter) } -fn windows_python_info(executable: &Path) -> Result> { +struct WindowsPythonInfo { + major: usize, + minor: usize, + pointer_width: Option, +} + +fn windows_python_info(executable: &Path) -> Result> { let python_info = Command::new(executable) .arg("-c") .arg("import sys; print(sys.version)") @@ -276,12 +282,9 @@ fn windows_python_info(executable: &Path) -> Result> { } else { 32 }; - Ok(Some(InterpreterConfig { + Ok(Some(WindowsPythonInfo { major, minor, - interpreter_kind: InterpreterKind::CPython, - abiflags: String::new(), - ext_suffix: String::new(), pointer_width: Some(pointer_width), })) } else { @@ -353,6 +356,7 @@ struct InterpreterMetadataMessage { // comes from `platform.system()` system: String, soabi: Option, + gil_disabled: bool, } /// The location and version of an interpreter @@ -425,19 +429,19 @@ fn fun_with_abiflags( if matches!(message.abiflags.as_deref(), Some("") | None) { Ok("".to_string()) } else { - bail!("A python 3 interpreter on windows does not define abiflags in its sysconfig ಠ_ಠ") + bail!("A python 3 interpreter on Windows does not define abiflags in its sysconfig ಠ_ಠ") } } else if let Some(ref abiflags) = message.abiflags { if message.minor >= 8 { // for 3.8, "builds with and without pymalloc are ABI compatible" and the flag dropped Ok(abiflags.to_string()) } else if (abiflags != "m") && (abiflags != "dm") { - bail!("A python 3 interpreter on linux or mac os must have 'm' or 'dm' as abiflags ಠ_ಠ") + bail!("A python 3 interpreter on Linux or macOS must have 'm' or 'dm' as abiflags ಠ_ಠ") } else { Ok(abiflags.to_string()) } } else { - bail!("A python 3 interpreter on linux or mac os must define abiflags in its sysconfig ಠ_ಠ") + bail!("A python 3 interpreter on Linux or macOS must define abiflags in its sysconfig ಠ_ಠ") } } @@ -448,7 +452,8 @@ impl PythonInterpreter { false } else { match self.interpreter_kind { - InterpreterKind::CPython => true, + // Free-threaded python does not have stable api support yet + InterpreterKind::CPython => !self.config.gil_disabled, InterpreterKind::PyPy | InterpreterKind::GraalPy => false, } } @@ -695,6 +700,7 @@ impl PythonInterpreter { .ext_suffix .context("syconfig didn't define an `EXT_SUFFIX` ಠ_ಠ")?, pointer_width: None, + gil_disabled: message.gil_disabled, }, executable, platform, @@ -989,19 +995,19 @@ mod tests { let target = Target::from_target_triple(Some("x86_64-unknown-linux-gnu".to_string())).unwrap(); let pythons = PythonInterpreter::find_by_target(&target, None); - assert_eq!(pythons.len(), 11); + assert_eq!(pythons.len(), 12); let pythons = PythonInterpreter::find_by_target( &target, Some(&VersionSpecifiers::from_str(">=3.8").unwrap()), ); - assert_eq!(pythons.len(), 9); + assert_eq!(pythons.len(), 10); let pythons = PythonInterpreter::find_by_target( &target, Some(&VersionSpecifiers::from_str(">=3.10").unwrap()), ); - assert_eq!(pythons.len(), 5); + assert_eq!(pythons.len(), 6); } #[test] @@ -1010,6 +1016,7 @@ mod tests { (".cpython-37m-x86_64-linux-gnu.so", Some("cp37m")), (".cpython-310-x86_64-linux-gnu.so", Some("cp310")), (".cpython-310-darwin.so", Some("cp310")), + (".cpython-313t-darwin.so", Some("cp313t")), (".cp310-win_amd64.pyd", Some("cp310")), (".cp39-mingw_x86_64.pyd", Some("cp39")), (".cpython-312-wasm32-wasi.so", Some("cp312")), diff --git a/tests/common/integration.rs b/tests/common/integration.rs index de5144dbc..73bcec0a9 100644 --- a/tests/common/integration.rs +++ b/tests/common/integration.rs @@ -94,7 +94,6 @@ pub fn test_integration( let file = File::create(venvs_dir.join("cffi-provider.lock"))?; file.file().lock_exclusive()?; if !dbg!(venvs_dir.join(cffi_provider)).is_dir() { - println!("CRAETAITROAPTGIIDSOGRKADS"); dbg!(create_virtualenv_name( cffi_provider, python_interp.clone().map(PathBuf::from)