From 607be83c6bfb4f5baf39e83182d77a6b8fe35c4b Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 24 Feb 2026 11:45:14 -0700 Subject: [PATCH 01/15] Add initial support for abi3.abi3t tag from PEP 803 --- pyproject.toml | 2 +- setuptools/command/bdist_wheel.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bbfa0dd0ca..eb97864ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ doc = [ ssl = [] certs = [] core = [ - "packaging>=24.2", + "packaging @ get+https://github.com/ngoldbaum/packaging@abi3.abi3t", "more_itertools>=8.8", "jaraco.text>=3.7", "importlib_metadata>=6; python_version < '3.10'", diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 3bdfa0b35a..3ed8c36a5a 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -281,11 +281,11 @@ def _validate_py_limited_api(self) -> None: if not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'") - if sysconfig.get_config_var("Py_GIL_DISABLED"): + if sysconfig.get_config_var("Py_GIL_DISABLED") and sys.version_info < (3, 15): raise ValueError( f"`py_limited_api={self.py_limited_api!r}` not supported. " - "`Py_LIMITED_API` is currently incompatible with " - "`Py_GIL_DISABLED`. " + "`Py_LIMITED_API` is incompatible with `Py_GIL_DISABLED` " + "on Python 3.14 and older. " "See https://github.com/python/cpython/issues/111506." ) @@ -345,7 +345,10 @@ def get_tag(self) -> tuple[str, str, str]: # We don't work on CPython 3.1, 3.0. if self.py_limited_api and (impl_name + impl_ver).startswith("cp3"): impl = self.py_limited_api - abi_tag = "abi3" + if sysconfig.get_config_var("Py_GIL_DISABLED"): + abi_tag = "abi3.abi3t" + else: + abi_tag = "abi3" else: abi_tag = str(get_abi_tag()).lower() tag = (impl, abi_tag, plat_name) @@ -354,7 +357,9 @@ def get_tag(self) -> tuple[str, str, str]: (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] assert tag in supported_tags, ( - f"would build wheel with unsupported tag {tag}" + f"would build wheel with unsupported tag {tag},\n" + "supported tags are:\n" + f"{supported_tags}" ) return tag From 2059833cfbe55c295c60be945d8f1c9cd5081e22 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 3 Mar 2026 11:13:06 -0700 Subject: [PATCH 02/15] fix typo --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb97864ad1..d9772cc987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ doc = [ ssl = [] certs = [] core = [ - "packaging @ get+https://github.com/ngoldbaum/packaging@abi3.abi3t", + "packaging @ git+https://github.com/ngoldbaum/packaging@abi3.abi3t", "more_itertools>=8.8", "jaraco.text>=3.7", "importlib_metadata>=6; python_version < '3.10'", From a2a203386f3e223fa51c5329010a4d961fa149ae Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 3 Mar 2026 11:14:49 -0700 Subject: [PATCH 03/15] revert error message change --- setuptools/command/bdist_wheel.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 3ed8c36a5a..0de37cfb89 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -357,9 +357,7 @@ def get_tag(self) -> tuple[str, str, str]: (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] assert tag in supported_tags, ( - f"would build wheel with unsupported tag {tag},\n" - "supported tags are:\n" - f"{supported_tags}" + f"would build wheel with unsupported tag {tag}" ) return tag From 92a4fd06f44bb20839c8ba0ce8daa85191e8acef Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 5 Mar 2026 12:15:40 -0700 Subject: [PATCH 04/15] support compressed ABI tags --- setuptools/command/bdist_wheel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 0de37cfb89..97121a8796 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -352,11 +352,12 @@ def get_tag(self) -> tuple[str, str, str]: else: abi_tag = str(get_abi_tag()).lower() tag = (impl, abi_tag, plat_name) + tag_str = "-".join(tag) # issue gh-374: allow overriding plat_name supported_tags = [ (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] - assert tag in supported_tags, ( + assert any((t.interpreter, t.abi, plat_name) in supported_tags for t in tags.parse_tag(tag_str)), ( f"would build wheel with unsupported tag {tag}" ) return tag From 0ef33f1ed6c80494b4c89d4e8a0b947e1d2c79a2 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 16 Mar 2026 09:51:57 -0600 Subject: [PATCH 05/15] refactor to satisfy linter --- setuptools/command/bdist_wheel.py | 33 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 97121a8796..832d22ba78 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -300,6 +300,21 @@ def wheel_dist_name(self) -> str: components.append(self.build_number) return "-".join(components) + @property + def abi_tag(self) -> str: + impl_name = tags.interpreter_name() + impl_ver = tags.interpreter_version() + impl = impl_name + impl_ver + # We don't work on CPython 3.1, 3.0. + if self.py_limited_api and impl.startswith("cp3"): + if sysconfig.get_config_var("Py_GIL_DISABLED"): + abi_tag = "abi3.abi3t" + else: + abi_tag = "abi3" + else: + abi_tag = str(get_abi_tag()).lower() + return abi_tag + def get_tag(self) -> tuple[str, str, str]: # bdist sets self.plat_name if unset, we should only use it for purepy # wheels if the user supplied it. @@ -342,24 +357,18 @@ def get_tag(self) -> tuple[str, str, str]: impl_name = tags.interpreter_name() impl_ver = tags.interpreter_version() impl = impl_name + impl_ver - # We don't work on CPython 3.1, 3.0. - if self.py_limited_api and (impl_name + impl_ver).startswith("cp3"): + abi_tag = self.abi_tag + if "abi3" in abi_tag: impl = self.py_limited_api - if sysconfig.get_config_var("Py_GIL_DISABLED"): - abi_tag = "abi3.abi3t" - else: - abi_tag = "abi3" - else: - abi_tag = str(get_abi_tag()).lower() tag = (impl, abi_tag, plat_name) - tag_str = "-".join(tag) # issue gh-374: allow overriding plat_name supported_tags = [ (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] - assert any((t.interpreter, t.abi, plat_name) in supported_tags for t in tags.parse_tag(tag_str)), ( - f"would build wheel with unsupported tag {tag}" - ) + assert any( + (t.interpreter, t.abi, plat_name) in supported_tags + for t in tags.parse_tag("-".join(tag)) + ), f"would build wheel with unsupported tag {tag}" return tag def run(self): From 5ba05a80260553829aa0890859bd2883a78878f0 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 16 Mar 2026 10:03:13 -0600 Subject: [PATCH 06/15] add assertion for pyright --- setuptools/command/bdist_wheel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 832d22ba78..dbd9dcbb10 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -359,6 +359,7 @@ def get_tag(self) -> tuple[str, str, str]: impl = impl_name + impl_ver abi_tag = self.abi_tag if "abi3" in abi_tag: + assert self.py_limited_api is not False impl = self.py_limited_api tag = (impl, abi_tag, plat_name) # issue gh-374: allow overriding plat_name From 3db6cfce0b6ad8c2188a913f533a8d39e5b6070a Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 2 Apr 2026 16:23:05 -0600 Subject: [PATCH 07/15] expand tests and improve coverage --- setuptools/command/bdist_wheel.py | 39 +++++++++++++++++----------- setuptools/tests/test_bdist_wheel.py | 24 ++++++++++++++--- setuptools/tests/test_build_ext.py | 28 +++++++++++++++----- 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index dbd9dcbb10..9a123d6f40 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -132,6 +132,19 @@ def safer_version(version: str) -> str: return safe_version(version).replace("-", "_") +def stable_abi_tag(impl_name, impl_version): + abi_tag = None + if impl_name == "cp" and impl_version[0] == '3': + if sysconfig.get_config_var("Py_GIL_DISABLED"): + # per PEP 803 these are possible on older Python versions + # but in practice these builds need cp315 or newer + abi_tag = "abi3.abi3t" + else: + # This is wrong on on CPython 3.1, 3.0 ¯\_(ツ)_/¯ + abi_tag = "abi3" + return abi_tag + + class bdist_wheel(Command): description = "create a wheel distribution" @@ -192,7 +205,7 @@ class bdist_wheel(Command): ( "py-limited-api=", None, - "Python tag (cp32|cp33|cpNN) for abi3 wheel tag [default: false]", + "Python tag (cp32|cp33|cpNN) for abi3 or abi3t ABI [default: false]", ), ( "dist-info-dir=", @@ -304,16 +317,13 @@ def wheel_dist_name(self) -> str: def abi_tag(self) -> str: impl_name = tags.interpreter_name() impl_ver = tags.interpreter_version() - impl = impl_name + impl_ver - # We don't work on CPython 3.1, 3.0. - if self.py_limited_api and impl.startswith("cp3"): - if sysconfig.get_config_var("Py_GIL_DISABLED"): - abi_tag = "abi3.abi3t" - else: - abi_tag = "abi3" - else: - abi_tag = str(get_abi_tag()).lower() - return abi_tag + tag = None + if self.py_limited_api: + tag = stable_abi_tag(impl_name, impl_ver) + if tag is None: + # not a stable ABI build, use version-specific ABI tag + tag = str(get_abi_tag()).lower() + return tag def get_tag(self) -> tuple[str, str, str]: # bdist sets self.plat_name if unset, we should only use it for purepy @@ -366,10 +376,9 @@ def get_tag(self) -> tuple[str, str, str]: supported_tags = [ (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] - assert any( - (t.interpreter, t.abi, plat_name) in supported_tags - for t in tags.parse_tag("-".join(tag)) - ), f"would build wheel with unsupported tag {tag}" + assert tag in supported_tags, ( + f"would build wheel with unsupported tag {tag}" + ) return tag def run(self): diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index 68cc0c4d36..664e7d4bb9 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -18,7 +18,7 @@ from packaging import tags import setuptools -from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag +from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag, stable_abi_tag from setuptools.dist import Distribution from setuptools.warnings import SetuptoolsDeprecationWarning @@ -408,6 +408,7 @@ def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path): EXTENSION_EXAMPLE = """\ +#define Py_LIMITED_API 0x03020000 #include static PyMethodDef methods[] = { @@ -435,7 +436,13 @@ def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path): name="extension.dist", version="0.1", description="A testing distribution \N{SNOWMAN}", - ext_modules=[Extension(name="extension", sources=["extension.c"])], + ext_modules=[ + Extension( + name="extension", + sources=["extension.c"], + py_limited_api=True + ) + ], ) """ @@ -443,7 +450,7 @@ def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path): @pytest.mark.filterwarnings( "once:Config variable '.*' is unset.*, Python ABI tag may be incorrect" ) -def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory): +def test_limited_api(monkeypatch, tmp_path, tmp_path_factory): """Test that building a binary wheel with the limited ABI works.""" source_dir = tmp_path_factory.mktemp("extension_dist") (source_dir / "setup.py").write_text(EXTENSION_SETUPPY, encoding="utf-8") @@ -451,7 +458,9 @@ def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory): build_dir = tmp_path.joinpath("build") dist_dir = tmp_path.joinpath("dist") monkeypatch.chdir(source_dir) - bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run() + bdist_wheel_cmd( + bdist_dir=str(build_dir), dist_dir=str(dist_dir), py_limited_api="cp32" + ).run() def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path): @@ -538,6 +547,13 @@ def test_get_abi_tag_fallback(monkeypatch): assert get_abi_tag() == "unknown_python_310" +def test_stable_abi_tag(monkeypatch): + monkeypatch.setattr(sysconfig, "get_config_var", lambda x: 0) + assert stable_abi_tag("cp", "315") == "abi3" + monkeypatch.setattr(sysconfig, "get_config_var", lambda x: '1') + assert stable_abi_tag("cp", "315") == "abi3.abi3t" + + def test_platform_with_space(dummy_dist, monkeypatch): """Ensure building on platforms with a space in the name succeed.""" monkeypatch.chdir(dummy_dist) diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index c7b60ac32f..a064ed648e 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -7,6 +7,7 @@ import pytest from jaraco import path +from setuptools.command import build_ext as build_ext_mod from setuptools.command.build_ext import build_ext, get_abi3_suffix from setuptools.dist import Distribution from setuptools.errors import CompileError @@ -19,6 +20,10 @@ from distutils.sysconfig import get_config_var IS_PYPY = '__pypy__' in sys.builtin_module_names +# from a Mac running Python 3.14 +ABI3_EXT_SUFFIXES = ['cpython-314-darwin.so', 'abi3.so', 'so'] +# from a Mac running Python 3.15t +ABI3T_EXT_SUFFIXES = ['.cpython-315t-darwin.so', '.abi3t.so', '.so'] class TestBuildExt: @@ -35,11 +40,7 @@ def test_get_ext_filename(self): wanted = orig.build_ext.get_ext_filename(cmd, 'foo') assert res == wanted - def test_abi3_filename(self): - """ - Filename needs to be loadable by several versions - of Python 3 if 'is_abi3' is truthy on Extension() - """ + def check_stable_abi(self, abi_name): print(get_abi3_suffix()) extension = Extension('spam.eggs', ['eggs.c'], py_limited_api=True) @@ -54,7 +55,22 @@ def test_abi3_filename(self): elif sys.platform == 'win32': assert res.endswith('eggs.pyd') else: - assert 'abi3' in res + assert abi_name in res + + @pytest.mark.parametrize( + ('extension_name', 'suffixes'), + [ + ("abi3", ABI3_EXT_SUFFIXES), + ("abi3t", ABI3T_EXT_SUFFIXES), + ], + ) + def test_stable_abi_filename(self, monkeypatch, extension_name, suffixes): + """ + Test that extension filename is correct if 'py_limited_abi' is + truthy on Extension() + """ + monkeypatch.setattr(build_ext_mod, "EXTENSION_SUFFIXES", suffixes) + self.check_stable_abi(extension_name) def test_ext_suffix_override(self): """ From 62fa64b199d00fd5de85cc848fdc72ccf2d1d56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 8 Apr 2026 19:20:29 +0200 Subject: [PATCH 08/15] fix asserting for abi3.abi3t tag being supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- setuptools/command/bdist_wheel.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 9a123d6f40..aa480f02f9 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -376,9 +376,12 @@ def get_tag(self) -> tuple[str, str, str]: supported_tags = [ (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() ] - assert tag in supported_tags, ( - f"would build wheel with unsupported tag {tag}" - ) + # abi_tag can contain multiple (e.g. "abi3.abi3t") tags + # only one of them will be supported + assert any( + (impl, exploded_abi_tag, plat_name) in supported_tags + for exploded_abi_tag in abi_tag.split(".") + ), f"would build wheel with unsupported tag {tag}" return tag def run(self): From 62086f30a70892e1e9044dc5f63852b84484d5f4 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 09:50:49 -0600 Subject: [PATCH 09/15] apply suggestions from Ralf --- setuptools/command/bdist_wheel.py | 1 - setuptools/tests/test_build_ext.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index aa480f02f9..f032bfc091 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -140,7 +140,6 @@ def stable_abi_tag(impl_name, impl_version): # but in practice these builds need cp315 or newer abi_tag = "abi3.abi3t" else: - # This is wrong on on CPython 3.1, 3.0 ¯\_(ツ)_/¯ abi_tag = "abi3" return abi_tag diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index a064ed648e..a9d25f2419 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -21,7 +21,7 @@ IS_PYPY = '__pypy__' in sys.builtin_module_names # from a Mac running Python 3.14 -ABI3_EXT_SUFFIXES = ['cpython-314-darwin.so', 'abi3.so', 'so'] +ABI3_EXT_SUFFIXES = ['.cpython-314-darwin.so', '.abi3.so', '.so'] # from a Mac running Python 3.15t ABI3T_EXT_SUFFIXES = ['.cpython-315t-darwin.so', '.abi3t.so', '.so'] From 1fd4f35d7b65e0bf9b9840e8ee85544b4a0fb6d6 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 12:58:43 -0600 Subject: [PATCH 10/15] fix windows tests --- setuptools/tests/test_build_ext.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index a9d25f2419..29c5e5e05d 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -69,7 +69,8 @@ def test_stable_abi_filename(self, monkeypatch, extension_name, suffixes): Test that extension filename is correct if 'py_limited_abi' is truthy on Extension() """ - monkeypatch.setattr(build_ext_mod, "EXTENSION_SUFFIXES", suffixes) + if sys.platform != 'win32': + monkeypatch.setattr(build_ext_mod, "EXTENSION_SUFFIXES", suffixes) self.check_stable_abi(extension_name) def test_ext_suffix_override(self): From 70fc47fa1ddd5129790a2cf5ffb7bdc5c153f4f9 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 12:59:06 -0600 Subject: [PATCH 11/15] refactor to use tags.parse_tags --- setuptools/command/bdist_wheel.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index f032bfc091..ebde40532a 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -16,6 +16,7 @@ from collections.abc import Iterable, Sequence from email.generator import BytesGenerator from glob import iglob +from itertools import chain from typing import Literal, cast from zipfile import ZIP_DEFLATED, ZIP_STORED @@ -30,6 +31,8 @@ from distutils import log +flatten = chain.from_iterable + def safe_version(version: str) -> str: """ @@ -371,16 +374,17 @@ def get_tag(self) -> tuple[str, str, str]: assert self.py_limited_api is not False impl = self.py_limited_api tag = (impl, abi_tag, plat_name) + possible_tags = tags.parse_tag("-".join(tag)) # issue gh-374: allow overriding plat_name - supported_tags = [ - (t.interpreter, t.abi, plat_name) for t in tags.sys_tags() - ] + sys_tags = ( + "-".join((t.interpreter, t.abi, plat_name)) for t in tags.sys_tags() + ) + supported_tags = flatten(tags.parse_tag(t) for t in sys_tags) # abi_tag can contain multiple (e.g. "abi3.abi3t") tags # only one of them will be supported - assert any( - (impl, exploded_abi_tag, plat_name) in supported_tags - for exploded_abi_tag in abi_tag.split(".") - ), f"would build wheel with unsupported tag {tag}" + assert any(t in supported_tags for t in possible_tags), ( + f"would build wheel with unsupported tag {tag}" + ) return tag def run(self): From 2da147ef6271fc7d0ba46b65eb244798fe358642 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 13:14:46 -0600 Subject: [PATCH 12/15] add newsfragment --- newsfragments/5193.feature.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 newsfragments/5193.feature.rst diff --git a/newsfragments/5193.feature.rst b/newsfragments/5193.feature.rst new file mode 100644 index 0000000000..fd2f61c013 --- /dev/null +++ b/newsfragments/5193.feature.rst @@ -0,0 +1,6 @@ +Added support for building abi3t extensions and abi3.abi3t wheels on Python 3.15 +and newer. See `PEP 803 `_ and the Python 3.15 `"What's New" entry`_ +for more details. + +.. _PEP803: https://peps.python.org/pep-0803/ +.. _whatsnew: https://docs.python.org/3.15/whatsnew/3.15.html#pep-803-abi3t-stable-abi-for-free-threaded-builds From 01eb17f7a247c3805a68fdcc8d18bc2340decfec Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 13:15:53 -0600 Subject: [PATCH 13/15] remove packaging pin --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d9772cc987..bbfa0dd0ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ doc = [ ssl = [] certs = [] core = [ - "packaging @ git+https://github.com/ngoldbaum/packaging@abi3.abi3t", + "packaging>=24.2", "more_itertools>=8.8", "jaraco.text>=3.7", "importlib_metadata>=6; python_version < '3.10'", From 39d8e0403e85f137551e7f2946d06a3853cf2dab Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Wed, 22 Apr 2026 13:22:28 -0600 Subject: [PATCH 14/15] fix sphinx warning --- newsfragments/5193.feature.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/newsfragments/5193.feature.rst b/newsfragments/5193.feature.rst index fd2f61c013..bec9a7613e 100644 --- a/newsfragments/5193.feature.rst +++ b/newsfragments/5193.feature.rst @@ -1,6 +1,6 @@ Added support for building abi3t extensions and abi3.abi3t wheels on Python 3.15 -and newer. See `PEP 803 `_ and the Python 3.15 `"What's New" entry`_ -for more details. +and newer. See `PEP 803 `_ and the Python 3.15 `"What's New" entry +`_ for more details. .. _PEP803: https://peps.python.org/pep-0803/ .. _whatsnew: https://docs.python.org/3.15/whatsnew/3.15.html#pep-803-abi3t-stable-abi-for-free-threaded-builds From 3bfee565fa6a01e510c777fb7eb72f3ddcd055b8 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 27 Apr 2026 15:56:13 -0600 Subject: [PATCH 15/15] fix generator exhaustion issue --- setuptools/command/bdist_wheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index ebde40532a..fc2c532a38 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -379,7 +379,7 @@ def get_tag(self) -> tuple[str, str, str]: sys_tags = ( "-".join((t.interpreter, t.abi, plat_name)) for t in tags.sys_tags() ) - supported_tags = flatten(tags.parse_tag(t) for t in sys_tags) + supported_tags = list(flatten(tags.parse_tag(t) for t in sys_tags)) # abi_tag can contain multiple (e.g. "abi3.abi3t") tags # only one of them will be supported assert any(t in supported_tags for t in possible_tags), (