diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2fade69b2..506e9e5af 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -104,6 +104,44 @@ jobs: env_vars: PYTHON name: ${{ matrix.python }} + msvc: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + python: + - '3.11' + meson: + - + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up target Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Install Ninja + run: python -m pip --disable-pip-version-check install ninja + + - name: Setup MSVC + uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1 + with: + architecture: x64 + + - name: Install Meson + run: python -m pip --disable-pip-version-check install "meson==${{ matrix.meson }}" + if: ${{ matrix.meson }} + + - name: Install + run: python -m pip --disable-pip-version-check install .[test] + + - name: Run tests + run: >- + python -m pytest --showlocals -vv + cygwin: runs-on: windows-latest strategy: diff --git a/docs/explanations/default-options.rst b/docs/explanations/default-options.rst index f04e39fe7..c5e3e0eba 100644 --- a/docs/explanations/default-options.rst +++ b/docs/explanations/default-options.rst @@ -8,42 +8,87 @@ Default build options ********************* -Meson offers many `built-in options `__, -and in the vast majority of cases those have good defaults. There are a couple -of cases however where ``meson-python`` either needs to or chooses to override -those with its own defaults. To view what those are for the version of -``meson-python`` you have installed, look at the *User defined options* section -of the output during the configure stage of the build (e.g., by running -``python -m build --wheel``). This will look something like: +Meson offers many `built-in options`__ to control how the project is +built and installed. In the vast majority of cases those have good +defaults. There are however a few options that ``meson-python`` +overrides with its own defaults to adjust the build process to the +task of building Python wheels. + +The default options specified by ``meson-python`` are overridden by +package specific options specified in ``pyproject.toml`` and by +options provided by the user at build time via the Python build +front-end. Refer to the :ref:`how-to-guides-meson-args` guide for +details. + +The options used to build the project are summarized in the *User +defined options* section of the output of the ``meson setup`` stage of +the build, for example when running ``python -m build -w``. This will +look something like: + +__ https://mesonbuild.com/Builtin-options.html .. code-block:: text User defined options - Native files : /home/username/code/project/.mesonpy-native-file.ini - debug : false - optimization : 2 - prefix : /home/username/mambaforge/envs/project-dev - python.platlibdir: /home/username/mambaforge/envs/project-dev/lib/python3.10/site-packages - python.purelibdir: /home/username/mambaforge/envs/project-dev/lib/python3.10/site-packages - b_ndebug : if-release - -Let's go through each option and why they are used: - -- meson-python uses a native file, written to the build dir and named - ``mesonpy-native-file.ini``, in order to point Meson at the correct - ``python`` interpreter to use (the same one for which ``meson-python`` was - installed). This is necessary, because Meson may otherwise look for the first - Python interpreter on the PATH (usually the same one, but not always the - case). Users may use ``--native-file`` to pass a second native file to Meson; - Meson will merge contents of both native file, so as long as the - user-provided file does not try to pass a different path for the ``python`` - binary, this will work without a conflict. -- The ``prefix`` and ``platlibdir``/``purelibdir`` options also point Meson at - that same interpreter and the environment in which it is installed. -- The ``debug``, ``optimization`` and ``b_ndebug`` options are overridden, - because Meson defaults to values that are appropriate for development, while - the main purpose of meson-python is to build release artifacts. - -It is possible to override these defaults, either permanently in your -``pyproject.toml`` or at build time via the build frontend CLI. -See the :ref:`how-to-guides-meson-args` page for examples of both methods. + Native files: $builddir/meson-python-native-file.ini + buildtype : release + b_ndebug : if-release + b_vscrt : md + +where the path to the build directory has been replaced with +``$builddir`` for clarity. + +The options that ``meson-python`` specifies by default are: + +.. option:: native-file=$builddir/meson-python-native-file.ini + + ``meson-python`` uses a native file to point Meson at the + ``python`` interpreter that the build must target. This is the + Python interpreter that is used to run the Python build + front-end. Meson would otherwise look for the first Python + interpreter on the ``$PATH``, which may not be the same. + + Additional ``--native-file`` options can be passed to ``meson + setup`` if further adjustments to the native environment need to be + made. Meson will merge the contents of all machine files. The + user-provided machine files must not override the path for the + ``python`` binary. + +.. option:: buildtype=release + + The Meson default is to produce a debug build with binaries + compiled with debug symbols and, when compiling with MSVC, linking + to the Visual Studio debug runtime, see below. The main purpose of + ``meson-python`` is to build release artifacts, therefore a more + appropriate `build type`__ is selected. A release build is compiled + without debug symbols and with compiler optimizations. Refer to the + `Meson documentation`__ for more details. + +__ https://mesonbuild.com/Builtin-options.html#details-for-buildtype +__ https://mesonbuild.com/Builtin-options.html#core-options + +.. option:: b_ndebug=if-release + + For reasons related to backward compatibility, Meson does not + disable assertions for release builds. For most users this is a + surprising and undesired behavior. This option instructs Meson to + pass the ``-DNDEBUG`` option to the compilers, unless the build + type is set to something else than release. + +.. option:: b_vscrt=md + + With the default options, when compiling a debug build, Meson + instructs the MSVC compiler to use the debug version of the Visual + Studio runtime library. This causes the MSVC linker to look for the + debug build of all the linked DLLs. The Python distribution for + Windows does not contain a debug version of the Python DLL and + linking fails. These linking failures are surprising and hard to + diagnose. To avoid this issue when users explicitly asks for a + debug build, ``meson-python`` sets this options to instruct Meson + to compile with the release version of the Visual Studio + runtime. For more details, refer to the `Meson documentation`__ and + to the `Visual Studio documentation`__ . This option is ignored + when other compilers are used. + +__ https://mesonbuild.com/Builtin-options.html#base-options +__ https://learn.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170 diff --git a/docs/how-to-guides/meson-args.rst b/docs/how-to-guides/meson-args.rst index 9197b1d95..1cd03aff1 100644 --- a/docs/how-to-guides/meson-args.rst +++ b/docs/how-to-guides/meson-args.rst @@ -40,9 +40,9 @@ precedence over, and can be used to override, the ones specified in ``meson-python`` overrides some of the default Meson options with :ref:`settings ` more appropriate for -building Python wheel. User options specified via ``pyproject.toml`` -or via Python build front-end config settings can be used to override -the ``meson-python`` defaults.. +building a Python wheel. User options specified via ``pyproject.toml`` +or via Python build front-end config settings override the +``meson-python`` defaults. Examples diff --git a/meson.build b/meson.build index 0fe1f1b89..df9d466f1 100644 --- a/meson.build +++ b/meson.build @@ -17,7 +17,6 @@ py.install_sources( 'mesonpy/_dylib.py', 'mesonpy/_editable.py', 'mesonpy/_elf.py', - 'mesonpy/_introspection.py', 'mesonpy/_tags.py', 'mesonpy/_util.py', 'mesonpy/_wheelfile.py', diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 037a0cbd5..621386833 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -48,7 +48,6 @@ import mesonpy._compat import mesonpy._dylib import mesonpy._elf -import mesonpy._introspection import mesonpy._tags import mesonpy._util import mesonpy._wheelfile @@ -128,7 +127,8 @@ def _init_colors() -> Dict[str, str]: _STYLES = _init_colors() # holds the color values, should be _COLORS or _NO_COLORS -_EXTENSION_SUFFIXES = importlib.machinery.EXTENSION_SUFFIXES.copy() +_SUFFIXES = importlib.machinery.all_suffixes() +_EXTENSION_SUFFIXES = importlib.machinery.EXTENSION_SUFFIXES _EXTENSION_SUFFIX_REGEX = re.compile(r'^\.(?:(?P[^.]+)\.)?(?:so|pyd|dll)$') assert all(re.match(_EXTENSION_SUFFIX_REGEX, x) for x in _EXTENSION_SUFFIXES) @@ -378,25 +378,15 @@ def top_level_modules(self) -> Collection[str]: modules = set() for type_ in self._wheel_files: for path, _ in self._wheel_files[type_]: - top_part = path.parts[0] - # file module - if top_part.endswith('.py'): - modules.add(top_part[:-3]) + name, dot, ext = path.parts[0].partition('.') + if dot: + # module + suffix = dot + ext + if suffix in _SUFFIXES: + modules.add(name) else: - # native module - for extension in _EXTENSION_SUFFIXES: - if top_part.endswith(extension): - modules.add(top_part[:-len(extension)]) - # XXX: We assume the order in _EXTENSION_SUFFIXES - # goes from more specific to last, so we go - # with the first match we find. - break - else: # nobreak - # skip Windows import libraries - if top_part.endswith('.a'): - continue - # package module - modules.add(top_part) + # package + modules.add(name) return modules def _is_native(self, file: Union[str, pathlib.Path]) -> bool: @@ -422,7 +412,7 @@ def _is_native(self, file: Union[str, pathlib.Path]) -> bool: return True return False - def _install_path( + def _install_path( # noqa: C901 self, wheel_file: mesonpy._wheelfile.WheelFile, counter: mesonpy._util.CLICounter, @@ -477,7 +467,12 @@ def _install_path( raise NotImplementedError("Bundling libraries in wheel is not supported on platform '{}'" .format(platform.system())) - wheel_file.write(origin, location) + try: + wheel_file.write(origin, location) + except FileNotFoundError: + # work around for Meson bug, see https://github.com/mesonbuild/meson/pull/11655 + if not os.fspath(origin).endswith('.pdb'): + raise def _wheel_write_metadata(self, whl: mesonpy._wheelfile.WheelFile) -> None: # add metadata @@ -742,26 +737,15 @@ def _run(self, cmd: Sequence[str]) -> None: def _configure(self, reconfigure: bool = False) -> None: """Configure Meson project.""" - sys_paths = mesonpy._introspection.SYSCONFIG_PATHS setup_args = [ - f'--prefix={sys.base_prefix}', os.fspath(self._source_dir), os.fspath(self._build_dir), f'--native-file={os.fspath(self._meson_native_file)}', - # TODO: Allow configuring these arguments - '-Ddebug=false', + # default build options + '-Dbuildtype=release', '-Db_ndebug=if-release', - '-Doptimization=2', - - # XXX: This should not be needed, but Meson is using the wrong paths - # in some scenarios, like on macOS. - # https://github.com/mesonbuild/meson-python/pull/87#discussion_r1047041306 - '--python.purelibdir', - sys_paths['purelib'], - '--python.platlibdir', - sys_paths['platlib'], - - # user args + '-Db_vscrt=md', + # user build options *self._meson_args['setup'], ] if reconfigure: @@ -800,18 +784,11 @@ def _wheel_builder(self) -> _WheelBuilder: self._install_plan, ) - def build_commands(self, install_dir: Optional[pathlib.Path] = None) -> Sequence[Sequence[str]]: + def build_commands(self) -> Sequence[Sequence[str]]: assert self._ninja is not None # help mypy out return ( (self._ninja, *self._meson_args['compile'],), - ( - 'meson', - 'install', - '--only-changed', - '--destdir', - os.fspath(install_dir or self._install_dir), - *self._meson_args['install'], - ), + ('meson', 'install', '--no-rebuild', '--destdir', os.fspath(self._install_dir), *self._meson_args['install']), ) @functools.lru_cache(maxsize=None) diff --git a/mesonpy/_introspection.py b/mesonpy/_introspection.py deleted file mode 100644 index 1681528e4..000000000 --- a/mesonpy/_introspection.py +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2022 The meson-python developers -# -# SPDX-License-Identifier: MIT - -from __future__ import annotations - -import sys -import sysconfig -import typing - - -if typing.TYPE_CHECKING: # pragma: no cover - from mesonpy._compat import Mapping - - -def sysconfig_paths() -> Mapping[str, str]: - sys_vars = sysconfig.get_config_vars().copy() - sys_vars['base'] = sys_vars['platbase'] = sys.base_prefix - return sysconfig.get_paths(vars=sys_vars) - - -SYSCONFIG_PATHS = sysconfig_paths() - - -__all__ = [ - 'SYSCONFIG_PATHS', -] diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 6d4b24ba2..30dca3412 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -4,6 +4,7 @@ import platform import re +import shutil import stat import subprocess import sys @@ -28,7 +29,8 @@ from distutils.sysconfig import get_config_var EXT_SUFFIX = get_config_var('EXT_SUFFIX') -EXT_IMP_SUFFIX = re.sub(r'.pyd$', '.dll', EXT_SUFFIX) + '.a' +if sys.platform in {'win32', 'cygwin'}: + EXT_IMP_SUFFIX = re.sub(r'.(pyd|dll)$', '.lib' if shutil.which('cl.exe') else '.dll.a', EXT_SUFFIX) # Test against the wheel tag generated by packaging module. tag = next(packaging.tags.sys_tags())