Skip to content

Commit

Permalink
ENH: make the Meson that is used user-configurable
Browse files Browse the repository at this point in the history
Allow using a MESON environment variable, analogous to the NINJA one,
or a string to an executable or Python script in the
`[tool.meson-python]` section of pyproject.toml

Closes mesonbuildgh-458
  • Loading branch information
rgommers committed Sep 10, 2023
1 parent a0f0827 commit 4b56612
Showing 1 changed file with 52 additions and 20 deletions.
72 changes: 52 additions & 20 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,13 @@ def _bool(value: Any, name: str) -> bool:
raise ConfigError(f'Configuration entry "{name}" must be a boolean')
return value

def _str(value: Any, name: str) -> bool:
if not isinstance(value, str):
raise ConfigError(f'Configuration entry "{name}" must be a string')
return value

scheme = _table({
'cli': _str,
'limited-api': _bool,
'args': _table({
name: _strings for name in _MESON_ARGS_KEYS
Expand Down Expand Up @@ -607,7 +613,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)

self._ninja = _env_ninja_command()
if self._ninja is None:
Expand Down Expand Up @@ -645,20 +666,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 +747,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 +757,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 +831,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 +926,31 @@ def _parse_version_string(string: str) -> Tuple[int, ...]:
return (0, )


def _get_meson_command(
meson_tool: str | None = None, /, *, version: str = _MESON_REQUIRED_VERSION
) -> List[str, ...]:
"""Return the command to invoke meson.
Uses the MESON env var if set, else the `cli` entry in pyproject.toml if given,
else the standard `meson` executable.
"""
_meson = os.environ.get('MESON')
if _meson is None:
_meson = meson_tool if meson_tool else '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"):
meson_cli = [sys.executable, _meson]
else:
meson_cli = [_meson]

_check_meson_version(meson_cli, version=version)
return meson_cli


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,7 +965,7 @@ def _env_ninja_command(*, version: str = _NINJA_REQUIRED_VERSION) -> Optional[st
return None


def _check_meson_version(*, version: str = _MESON_REQUIRED_VERSION) -> None:
def _check_meson_version(meson_cli : List[str, ...], /, *, 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
Expand All @@ -944,7 +976,7 @@ def _check_meson_version(*, version: str = _MESON_REQUIRED_VERSION) -> None:
"""
required_version = _parse_version_string(version)
meson_version = subprocess.run(['meson', '--version'], check=False, text=True, capture_output=True).stdout
meson_version = subprocess.run(meson_cli + ['--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}.')

Expand Down

0 comments on commit 4b56612

Please sign in to comment.