diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9d534689..153c2af6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changelog * Do specifier matching correctly when the specifier contains an epoch number and has more components than the version (:issue:`683`) +* Support the experimental ``--disable-gil`` builds in packaging.tags + (:issue:`727`) * BREAKING: Make optional ``metadata.Metadata`` attributes default to ``None`` (:issue:`733`) * Fix errors when trying to access the ``description_content_type``, ``keywords``, and ``requires_python`` attributes on ``metadata.Metadata`` when those values diff --git a/src/packaging/tags.py b/src/packaging/tags.py index 37f33b1e..dc0bbb6d 100644 --- a/src/packaging/tags.py +++ b/src/packaging/tags.py @@ -4,6 +4,7 @@ import logging import platform +import re import struct import subprocess import sys @@ -124,20 +125,37 @@ def _normalize_string(string: str) -> str: return string.replace(".", "_").replace("-", "_").replace(" ", "_") -def _abi3_applies(python_version: PythonVersion) -> bool: +def _is_threaded_cpython(abis: List[str]) -> bool: + """ + Determine if the ABI corresponds to a threaded (`--disable-gil`) build. + + The threaded builds are indicated by a "t" in the abiflags. + """ + if len(abis) == 0: + return False + # expect e.g., cp313 + m = re.match(r"cp\d+(.*)", abis[0]) + if not m: + return False + abiflags = m.group(1) + return "t" in abiflags + + +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool: """ Determine if the Python version supports abi3. - PEP 384 was first implemented in Python 3.2. + PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`) + builds do not support abi3. """ - return len(python_version) > 1 and tuple(python_version) >= (3, 2) + return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: py_version = tuple(py_version) # To allow for version comparison. abis = [] version = _version_nodot(py_version[:2]) - debug = pymalloc = ucs4 = "" + threading = debug = pymalloc = ucs4 = "" with_debug = _get_config_var("Py_DEBUG", warn) has_refcount = hasattr(sys, "gettotalrefcount") # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled @@ -146,6 +164,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: has_ext = "_d.pyd" in EXTENSION_SUFFIXES if with_debug or (with_debug is None and (has_refcount or has_ext)): debug = "d" + if py_version >= (3, 13) and _get_config_var("Py_NOGIL", warn): + threading = "t" if py_version < (3, 8): with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) if with_pymalloc or with_pymalloc is None: @@ -159,13 +179,8 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: elif debug: # Debug builds can also load "normal" extension modules. # We can also assume no UCS-4 or pymalloc requirement. - abis.append(f"cp{version}") - abis.insert( - 0, - "cp{version}{debug}{pymalloc}{ucs4}".format( - version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 - ), - ) + abis.append(f"cp{version}{threading}") + abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}") return abis @@ -213,11 +228,14 @@ def cpython_tags( for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) - if _abi3_applies(python_version): + + threading = _is_threaded_cpython(abis) + use_abi3 = _abi3_applies(python_version, threading) + if use_abi3: yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) - if _abi3_applies(python_version): + if use_abi3: for minor_version in range(python_version[1] - 1, 1, -1): for platform_ in platforms: interpreter = "cp{version}".format( diff --git a/tests/test_tags.py b/tests/test_tags.py index c9e921e1..e7f1c583 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -781,6 +781,14 @@ def test_all_args(self): tags.Tag("cp32", "abi3", "plat2"), ] + result = list(tags.cpython_tags((3, 13), ["cp313t"], ["plat1", "plat2"])) + assert result == [ + tags.Tag("cp313", "cp313t", "plat1"), + tags.Tag("cp313", "cp313t", "plat2"), + tags.Tag("cp313", "none", "plat1"), + tags.Tag("cp313", "none", "plat2"), + ] + def test_python_version_defaults(self): tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"])) interpreter = "cp" + tags._version_nodot(sys.version_info[:2]) @@ -894,6 +902,17 @@ def test__generic_abi_graal(self, monkeypatch): monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == ["graalpy_38_native"] + def test__generic_abi_disable_gil(self, monkeypatch): + config = { + "Py_DEBUG": False, + "EXT_SUFFIX": ".cpython-313t-x86_64-linux-gnu.so", + "WITH_PYMALLOC": 0, + "Py_NOGIL": 1, + } + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == ["cp313t"] + assert tags._generic_abi() == tags._cpython_abis((3, 13)) + def test__generic_abi_none(self, monkeypatch): config = {"EXT_SUFFIX": "..so"} monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) @@ -922,6 +941,7 @@ def test__generic_abi_old_windows(self, monkeypatch): "EXT_SUFFIX": ".pyd", "Py_DEBUG": 0, "WITH_PYMALLOC": 0, + "Py_NOGIL": 0, } monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) assert tags._generic_abi() == tags._cpython_abis(sys.version_info[:2])