Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/reference/environment-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ Environment variables
possible for ``meson-python`` to detect this, and it will not set the
Python wheel platform tag accordingly.

.. envvar:: MESON

Specifies the ``meson`` executable or script to use. It overrides
``tool.meson-python.meson``. See :ref:`reference-pyproject-settings` for
more details.

.. envvar:: MESONPY_EDITABLE_VERBOSE

Setting this environment variable to any value enables directing to the
Expand All @@ -68,7 +74,7 @@ Environment variables

.. envvar:: NINJA

Specify the ninja_ executable to use. It can also be used to select
Specifies the ninja_ executable to use. It can also be used to select
ninja_ alternatives like samurai_.

.. _ninja: https://ninja-build.org
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/pyproject-settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ use them and examples.
``-Dpython.allow_limited_api=false`` option is passed to ``meson
setup``.

.. option:: tool.meson-python.meson

A string specifying the ``meson`` executable or script to use. If it is a
path to an existing file with a name ending in ``.py``, it will be invoked
as a Python script using the same Python interpreter that is used to run
``meson-python`` itself. It can be overrridden by the :envvar:`MESON`
environment variable.

.. option:: tool.meson-python.args.dist

Extra arguments to be passed to the ``meson dist`` command.
Expand Down
94 changes: 59 additions & 35 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,15 @@ def _bool(value: Any, name: str) -> bool:
raise ConfigError(f'Configuration entry "{name}" must be a boolean')
return value

def _string_or_path(value: Any, name: str) -> str:
if not isinstance(value, str):
raise ConfigError(f'Configuration entry "{name}" must be a string')
if os.path.isfile(value):
value = os.path.abspath(value)
return value

scheme = _table({
'meson': _string_or_path,
'limited-api': _bool,
'args': _table({
name: _strings for name in _MESON_ARGS_KEYS
Expand Down Expand Up @@ -592,7 +600,7 @@ def _string_or_strings(value: Any, name: str) -> List[str]:
class Project():
"""Meson project wrapper to generate Python artifacts."""

def __init__( # noqa: C901
def __init__(
self,
source_dir: Path,
build_dir: Path,
Expand All @@ -607,7 +615,22 @@ def __init__( # noqa: C901
self._meson_args: MesonArgs = collections.defaultdict(list)
self._limited_api = False

_check_meson_version()
# load pyproject.toml
pyproject = tomllib.loads(self._source_dir.joinpath('pyproject.toml').read_text())

# 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)

# meson arguments from the command line take precedence over
# arguments from the configuration file thus are added later
if meson_args:
for key, value in meson_args.items():
self._meson_args[key].extend(value)

# determine command to invoke meson
self._meson = _get_meson_command(pyproject_config.get('meson'))

self._ninja = _env_ninja_command()
if self._ninja is None:
Expand Down Expand Up @@ -645,20 +668,6 @@ def __init__( # noqa: C901
self._meson_cross_file.write_text(cross_file_data)
self._meson_args['setup'].extend(('--cross-file', os.fspath(self._meson_cross_file)))

# load pyproject.toml
pyproject = tomllib.loads(self._source_dir.joinpath('pyproject.toml').read_text())

# 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)

# meson arguments from the command line take precedence over
# arguments from the configuration file thus are added later
if meson_args:
for key, value in meson_args.items():
self._meson_args[key].extend(value)

# write the native file
native_file_data = textwrap.dedent(f'''
[binaries]
Expand Down Expand Up @@ -740,7 +749,7 @@ def _configure(self, reconfigure: bool = False) -> None:
]
if reconfigure:
setup_args.insert(0, '--reconfigure')
self._run(['meson', 'setup', *setup_args])
self._run(self._meson + ['setup', *setup_args])

@property
def _build_command(self) -> List[str]:
Expand All @@ -750,7 +759,7 @@ def _build_command(self) -> List[str]:
# environment. Using the --ninja-args option allows to
# provide the exact same semantics for the compile arguments
# provided by the users.
cmd = ['meson', 'compile']
cmd = self._meson + ['compile']
args = list(self._meson_args['compile'])
if args:
cmd.append(f'--ninja-args={args!r}')
Expand Down Expand Up @@ -824,7 +833,7 @@ def version(self) -> str:
def sdist(self, directory: Path) -> pathlib.Path:
"""Generates a sdist (source distribution) in the specified directory."""
# generate meson dist file
self._run(['meson', 'dist', '--allow-dirty', '--no-tests', '--formats', 'gztar', *self._meson_args['dist']])
self._run(self._meson + ['dist', '--allow-dirty', '--no-tests', '--formats', 'gztar', *self._meson_args['dist']])

# move meson dist file to output path
dist_name = f'{self.name}-{self.version}'
Expand Down Expand Up @@ -919,6 +928,37 @@ def _parse_version_string(string: str) -> Tuple[int, ...]:
return (0, )


def _get_meson_command(
meson: Optional[str] = None, *, version: str = _MESON_REQUIRED_VERSION
) -> List[str]:
"""Return the command to invoke meson."""

# The MESON env var, if set, overrides the config value from pyproject.toml.
# The config value, if given, is an absolute path or the name of an executable.
meson = os.environ.get('MESON', meson or 'meson')

# If the specified Meson string ends in `.py`, we run it with the current
# Python executable. This avoids problems for users on Windows, where
# making a script executable isn't enough to get it to run when invoked
# directly. For packages that vendor a forked Meson, the `meson.py` in the
# root of the Meson repo can be used this way.
if meson.endswith('.py'):
cmd = [sys.executable, meson]
else:
cmd = [meson]

# The meson Python package is a dependency of the meson-python Python
# package, however, it may occur that the meson Python package is installed
# but the corresponding meson command is not available in $PATH. Implement
# a runtime check to verify that the build environment is setup correcly.
required_version = _parse_version_string(version)
meson_version = subprocess.run(cmd + ['--version'], check=False, text=True, capture_output=True).stdout
if _parse_version_string(meson_version) < required_version:
raise ConfigError(f'Could not find meson version {version} or newer, found {meson_version}.')

return cmd


def _env_ninja_command(*, version: str = _NINJA_REQUIRED_VERSION) -> Optional[str]:
"""Returns the path to ninja, or None if no ninja found."""
required_version = _parse_version_string(version)
Expand All @@ -933,22 +973,6 @@ def _env_ninja_command(*, version: str = _NINJA_REQUIRED_VERSION) -> Optional[st
return None


def _check_meson_version(*, version: str = _MESON_REQUIRED_VERSION) -> None:
"""Check that the meson executable in the path has an appropriate version.

The meson Python package is a dependency of the meson-python
Python package, however, it may occur that the meson Python
package is installed but the corresponding meson command is not
available in $PATH. Implement a runtime check to verify that the
build environment is setup correcly.

"""
required_version = _parse_version_string(version)
meson_version = subprocess.run(['meson', '--version'], check=False, text=True, capture_output=True).stdout
if _parse_version_string(meson_version) < required_version:
raise ConfigError(f'Could not find meson version {version} or newer, found {meson_version}.')


def _add_ignore_files(directory: pathlib.Path) -> None:
directory.joinpath('.gitignore').write_text(textwrap.dedent('''
# This file is generated by meson-python. It will not be recreated if deleted or modified.
Expand Down
5 changes: 0 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ extend-ignore = [
select = [
'B', # flake8-bugbear
'C4', # flake8-comprehensions
'C9', # mccabe
'E', # pycodestyle
'F', # pyflakes
'W', # pycodestyle
Expand All @@ -92,10 +91,6 @@ exclude = [
'docs/conf.py',
]

[tool.ruff.mccabe]
max-complexity = 12


[tool.isort]
lines_between_types = 1
lines_after_imports = 2
Expand Down
11 changes: 11 additions & 0 deletions tests/packages/vendored-meson/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2023 The meson-python developers
#
# SPDX-License-Identifier: MIT

project('vendored-meson', version: '1.0.0')

py = import('python').find_installation()

if not get_option('custom-meson-used')
error('Expected option "custom-meson-used" was not specified')
endif
5 changes: 5 additions & 0 deletions tests/packages/vendored-meson/meson_options.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The meson-python developers
#
# SPDX-License-Identifier: MIT

option('custom-meson-used', type: 'boolean', value: false)
10 changes: 10 additions & 0 deletions tests/packages/vendored-meson/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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]
meson = 'third-party/meson.py'
15 changes: 15 additions & 0 deletions tests/packages/vendored-meson/third-party/meson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2023 The meson-python developers
#
# SPDX-License-Identifier: MIT

import sys

from mesonbuild import mesonmain


if 'setup' in sys.argv:
sys.argv.append('-Dcustom-meson-used=true')


if __name__ == '__main__':
sys.exit(mesonmain.main())
6 changes: 6 additions & 0 deletions tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,9 @@ def test_install_subdir(wheel_install_subdir):
'nested/deep/deep.py',
'nested/nested.py',
}


def test_vendored_meson(wheel_vendored_meson):
# This test will error if the vendored meson.py wrapper script in
# the test package isn't used.
pass