From 22b811845e6488ba5a3f808e2fabc133007718a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 16:15:22 +0200 Subject: [PATCH 01/14] ENH: add support for PEP 803 `abi3t` with Python 3.15.0b2+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tag/extension support and initial tests for `abi3t` as specified in PEP 803 and implemented in Python 3.15.0b2. This is currently limited to building `abi3t` extensions from freethreading builds of Python, since Meson does not support forcing `abi3t` builds explicitly right now. See https://github.com/mesonbuild/meson/issues/15637 for the relevant discussion. The additional test case utilizes the new API introduced in PEP 793 and PEP 820, and therefore requires Python 3.15.0b2. The relevant test tests wheel correctness both with GIL-enabled and free-threading builds of Python 3.15.0b2. The behavior for older versions of Python remains unchanged. Signed-off-by: Michał Górny --- mesonpy/__init__.py | 15 ++++++--- tests/packages/limited-api-ft/meson.build | 14 +++++++++ tests/packages/limited-api-ft/module.c | 33 ++++++++++++++++++++ tests/packages/limited-api-ft/pyproject.toml | 10 ++++++ tests/test_tags.py | 8 ++++- tests/test_wheel.py | 11 +++++++ 6 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 tests/packages/limited-api-ft/meson.build create mode 100644 tests/packages/limited-api-ft/module.c create mode 100644 tests/packages/limited-api-ft/pyproject.toml diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 7325d23f..44401f24 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -426,6 +426,13 @@ def _stable_abi(self) -> Optional[str]: # not use the stable ABI filename suffix and wheels should not # be tagged with the abi3 tag. if self._limited_api and '__pypy__' not in sys.builtin_module_names: + # On free-threaded Python 3.15.0b2+, we expect to be + # building 'abi3t' wheels for the time being. In the future + # we will want an option to target 'abi3t' from GIL-enabled + # Python too. + is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info >= (3, 15, 0, 'beta', 2) + expected_abi = 'abi3t' if is_abi3t else 'abi3' + # Verify stable ABI compatibility: examine files installed # in {platlib} that look like extension modules, and raise # an exception if any of them has a Python version @@ -434,11 +441,11 @@ def _stable_abi(self) -> Optional[str]: match = _EXTENSION_SUFFIX_REGEX.match(entry.dst.name) if match: abi = match.group('abi') - if abi is not None and abi != 'abi3': + if abi is not None and abi != expected_abi: raise BuildError( f'The package declares compatibility with Python limited API but extension ' f'module {os.fspath(entry.dst)!r} is tagged for a specific Python version.') - return 'abi3' + return 'abi3.abi3t' if is_abi3t else 'abi3' return None def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, destination: pathlib.Path) -> None: @@ -874,9 +881,9 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')): + if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info < (3, 15, 0, 'beta', 2): raise BuildError( - 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython. ' + 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython < 3.15.0b2. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') # Shared library support on Windows requires collaboration diff --git a/tests/packages/limited-api-ft/meson.build b/tests/packages/limited-api-ft/meson.build new file mode 100644 index 00000000..297bf16f --- /dev/null +++ b/tests/packages/limited-api-ft/meson.build @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023-2026 The meson-python developers +# +# SPDX-License-Identifier: MIT + +project('limited-api-ft', 'c', version: '1.0.0', meson_version: '>= 1.3') + +py = import('python').find_installation(pure: false) + +py.extension_module( + 'module', + 'module.c', + limited_api: '3.15', + install: true, +) diff --git a/tests/packages/limited-api-ft/module.c b/tests/packages/limited-api-ft/module.c new file mode 100644 index 00000000..3bd28b7f --- /dev/null +++ b/tests/packages/limited-api-ft/module.c @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023-2026 The meson-python developers +// +// SPDX-License-Identifier: MIT + +#include + +static PyObject* add(PyObject *self, PyObject *args) { + int a, b; + + if (!PyArg_ParseTuple(args, "ii", &a, &b)) + return NULL; + + return PyLong_FromLong(a + b); +} + +static PyMethodDef methods[] = { + {"add", add, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +PyABIInfo_VAR(abi_info); + +static PySlot module_slots[] = { + PySlot_STATIC_DATA(Py_mod_name, "plat"), + PySlot_STATIC_DATA(Py_mod_methods, methods), + PySlot_STATIC_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_END, +}; + +PyMODEXPORT_FUNC PyModExport_module(void) { + return module_slots; +} diff --git a/tests/packages/limited-api-ft/pyproject.toml b/tests/packages/limited-api-ft/pyproject.toml new file mode 100644 index 00000000..26631d4e --- /dev/null +++ b/tests/packages/limited-api-ft/pyproject.toml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2023-2026 The meson-python developers +# +# SPDX-License-Identifier: MIT + +[build-system] +build-backend = 'mesonpy' +requires = ['meson-python'] + +[tool.meson-python] +limited-api = true diff --git a/tests/test_tags.py b/tests/test_tags.py index d2bbd057..3c5b39e8 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -19,6 +19,7 @@ import mesonpy._tags from .conftest import adjust_packaging_platform_tag +from .test_wheel import NOGIL_BUILD # Test against the wheel tag generated by packaging module. @@ -130,7 +131,12 @@ def test_tag_stable_abi(): 'platlib': [f'extension{ABI3SUFFIX}'], }, limited_api=True) # PyPy does not support the stable ABI. - abi = 'abi3' if '__pypy__' not in sys.builtin_module_names else ABI + if '__pypy__' in sys.builtin_module_names: + abi = ABI + elif NOGIL_BUILD and sys.version_info >= (3, 15, 0, 'beta', 2): + abi = 'abi3.abi3t' + else: + abi = 'abi3' assert str(builder.tag) == f'{INTERPRETER}-{abi}-{PLATFORM}' diff --git a/tests/test_wheel.py b/tests/test_wheel.py index e3c93559..d8f2f81e 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -412,3 +412,14 @@ def test_cmake_subproject(wheel_cmake_subproject): f'.cmake_subproject.mesonpy.libs/libcmaketest{LIB_SUFFIX}', f'cmakesubproject{EXT_SUFFIX}', } + + +# Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. +@pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') +@pytest.mark.skipif(sys.version_info < (3, 15, 0, 'beta', 2), reason='Requires PEP 820 API, present since Python 3.15.0b2') +def test_limited_api_ft(wheel_limited_api_ft): + artifact = wheel.wheelfile.WheelFile(wheel_limited_api_ft) + name = artifact.parsed_filename + assert name.group('pyver') == INTERPRETER + assert name.group('abi') == 'abi3.abi3t' if NOGIL_BUILD else 'abi3' + assert name.group('plat') == PLATFORM From 0d43c6541ff962e45739654d48c9932845e6b052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 17:14:28 +0200 Subject: [PATCH 02/14] Move the version check to a helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 9 +++++++-- tests/test_tags.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 44401f24..afa6f752 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -329,6 +329,11 @@ def _is_native(file: Path) -> bool: return f.read(4) == b'\x7fELF' # ELF +def _is_abi3t_capable() -> bool: + """Return true if Python has abi3t support.""" + return sys.version_info >= (3, 15, 0, 'beta', 2) + + @dataclasses.dataclass class _WheelBuilder(): """Helper class to build wheels from projects.""" @@ -430,7 +435,7 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info >= (3, 15, 0, 'beta', 2) + is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() expected_abi = 'abi3t' if is_abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed @@ -881,7 +886,7 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info < (3, 15, 0, 'beta', 2): + if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): raise BuildError( 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython < 3.15.0b2. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') diff --git a/tests/test_tags.py b/tests/test_tags.py index 3c5b39e8..87325021 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -133,7 +133,7 @@ def test_tag_stable_abi(): # PyPy does not support the stable ABI. if '__pypy__' in sys.builtin_module_names: abi = ABI - elif NOGIL_BUILD and sys.version_info >= (3, 15, 0, 'beta', 2): + elif NOGIL_BUILD and mesonpy._is_abi3t_capable(): abi = 'abi3.abi3t' else: abi = 'abi3' From a23fe90a0c9e8a9368dbffaf32a5bf4f02976ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 17:25:15 +0200 Subject: [PATCH 03/14] Replace the inline free-threaded checks with a function as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 9 +++++++-- tests/test_editable.py | 8 ++++---- tests/test_tags.py | 3 +-- tests/test_wheel.py | 8 +++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index afa6f752..dca83cf9 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -334,6 +334,11 @@ def _is_abi3t_capable() -> bool: return sys.version_info >= (3, 15, 0, 'beta', 2) +def _is_free_threaded() -> bool: + """Return true if we are running a free-threaded (no-GIL) Python.""" + return sysconfig.get_config_var('Py_GIL_DISABLED') + + @dataclasses.dataclass class _WheelBuilder(): """Helper class to build wheels from projects.""" @@ -435,7 +440,7 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() + is_abi3t = _is_free_threaded() and _is_abi3t_capable() expected_abi = 'abi3t' if is_abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed @@ -886,7 +891,7 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): + if self._limited_api and _is_free_threaded() and not _is_abi3t_capable(): raise BuildError( 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython < 3.15.0b2. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') diff --git a/tests/test_editable.py b/tests/test_editable.py index 92656ec6..25a60be4 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -18,7 +18,7 @@ from mesonpy import _editable -from .test_wheel import EXT_SUFFIX, NOGIL_BUILD +from .test_wheel import EXT_SUFFIX def find_cython_version(): @@ -75,7 +75,7 @@ def test_collect(package_complex): assert tree['complex']['more']['__init__.py'] == os.path.join(root, 'complex', 'more', '__init__.py') -@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_mesonpy_meta_finder(package_complex, tmp_path): # build a package in a temporary directory @@ -210,7 +210,7 @@ def test_editble_reentrant(venv, editable_imports_itself_during_build): path.write_text(code) -@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_editable_pkgutils_walk_packages(package_complex, tmp_path): # build a package in a temporary directory @@ -296,7 +296,7 @@ def test_editable_rebuild(package_purelib_and_platlib, tmp_path, verbose, args): sys.modules.pop('pure', None) -@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_editable_verbose(venv, package_complex, editable_complex, monkeypatch): monkeypatch.setenv('MESONPY_EDITABLE_VERBOSE', '1') diff --git a/tests/test_tags.py b/tests/test_tags.py index 87325021..31346c3f 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -19,7 +19,6 @@ import mesonpy._tags from .conftest import adjust_packaging_platform_tag -from .test_wheel import NOGIL_BUILD # Test against the wheel tag generated by packaging module. @@ -133,7 +132,7 @@ def test_tag_stable_abi(): # PyPy does not support the stable ABI. if '__pypy__' in sys.builtin_module_names: abi = ABI - elif NOGIL_BUILD and mesonpy._is_abi3t_capable(): + elif mesonpy._is_free_threaded() and mesonpy._is_abi3t_capable(): abi = 'abi3.abi3t' else: abi = 'abi3' diff --git a/tests/test_wheel.py b/tests/test_wheel.py index d8f2f81e..91604b51 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -35,8 +35,6 @@ 'win32': '.dll', }.get(sys.platform, '.so') -NOGIL_BUILD = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) - # Test against the wheel tag generated by packaging module. tag = next(packaging.tags.sys_tags()) ABI = tag.abi @@ -321,7 +319,7 @@ def test_skip_subprojects(package_subproject, tmp_path, arg): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. @pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') -@pytest.mark.skipif(NOGIL_BUILD, reason='Free-threaded CPython does not support the limited API') +@pytest.mark.skipif(mesonpy._is_free_threaded(), reason='Free-threaded CPython does not support the limited API') @pytest.mark.xfail('__pypy__' in sys.builtin_module_names, reason='PyPy does not support the abi3 platform tag for wheels') def test_limited_api(wheel_limited_api): artifact = wheel.wheelfile.WheelFile(wheel_limited_api) @@ -333,7 +331,7 @@ def test_limited_api(wheel_limited_api): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. @pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') -@pytest.mark.skipif(NOGIL_BUILD, reason='Free-threaded CPython does not support the limited API') +@pytest.mark.skipif(mesonpy._is_free_threaded(), reason='Free-threaded CPython does not support the limited API') @pytest.mark.xfail('__pypy__' in sys.builtin_module_names, reason='PyPy does not use special modules suffix for stable ABI') def test_limited_api_bad(package_limited_api, tmp_path): with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '): @@ -421,5 +419,5 @@ def test_limited_api_ft(wheel_limited_api_ft): artifact = wheel.wheelfile.WheelFile(wheel_limited_api_ft) name = artifact.parsed_filename assert name.group('pyver') == INTERPRETER - assert name.group('abi') == 'abi3.abi3t' if NOGIL_BUILD else 'abi3' + assert name.group('abi') == 'abi3.abi3t' if mesonpy._is_free_threaded() else 'abi3' assert name.group('plat') == PLATFORM From bf0fb07bf3e7d845de69ad933e858c818bbba01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 19:33:35 +0200 Subject: [PATCH 04/14] Apply suggestions from code review Co-authored-by: Daniele Nicolodi --- mesonpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index dca83cf9..591e39b5 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -893,7 +893,7 @@ def __init__( if self._limited_api and _is_free_threaded() and not _is_abi3t_capable(): raise BuildError( - 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython < 3.15.0b2. ' + 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version 3.15. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') # Shared library support on Windows requires collaboration From c20b4708a2b6220d4d03aeb11a78a4b643c10952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 19:33:59 +0200 Subject: [PATCH 05/14] Revert "Replace the inline free-threaded checks with a function as well" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a23fe90a0c9e8a9368dbffaf32a5bf4f02976ec9. Signed-off-by: Michał Górny --- mesonpy/__init__.py | 9 ++------- tests/test_editable.py | 8 ++++---- tests/test_tags.py | 3 ++- tests/test_wheel.py | 8 +++++--- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 591e39b5..6d81c09e 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -334,11 +334,6 @@ def _is_abi3t_capable() -> bool: return sys.version_info >= (3, 15, 0, 'beta', 2) -def _is_free_threaded() -> bool: - """Return true if we are running a free-threaded (no-GIL) Python.""" - return sysconfig.get_config_var('Py_GIL_DISABLED') - - @dataclasses.dataclass class _WheelBuilder(): """Helper class to build wheels from projects.""" @@ -440,7 +435,7 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - is_abi3t = _is_free_threaded() and _is_abi3t_capable() + is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() expected_abi = 'abi3t' if is_abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed @@ -891,7 +886,7 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and _is_free_threaded() and not _is_abi3t_capable(): + if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): raise BuildError( 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version 3.15. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') diff --git a/tests/test_editable.py b/tests/test_editable.py index 25a60be4..92656ec6 100644 --- a/tests/test_editable.py +++ b/tests/test_editable.py @@ -18,7 +18,7 @@ from mesonpy import _editable -from .test_wheel import EXT_SUFFIX +from .test_wheel import EXT_SUFFIX, NOGIL_BUILD def find_cython_version(): @@ -75,7 +75,7 @@ def test_collect(package_complex): assert tree['complex']['more']['__init__.py'] == os.path.join(root, 'complex', 'more', '__init__.py') -@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_mesonpy_meta_finder(package_complex, tmp_path): # build a package in a temporary directory @@ -210,7 +210,7 @@ def test_editble_reentrant(venv, editable_imports_itself_during_build): path.write_text(code) -@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_editable_pkgutils_walk_packages(package_complex, tmp_path): # build a package in a temporary directory @@ -296,7 +296,7 @@ def test_editable_rebuild(package_purelib_and_platlib, tmp_path, verbose, args): sys.modules.pop('pure', None) -@pytest.mark.skipif(mesonpy._is_free_threaded() and CYTHON_VERSION < (3, 1, 0), +@pytest.mark.skipif(NOGIL_BUILD and CYTHON_VERSION < (3, 1, 0), reason='Cython version too old, no free-threaded CPython support') def test_editable_verbose(venv, package_complex, editable_complex, monkeypatch): monkeypatch.setenv('MESONPY_EDITABLE_VERBOSE', '1') diff --git a/tests/test_tags.py b/tests/test_tags.py index 31346c3f..87325021 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -19,6 +19,7 @@ import mesonpy._tags from .conftest import adjust_packaging_platform_tag +from .test_wheel import NOGIL_BUILD # Test against the wheel tag generated by packaging module. @@ -132,7 +133,7 @@ def test_tag_stable_abi(): # PyPy does not support the stable ABI. if '__pypy__' in sys.builtin_module_names: abi = ABI - elif mesonpy._is_free_threaded() and mesonpy._is_abi3t_capable(): + elif NOGIL_BUILD and mesonpy._is_abi3t_capable(): abi = 'abi3.abi3t' else: abi = 'abi3' diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 91604b51..d8f2f81e 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -35,6 +35,8 @@ 'win32': '.dll', }.get(sys.platform, '.so') +NOGIL_BUILD = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) + # Test against the wheel tag generated by packaging module. tag = next(packaging.tags.sys_tags()) ABI = tag.abi @@ -319,7 +321,7 @@ def test_skip_subprojects(package_subproject, tmp_path, arg): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. @pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') -@pytest.mark.skipif(mesonpy._is_free_threaded(), reason='Free-threaded CPython does not support the limited API') +@pytest.mark.skipif(NOGIL_BUILD, reason='Free-threaded CPython does not support the limited API') @pytest.mark.xfail('__pypy__' in sys.builtin_module_names, reason='PyPy does not support the abi3 platform tag for wheels') def test_limited_api(wheel_limited_api): artifact = wheel.wheelfile.WheelFile(wheel_limited_api) @@ -331,7 +333,7 @@ def test_limited_api(wheel_limited_api): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. @pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') -@pytest.mark.skipif(mesonpy._is_free_threaded(), reason='Free-threaded CPython does not support the limited API') +@pytest.mark.skipif(NOGIL_BUILD, reason='Free-threaded CPython does not support the limited API') @pytest.mark.xfail('__pypy__' in sys.builtin_module_names, reason='PyPy does not use special modules suffix for stable ABI') def test_limited_api_bad(package_limited_api, tmp_path): with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '): @@ -419,5 +421,5 @@ def test_limited_api_ft(wheel_limited_api_ft): artifact = wheel.wheelfile.WheelFile(wheel_limited_api_ft) name = artifact.parsed_filename assert name.group('pyver') == INTERPRETER - assert name.group('abi') == 'abi3.abi3t' if mesonpy._is_free_threaded() else 'abi3' + assert name.group('abi') == 'abi3.abi3t' if NOGIL_BUILD else 'abi3' assert name.group('plat') == PLATFORM From 27e23c8cfd50bfd9b570217f93b8829fb73948b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 19:35:31 +0200 Subject: [PATCH 06/14] Rename the variable to `abi3t` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 6d81c09e..d6969807 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -435,8 +435,8 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - is_abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() - expected_abi = 'abi3t' if is_abi3t else 'abi3' + abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() + expected_abi = 'abi3t' if abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed # in {platlib} that look like extension modules, and raise @@ -450,7 +450,7 @@ def _stable_abi(self) -> Optional[str]: raise BuildError( f'The package declares compatibility with Python limited API but extension ' f'module {os.fspath(entry.dst)!r} is tagged for a specific Python version.') - return 'abi3.abi3t' if is_abi3t else 'abi3' + return 'abi3.abi3t' if abi3t else 'abi3' return None def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, destination: pathlib.Path) -> None: From 1a16140b05eeeaf5cd675646347d7471f738e99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 23 Jun 2026 19:36:51 +0200 Subject: [PATCH 07/14] Wrap the long text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index d6969807..091eaddf 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -888,7 +888,8 @@ def __init__( if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): raise BuildError( - 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version 3.15. ' + 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before ' + 'version 3.15. ' 'The "python.allow_limited_api" Meson build option may be used to override the package default.') # Shared library support on Windows requires collaboration From 66ebdd1729161f8691bdea22a20aad9f34fd9991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 10:35:42 +0200 Subject: [PATCH 08/14] Rewrap message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 091eaddf..37fcd857 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -888,9 +888,8 @@ def __init__( if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): raise BuildError( - 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before ' - 'version 3.15. ' - 'The "python.allow_limited_api" Meson build option may be used to override the package default.') + 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version ' + '3.15. The "python.allow_limited_api" Meson build option may be used to override the package default.') # Shared library support on Windows requires collaboration # from the package, make sure the developers acknowledge this. From f18d1ca3b49c6baedeefa8de7d05ae256bea50ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 13:43:36 +0200 Subject: [PATCH 09/14] Revert "Move the version check to a helper function" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0d43c6541ff962e45739654d48c9932845e6b052. Signed-off-by: Michał Górny --- mesonpy/__init__.py | 9 ++------- tests/test_tags.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 37fcd857..72da2233 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -329,11 +329,6 @@ def _is_native(file: Path) -> bool: return f.read(4) == b'\x7fELF' # ELF -def _is_abi3t_capable() -> bool: - """Return true if Python has abi3t support.""" - return sys.version_info >= (3, 15, 0, 'beta', 2) - - @dataclasses.dataclass class _WheelBuilder(): """Helper class to build wheels from projects.""" @@ -435,7 +430,7 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and _is_abi3t_capable() + abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info >= (3, 15, 0, 'beta', 2) expected_abi = 'abi3t' if abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed @@ -886,7 +881,7 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and not _is_abi3t_capable(): + if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info < (3, 15, 0, 'beta', 2): raise BuildError( 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version ' '3.15. The "python.allow_limited_api" Meson build option may be used to override the package default.') diff --git a/tests/test_tags.py b/tests/test_tags.py index 87325021..3c5b39e8 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -133,7 +133,7 @@ def test_tag_stable_abi(): # PyPy does not support the stable ABI. if '__pypy__' in sys.builtin_module_names: abi = ABI - elif NOGIL_BUILD and mesonpy._is_abi3t_capable(): + elif NOGIL_BUILD and sys.version_info >= (3, 15, 0, 'beta', 2): abi = 'abi3.abi3t' else: abi = 'abi3' From e56474b022286c06dd7c37e3e6da050da5a60c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 13:44:54 +0200 Subject: [PATCH 10/14] Shorten the version check to just (3, 15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- mesonpy/__init__.py | 4 ++-- tests/test_tags.py | 2 +- tests/test_wheel.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 72da2233..3ca554af 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -430,7 +430,7 @@ def _stable_abi(self) -> Optional[str]: # building 'abi3t' wheels for the time being. In the future # we will want an option to target 'abi3t' from GIL-enabled # Python too. - abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info >= (3, 15, 0, 'beta', 2) + abi3t = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info >= (3, 15) expected_abi = 'abi3t' if abi3t else 'abi3' # Verify stable ABI compatibility: examine files installed @@ -881,7 +881,7 @@ def __init__( if not allow_limited_api: self._limited_api = False - if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info < (3, 15, 0, 'beta', 2): + if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')) and sys.version_info < (3, 15): raise BuildError( 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython before version ' '3.15. The "python.allow_limited_api" Meson build option may be used to override the package default.') diff --git a/tests/test_tags.py b/tests/test_tags.py index 3c5b39e8..34380aa5 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -133,7 +133,7 @@ def test_tag_stable_abi(): # PyPy does not support the stable ABI. if '__pypy__' in sys.builtin_module_names: abi = ABI - elif NOGIL_BUILD and sys.version_info >= (3, 15, 0, 'beta', 2): + elif NOGIL_BUILD and sys.version_info >= (3, 15): abi = 'abi3.abi3t' else: abi = 'abi3' diff --git a/tests/test_wheel.py b/tests/test_wheel.py index d8f2f81e..1f40224e 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -416,7 +416,7 @@ def test_cmake_subproject(wheel_cmake_subproject): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. @pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') -@pytest.mark.skipif(sys.version_info < (3, 15, 0, 'beta', 2), reason='Requires PEP 820 API, present since Python 3.15.0b2') +@pytest.mark.skipif(sys.version_info < (3, 15), reason='Requires PEP 820 API, present since Python 3.15') def test_limited_api_ft(wheel_limited_api_ft): artifact = wheel.wheelfile.WheelFile(wheel_limited_api_ft) name = artifact.parsed_filename From ede9744994535985fbd2935f73b6b001c655e77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 18:24:39 +0200 Subject: [PATCH 11/14] Correct `PySlot_DATA` usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/packages/limited-api-ft/module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/packages/limited-api-ft/module.c b/tests/packages/limited-api-ft/module.c index 3bd28b7f..8a661f1b 100644 --- a/tests/packages/limited-api-ft/module.c +++ b/tests/packages/limited-api-ft/module.c @@ -23,8 +23,8 @@ PyABIInfo_VAR(abi_info); static PySlot module_slots[] = { PySlot_STATIC_DATA(Py_mod_name, "plat"), PySlot_STATIC_DATA(Py_mod_methods, methods), - PySlot_STATIC_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), - PySlot_STATIC_DATA(Py_mod_abi, &abi_info), + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), + PySlot_DATA(Py_mod_abi, &abi_info), PySlot_END, }; From a0aa5e1c783eb115cc4a463d35247160227308e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 18:41:38 +0200 Subject: [PATCH 12/14] Add a comment to `get_abi3_suffix()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/test_tags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_tags.py b/tests/test_tags.py index 34380aa5..29640b39 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -30,6 +30,9 @@ def get_abi3_suffix(): + # EXTENSION_SUFFIXES are ordered in preference order, and more specific ABI is preferred. + # On free-threaded 3.15+, this will match ".abi3t" as the only supported stable ABI. + # On GIL-enabled 3.15+, it will match plain ".abi3" first, since that ABI is more specific. for suffix in importlib.machinery.EXTENSION_SUFFIXES: if '.abi3' in suffix: # Unix return suffix From 1657f0b316b95bf4ccb4d37198b78bb12b8e3349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 18:43:32 +0200 Subject: [PATCH 13/14] Fix module name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/packages/limited-api/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/packages/limited-api/module.c b/tests/packages/limited-api/module.c index e53b2f13..c641b406 100644 --- a/tests/packages/limited-api/module.c +++ b/tests/packages/limited-api/module.c @@ -20,7 +20,7 @@ static PyMethodDef methods[] = { static struct PyModuleDef module = { PyModuleDef_HEAD_INIT, - "plat", + "module", NULL, -1, methods, From f816e009ac5f77f764770082750434902b4f9924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 24 Jun 2026 20:41:19 +0200 Subject: [PATCH 14/14] Fix module name in the correct test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/packages/limited-api-ft/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/packages/limited-api-ft/module.c b/tests/packages/limited-api-ft/module.c index 8a661f1b..98032e8f 100644 --- a/tests/packages/limited-api-ft/module.c +++ b/tests/packages/limited-api-ft/module.c @@ -21,7 +21,7 @@ static PyMethodDef methods[] = { PyABIInfo_VAR(abi_info); static PySlot module_slots[] = { - PySlot_STATIC_DATA(Py_mod_name, "plat"), + PySlot_STATIC_DATA(Py_mod_name, "module"), PySlot_STATIC_DATA(Py_mod_methods, methods), PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), PySlot_DATA(Py_mod_abi, &abi_info),