From a259fa8ddffd53c5e72c6133457f114e392167cb Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 14 Jun 2025 18:21:37 +0200 Subject: [PATCH 1/6] TST: enable fatal meson warnings for all tests Unless explicitly disabled in ``pyproject.toml``. This uses a pytest ``autouse`` fixture to monkeypatch the function that validates the ``pyproject.toml`` meson-python configuration to add ``--fatal-meson-warnings`` to the ``meson setup`` arguments, unless ``--no-fatal-meson-warnings`` is specified by the package. --- mesonpy/__init__.py | 16 +++++++++++----- tests/conftest.py | 26 ++++++++++++++++++++++++++ tests/test_project.py | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 77b0be327..a28533e98 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -659,6 +659,14 @@ def _string_or_strings(value: Any, name: str) -> List[str]: return config +def _get_meson_args(args: Dict[str, List[str]]) -> MesonArgs: + # This mostly exists so that it can be monkeypatched in tests + meson_args: MesonArgs = collections.defaultdict(list) + for key, value in args.items(): + meson_args[key].extend(value) # type: ignore[index] + return meson_args + + class Project(): """Meson project wrapper to generate Python artifacts.""" @@ -674,16 +682,14 @@ def __init__( self._editable_verbose = editable_verbose self._meson_native_file = self._build_dir / 'meson-python-native-file.ini' self._meson_cross_file = self._build_dir / 'meson-python-cross-file.ini' - self._meson_args: MesonArgs = collections.defaultdict(list) self._limited_api = False # load pyproject.toml pyproject = tomllib.loads(self._source_dir.joinpath('pyproject.toml').read_text(encoding='utf-8')) - - # load meson args from pyproject.toml pyproject_config = _validate_pyproject_config(pyproject) - for key, value in pyproject_config.get('args', {}).items(): - self._meson_args[key].extend(value) + + # get meson args from pyproject.toml + self._meson_args = _get_meson_args(pyproject_config.get('args', {})) # meson arguments from the command line take precedence over # arguments from the configuration file thus are added later diff --git a/tests/conftest.py b/tests/conftest.py index af737968c..f069ef714 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT +import argparse import contextlib import importlib.metadata import os @@ -192,3 +193,28 @@ def cleanenv(): # $MACOSX_DEPLOYMENT_TARGET affects the computation of the platform tag on macOS. yield mpatch.delenv('MACOSX_DEPLOYMENT_TARGET', raising=False) mpatch.undo() + + +@pytest.fixture(autouse=True, scope='session') +def meson_fatal_warnings(): + # Cannot use the 'monkeypatch' fixture because of scope mismatch. + mpatch = pytest.MonkeyPatch() + mesonpy_get_meson_args = mesonpy._get_meson_args + + def _get_meson_args(args): + meson_args = mesonpy_get_meson_args(args) + meson_setup_args = meson_args['setup'] + + # Add ``--fatal-meson-warnings`` to the ``meson setup`` arguments + # unless the project specifies ``--no-fatal-meson-warnings`` in + # ``tool.meson-build.args.setup``. + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--no-fatal-meson-warnings', action='store_true') + args, meson_setup_args = parser.parse_known_args(meson_setup_args) + if not args.no_fatal_meson_warnings: + meson_setup_args.append('--fatal-meson-warnings') + + meson_args['setup'] = meson_setup_args + return meson_args + + mpatch.setattr(mesonpy, '_get_meson_args', _get_meson_args) diff --git a/tests/test_project.py b/tests/test_project.py index 11b229ab7..b511b2171 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -399,7 +399,7 @@ def test_ios_project(package_simple, monkeypatch, multiarch, tmp_path): project = mesonpy.Project(source_dir=package_simple, build_dir=tmp_path) # Meson configuration points at the cross file - assert project._meson_args['setup'] == ['--cross-file', os.fspath(tmp_path / 'meson-python-cross-file.ini')] + assert project._meson_args['setup'][-2:] == ['--cross-file', os.fspath(tmp_path / 'meson-python-cross-file.ini')] # Meson config files exist, and have some relevant keys assert (tmp_path / 'meson-python-native-file.ini').exists() From 9186f6844f5a5887346417be3286a18aa5eb0776 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sat, 14 Jun 2025 18:43:04 +0200 Subject: [PATCH 2/6] TST: define minimum required meson version --- tests/packages/limited-api/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/packages/limited-api/meson.build b/tests/packages/limited-api/meson.build index 4973c6f14..606a12242 100644 --- a/tests/packages/limited-api/meson.build +++ b/tests/packages/limited-api/meson.build @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -project('limited-api', 'c', version: '1.0.0') +project('limited-api', 'c', version: '1.0.0', meson_version: '>= 1.3') py = import('python').find_installation(pure: false) From a3d86cea8ec07c864f0b73179dcb71f3d099bb66 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Tue, 28 Oct 2025 18:08:44 +0100 Subject: [PATCH 3/6] TST: make test the same with all Meson versions and avoid a warning --- tests/packages/subproject/pyproject.toml | 5 +---- .../packages/subproject/subprojects/dep/meson.build | 13 +++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/packages/subproject/pyproject.toml b/tests/packages/subproject/pyproject.toml index d339a1bb7..cf1dd4a82 100644 --- a/tests/packages/subproject/pyproject.toml +++ b/tests/packages/subproject/pyproject.toml @@ -8,10 +8,7 @@ requires = ['meson-python'] [tool.meson-python.wheel] exclude = [ - # Meson before version 1.3.0 install data files in - # ``{datadir}/{project name}/``, later versions install - # in the more correct ``{datadir}/{subproject name}/``. - '{datadir}/*/data.txt', + '{datadir}/dep/*.txt', '{py_purelib}/dep.*', ] include = [ diff --git a/tests/packages/subproject/subprojects/dep/meson.build b/tests/packages/subproject/subprojects/dep/meson.build index 34c6b7508..cc7c5c029 100644 --- a/tests/packages/subproject/subprojects/dep/meson.build +++ b/tests/packages/subproject/subprojects/dep/meson.build @@ -6,6 +6,15 @@ project('dep') py = import('python').find_installation() -py.install_sources('dep.py') +py.install_sources( + 'dep.py', +) -install_data('data.txt') +install_data( + 'data.txt', + # Prior to meson 1.3.0, the default install path for subproject data files + # is ``{datadir}/{main-project-name}/``. In later versions it is the more + # correct ``{datadir}/{subproject-name}/``. Specify the install path to + # obtain uniform behavior across different meson versions. + install_dir: '{datadir}' / meson.project_name(), +) From 093f87600d35b67f171f7e56056123ce90bc06a6 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 2 Nov 2025 12:12:39 +0100 Subject: [PATCH 4/6] TST: use the same spelling for pytest.mark.skipif() reason --- tests/test_wheel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 350798e58..e3c935599 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -186,7 +186,7 @@ def test_sharedlib_in_package(venv, wheel_sharedlib_in_package): assert int(output) == 42 -@pytest.mark.skipif(MESON_VERSION < (1, 3, 0), reason='Meson version too old') +@pytest.mark.skipif(MESON_VERSION < (1, 3, 0), reason='meson too old') def test_link_library_in_subproject(venv, wheel_link_library_in_subproject): venv.pip('install', wheel_link_library_in_subproject) output = venv.python('-c', 'import foo; print(foo.example_sum(3, 6))') @@ -306,7 +306,7 @@ def test_subprojects(package_subproject, tmp_path): # Requires Meson 1.2.0, see https://github.com/mesonbuild/meson/pull/11909. -@pytest.mark.skipif(MESON_VERSION < (1, 1, 99), reason='Meson version too old') +@pytest.mark.skipif(MESON_VERSION < (1, 1, 99), reason='meson too old') @pytest.mark.parametrize(('arg'), ['--skip-subprojects', '--skip-subprojects=dep']) def test_skip_subprojects(package_subproject, tmp_path, arg): filename = mesonpy.build_wheel(tmp_path, {'install-args': [arg]}) @@ -320,7 +320,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 version too old') +@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.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): @@ -332,7 +332,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 version too old') +@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.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): @@ -342,7 +342,7 @@ def test_limited_api_bad(package_limited_api, tmp_path): # Requires Meson 1.3.0, see https://github.com/mesonbuild/meson/pull/11745. -@pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='Meson version too old') +@pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='meson too old') def test_limited_api_disabled(package_limited_api, tmp_path): filename = mesonpy.build_wheel(tmp_path, {'setup-args': ['-Dpython.allow_limited_api=false']}) artifact = wheel.wheelfile.WheelFile(tmp_path / filename) @@ -401,7 +401,7 @@ def test_custom_target_install_dir(package_custom_target_dir, tmp_path): } # On Linux, Meson 1.10 or later is required, see https://github.com/mesonbuild/meson/pull/15141 -@pytest.mark.skipif(sys.platform == 'linux' and MESON_VERSION < (1, 9, 99), reason='Meson version too old') +@pytest.mark.skipif(sys.platform == 'linux' and MESON_VERSION < (1, 9, 99), reason='meson too old') @pytest.mark.skipif(sys.platform not in {'linux', 'darwin'}, reason='Not supported on this platform') def test_cmake_subproject(wheel_cmake_subproject): artifact = wheel.wheelfile.WheelFile(wheel_cmake_subproject) From 02df0492531c8486a304495830c806e7ee0b92b9 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 2 Nov 2025 13:03:02 +0100 Subject: [PATCH 5/6] TST: disable fatal meson warnings for link-against-local-lib package This package tests adding RPATH entries via linker arguments. This functionality is deprecated in Meson but it is used in the wild thus we should make sure it keeps working. --- tests/packages/link-against-local-lib/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/packages/link-against-local-lib/pyproject.toml b/tests/packages/link-against-local-lib/pyproject.toml index ef8c7eed6..88faeaa37 100644 --- a/tests/packages/link-against-local-lib/pyproject.toml +++ b/tests/packages/link-against-local-lib/pyproject.toml @@ -8,3 +8,6 @@ requires = ['meson-python'] [tool.meson-python] allow-windows-internal-shared-libs = true + +[tool.meson-python.args] +setup = ['--no-fatal-meson-warnings'] From df35414e0201b39fc92a428149b7d1520766d34f Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Sun, 2 Nov 2025 13:59:57 +0100 Subject: [PATCH 6/6] MAINT: make mypy happy not redefining a variable to a different type --- mesonpy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index a28533e98..546f8723a 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -844,8 +844,8 @@ def __init__( if self._limited_api: # check whether limited API is disabled for the Meson project options = self._info('intro-buildoptions') - value = next((option['value'] for option in options if option['name'] == 'python.allow_limited_api'), None) - if not value: + allow_limited_api = next((opt['value'] for opt in options if opt['name'] == 'python.allow_limited_api'), None) + if not allow_limited_api: self._limited_api = False if self._limited_api and bool(sysconfig.get_config_var('Py_GIL_DISABLED')):