Skip to content

Commit f4206b3

Browse files
lithomas1dnicolodi
authored andcommitted
BUG: use meson compile wrapper on Windows
The meson compile wrapper is required on Windows to setup the MSVC compiler environment. Using the --ninja-args option to meson compile allows to provide the exact same semantics for the compile arguments. The format in which ninja command line arguments need to be passed to --ninja-args makes the interface a bit awkward to use and adds some complexity to this otherwise simple patch.
1 parent a87d1ab commit f4206b3

File tree

3 files changed

+122
-23
lines changed

3 files changed

+122
-23
lines changed

docs/how-to-guides/meson-args.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ building a Python wheel. User options specified via ``pyproject.toml``
4444
or via Python build front-end config settings override the
4545
``meson-python`` defaults.
4646

47+
When building on Windows, ``meson-python`` invokes the ``ninja``
48+
command via the ``meson compile`` wrapper. When the GCC or the LLVM
49+
compilers are not found on the ``$PATH``, this activates the Visual
50+
Studio environment and allows ``ninja`` to use the MSVC compilers. To
51+
activate the Visual Studio environment unconditionally, pass the
52+
``--vsenv`` option to ``meson setup``, see this :ref:`example
53+
<vsenv-example>`. When using the ``meson compile`` wrapper, the user
54+
supplied options for the compilation command are passed via the
55+
``--ninja-args`` option. This ensures that the behaviour is
56+
independent of how the build is initiated. Refer to the `Meson
57+
documentation`__ for more details.
58+
59+
__ https://mesonbuild.com/Commands.html#backend-specific-arguments
60+
4761

4862
Examples
4963
========
@@ -152,3 +166,44 @@ To set this option temporarily at build-time:
152166
.. code-block:: console
153167
154168
$ python -m pip wheel . --config-settings=setup-args="-Doptimization=3" .
169+
170+
171+
.. _vsenv-example:
172+
173+
Force the use of the MSVC compilers on Windows
174+
----------------------------------------------
175+
176+
The MSVC compilers are not installed in the ``$PATH``. The Visual
177+
Studio environment needs to be activated for ``ninja`` to be able to
178+
use these compilers. This is taken care of by ``meson compile`` but
179+
only when the GCC compilers or the LLVM compilers are not found on the
180+
``$PATH``. Passing the ``--vsenv`` option to ``meson setup`` forces
181+
the activation of the Visual Studio environment and generates an error
182+
when the activation fails.
183+
184+
This option has no effect on other platforms thus, if your project
185+
requires to be compiled with MSVC, you can consider to set this option
186+
permanently in the project's ``pyproject.toml``:
187+
188+
.. code-block:: toml
189+
190+
[tool.meson-python.args]
191+
setup = ['--vsenv']
192+
193+
To set this option temporarily at build-time:
194+
195+
.. tab-set::
196+
197+
.. tab-item:: pypa/build
198+
:sync: key_pypa_build
199+
200+
.. code-block:: console
201+
202+
$ python -m build -Csetup-args="--vsenv" .
203+
204+
.. tab-item:: pip
205+
:sync: key_pip
206+
207+
.. code-block:: console
208+
209+
$ python -m pip wheel . --config-settings=setup-args="--vsenv" .

mesonpy/__init__.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,10 @@ def _wheel_write_metadata(self, whl: mesonpy._wheelfile.WheelFile) -> None:
489489
)
490490

491491
def build(self, directory: Path) -> pathlib.Path:
492-
self._project.build() # ensure project is built
492+
# ensure project is built
493+
self._project.build()
494+
# install the project
495+
self._project.install()
493496

494497
wheel_file = pathlib.Path(directory, f'{self.name}.whl')
495498

@@ -521,7 +524,8 @@ def build(self, directory: Path) -> pathlib.Path:
521524
return wheel_file
522525

523526
def build_editable(self, directory: Path, verbose: bool = False) -> pathlib.Path:
524-
self._project.build() # ensure project is built
527+
# ensure project is built
528+
self._project.build()
525529

526530
wheel_file = pathlib.Path(directory, f'{self.name}.whl')
527531

@@ -534,14 +538,13 @@ def build_editable(self, directory: Path, verbose: bool = False) -> pathlib.Path
534538

535539
# install loader module
536540
loader_module_name = f'_{self.normalized_name.replace(".", "_")}_editable_loader'
537-
build_cmd = [self._project._ninja, *self._project._meson_args['compile']]
538541
whl.writestr(
539542
f'{loader_module_name}.py',
540543
read_binary('mesonpy', '_editable.py') + textwrap.dedent(f'''
541544
install(
542545
{self.top_level_modules!r},
543546
{os.fspath(self._build_dir)!r},
544-
{build_cmd},
547+
{self._project._build_command!r},
545548
{verbose!r},
546549
)''').encode('utf-8'))
547550

@@ -787,18 +790,30 @@ def _wheel_builder(self) -> _WheelBuilder:
787790
self._install_plan,
788791
)
789792

790-
def build_commands(self) -> Sequence[Sequence[str]]:
793+
@property
794+
def _build_command(self) -> List[str]:
791795
assert self._ninja is not None # help mypy out
792-
return (
793-
(self._ninja, *self._meson_args['compile'],),
794-
('meson', 'install', '--no-rebuild', '--destdir', os.fspath(self._install_dir), *self._meson_args['install']),
795-
)
796+
if platform.system() == 'Windows':
797+
# On Windows use 'meson compile' to setup the MSVC compiler
798+
# environment. Using the --ninja-args option allows to
799+
# provide the exact same semantics for the compile arguments
800+
# provided by the users.
801+
cmd = ['meson', 'compile']
802+
args = list(self._meson_args['compile'])
803+
if args:
804+
cmd.append(f'--ninja-args={args!r}')
805+
return cmd
806+
return [self._ninja, *self._meson_args['compile']]
796807

797808
@functools.lru_cache(maxsize=None)
798809
def build(self) -> None:
799-
"""Trigger the Meson build."""
800-
for cmd in self.build_commands():
801-
self._run(cmd)
810+
"""Build the Meson project."""
811+
self._run(self._build_command)
812+
813+
def install(self) -> None:
814+
"""Install the Meson project."""
815+
destdir = os.fspath(self._install_dir)
816+
self._run(['meson', 'install', '--no-rebuild', '--destdir', destdir, *self._meson_args['install']])
802817

803818
@classmethod
804819
@contextlib.contextmanager

tests/test_project.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#
33
# SPDX-License-Identifier: MIT
44

5+
import ast
6+
import os.path
57
import platform
68
import shutil
79
import sys
@@ -60,12 +62,26 @@ def test_unsupported_python_version(package_unsupported_python_version):
6062

6163
def test_user_args(package_user_args, tmp_path, monkeypatch):
6264
project_run = mesonpy.Project._run
63-
call_args_list = []
65+
cmds = []
66+
args = []
6467

6568
def wrapper(self, cmd):
6669
# intercept and filter out test arguments and forward the call
67-
call_args_list.append(tuple(cmd))
68-
return project_run(self, [x for x in cmd if not x.startswith(('config-', 'cli-'))])
70+
if cmd[:2] == ['meson', 'compile']:
71+
# when using meson compile instead on ninja directly, the
72+
# arguments needs to be unmarshalled from the form used to
73+
# pass them to the --ninja-args option
74+
assert cmd[-1].startswith('--ninja-args=')
75+
cmds.append(cmd[:2])
76+
args.append(ast.literal_eval(cmd[-1].split('=')[1]))
77+
elif cmd[:1] == ['meson']:
78+
cmds.append(cmd[:2])
79+
args.append(cmd[2:])
80+
else:
81+
# direct ninja invocation
82+
cmds.append([os.path.basename(cmd[0])])
83+
args.append(cmd[1:])
84+
return project_run(self, [x for x in cmd if not x.startswith(('config-', 'cli-', '--ninja-args'))])
6985

7086
monkeypatch.setattr(mesonpy.Project, '_run', wrapper)
7187

@@ -79,19 +95,32 @@ def wrapper(self, cmd):
7995
mesonpy.build_sdist(tmp_path, config_settings)
8096
mesonpy.build_wheel(tmp_path, config_settings)
8197

82-
expected = [
98+
# check that the right commands are executed, namely that 'meson
99+
# compile' is used on Windows trather than a 'ninja' direct
100+
# invocation.
101+
assert cmds == [
83102
# sdist: calls to 'meson setup' and 'meson dist'
84-
('config-setup', 'cli-setup'),
85-
('config-dist', 'cli-dist'),
103+
['meson', 'setup'],
104+
['meson', 'dist'],
86105
# wheel: calls to 'meson setup', 'meson compile', and 'meson install'
87-
('config-setup', 'cli-setup'),
88-
('config-compile', 'cli-compile'),
89-
('config-install', 'cli-install'),
106+
['meson', 'setup'],
107+
['meson', 'compile'] if platform.system() == 'Windows' else ['ninja'],
108+
['meson', 'install']
90109
]
91110

92-
for expected_args, call_args in zip(expected, call_args_list):
111+
# check that the user options are passed to the invoked commands
112+
expected = [
113+
# sdist: calls to 'meson setup' and 'meson dist'
114+
['config-setup', 'cli-setup'],
115+
['config-dist', 'cli-dist'],
116+
# wheel: calls to 'meson setup', 'meson compile', and 'meson install'
117+
['config-setup', 'cli-setup'],
118+
['config-compile', 'cli-compile'],
119+
['config-install', 'cli-install'],
120+
]
121+
for expected_args, cmd_args in zip(expected, args):
93122
for arg in expected_args:
94-
assert arg in call_args
123+
assert arg in cmd_args
95124

96125

97126
@pytest.mark.parametrize('package', ('top-level', 'meson-args'))

0 commit comments

Comments
 (0)