From d73c8a3811736676ea71b09c8827c2b16c819b30 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 1/2] 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 | 17 +++++++--- .../limited-api-free-threaded/meson.build | 14 ++++++++ .../limited-api-free-threaded/module.c | 33 +++++++++++++++++++ .../limited-api-free-threaded/pyproject.toml | 10 ++++++ tests/test_tags.py | 11 ++++++- tests/test_wheel.py | 11 +++++++ 6 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 tests/packages/limited-api-free-threaded/meson.build create mode 100644 tests/packages/limited-api-free-threaded/module.c create mode 100644 tests/packages/limited-api-free-threaded/pyproject.toml diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 7325d23f1..3ca554af0 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. + 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 # 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 abi3t else 'abi3' return None def _install_path(self, wheel_file: mesonpy._wheelfile.WheelFile, origin: Path, destination: pathlib.Path) -> None: @@ -874,10 +881,10 @@ 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): raise BuildError( - 'The package targets Python\'s Limited API, which is not supported by free-threaded CPython. ' - '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. diff --git a/tests/packages/limited-api-free-threaded/meson.build b/tests/packages/limited-api-free-threaded/meson.build new file mode 100644 index 000000000..1e0030564 --- /dev/null +++ b/tests/packages/limited-api-free-threaded/meson.build @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 The meson-python developers +# +# SPDX-License-Identifier: MIT + +project('limited-api-free-thraded', '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-free-threaded/module.c b/tests/packages/limited-api-free-threaded/module.c new file mode 100644 index 000000000..107cf0291 --- /dev/null +++ b/tests/packages/limited-api-free-threaded/module.c @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 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, "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), + PySlot_END, +}; + +PyMODEXPORT_FUNC PyModExport_module(void) { + return module_slots; +} diff --git a/tests/packages/limited-api-free-threaded/pyproject.toml b/tests/packages/limited-api-free-threaded/pyproject.toml new file mode 100644 index 000000000..11f2627db --- /dev/null +++ b/tests/packages/limited-api-free-threaded/pyproject.toml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2023 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 d2bbd057b..29640b394 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. @@ -29,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 @@ -130,7 +134,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): + 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 e3c935599..2871326bb 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), reason='Requires PEP 820 API, present since Python 3.15') +def test_limited_api_free_threaded(wheel_limited_api_free_threaded): + artifact = wheel.wheelfile.WheelFile(wheel_limited_api_free_threaded) + 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 60986f7598d874cc0c94d2b50c3afbb1f926be30 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 2/2] TST: Fix the module name in `limited-api` 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/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 e53b2f13a..c641b4066 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,