Add an integration test and debug issues with the meson backend (#2718)
* Add an integration test with the meson backend * Modify script to allow testing of the GHA action on a PR * Try adding cython to languages in meson config * Revert "Try adding cython to languages in meson config" This reverts commit 50378a1c7e38665492ad0c683b178e4d96928e1e. * Pass --vsenv to meson on windows As seen here https://github.com/matplotlib/matplotlib/blob/9957c394bd01deb7a9bd9cb27804f447a52dc522/.github/workflows/cibuildwheel.yml#L114 * Disable win32 builds for the meson test * Move the windows-specific config into the test project definition This is so it can be tested with bin/run_example_ci_configs.py * Add some docs to the FAQ about meson on windows * Update bin/run_example_ci_configs.py Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com> --------- Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
co-authored by
Henry Schreiner
parent
0d2547bf6f
commit
a2f965f62f
@@ -16,6 +16,8 @@ import click
|
||||
|
||||
DIR = Path(__file__).parent.parent.resolve()
|
||||
|
||||
BuildBackend = typing.Literal["setuptools", "meson"]
|
||||
|
||||
|
||||
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return]
|
||||
@@ -27,11 +29,17 @@ def git_repo_has_changes() -> bool:
|
||||
return unstaged_changes or staged_changes
|
||||
|
||||
|
||||
def generate_basic_project(path: Path) -> None:
|
||||
def generate_project(path: Path, build_backend: BuildBackend) -> None:
|
||||
sys.path.insert(0, "")
|
||||
from test.test_projects.c import new_c_project # noqa: PLC0415
|
||||
match build_backend:
|
||||
case "meson":
|
||||
from test.test_projects.meson import new_meson_project as new_project # noqa: PLC0415
|
||||
case "setuptools":
|
||||
from test.test_projects.setuptools import new_c_project as new_project # noqa: PLC0415
|
||||
case _:
|
||||
typing.assert_never(build_backend)
|
||||
|
||||
project = new_c_project()
|
||||
project = new_project()
|
||||
project.generate(path)
|
||||
|
||||
|
||||
@@ -126,8 +134,9 @@ def ci_service_for_config_file(config_file: Path) -> CIService:
|
||||
|
||||
@click.command()
|
||||
@click.argument("config_files", nargs=-1, type=click.Path())
|
||||
@click.option("--build-backend", type=click.Choice(["setuptools", "meson"]), default="setuptools")
|
||||
def run_example_ci_configs(
|
||||
config_files: list[str],
|
||||
config_files: list[str], build_backend: BuildBackend = "setuptools"
|
||||
) -> None:
|
||||
"""
|
||||
Test the example configs. If no files are specified, will test
|
||||
@@ -166,7 +175,7 @@ def run_example_ci_configs(
|
||||
shell(f"git checkout --orphan {branch_name}", check=True)
|
||||
|
||||
example_project = Path("example_root")
|
||||
generate_basic_project(example_project)
|
||||
generate_project(example_project, build_backend=build_backend)
|
||||
|
||||
for config_file in config_file_paths:
|
||||
service = ci_service_for_config_file(config_file)
|
||||
|
||||
+15
@@ -99,6 +99,21 @@ Second, there might be platforms you want to ship for that NumPy (or some other
|
||||
|
||||
(Note the `*_ONLY_BINARY` variable also supports `":all:"`, and you don't need both that and `*_PREFER_BINARY`, you can use either one, depending on if you want a missing wheel to be a failure or an attempt to build in CI.)
|
||||
|
||||
### Building with Meson-Python on Windows
|
||||
|
||||
Meson generally works well with cibuildwheel, but there are a few things to be aware of:
|
||||
|
||||
- On GitHub Actions, the compiler that's chosen by default on Windows is often the MinGW compiler, rather than the MSVC toolchain that Python was compiled with.
|
||||
|
||||
The simplest fix for this is to configure cibuildwheel to pass the `--vsenv` flag to meson, like this:
|
||||
|
||||
```toml
|
||||
[tool.cibuildwheel.windows]
|
||||
config-settings = { "setup-args" = "--vsenv" }
|
||||
```
|
||||
|
||||
- If you need to build 32-bit Windows wheels, you need to activate a 32-bit compiler toolchain before starting cibuildwheel. Many users use [ilammy/msvc-dev-cmd](https://github.com/ilammy/msvc-dev-cmd) for this purpose.
|
||||
|
||||
### Automatic updates using Dependabot {: #automatic-updates}
|
||||
|
||||
Selecting a moving target (like the latest release) is generally a bad idea in CI. If something breaks, you can't tell whether it was your code or an upstream update that caused the breakage, and in a worst-case scenario, it could occur during a release.
|
||||
|
||||
@@ -2,8 +2,7 @@ import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from . import utils
|
||||
from .test_projects.c import new_c_project
|
||||
from . import test_projects, utils
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -14,7 +13,7 @@ from .test_projects.c import new_c_project
|
||||
],
|
||||
)
|
||||
def test_build_frontend_args(tmp_path, capfd, frontend_name):
|
||||
project = new_c_project()
|
||||
project = test_projects.new_c_project()
|
||||
project_dir = tmp_path / "project"
|
||||
project.generate(project_dir)
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import packaging.utils
|
||||
|
||||
from . import test_projects, utils
|
||||
|
||||
meson_project = test_projects.new_meson_project()
|
||||
|
||||
|
||||
def test_meson_python_basic(tmp_path, build_frontend_env):
|
||||
"""Test that cibuildwheel can build a project with meson-python backend."""
|
||||
project_dir = tmp_path / "project"
|
||||
|
||||
meson_project.generate(project_dir)
|
||||
|
||||
# build the wheels
|
||||
actual_wheels = utils.cibuildwheel_run(
|
||||
project_dir,
|
||||
add_env=build_frontend_env,
|
||||
single_python=True,
|
||||
)
|
||||
|
||||
# check that the expected wheels are produced
|
||||
# note that the meson test project doesn't support win32.
|
||||
is_windows = utils.get_platform() == "windows"
|
||||
expected_wheels = utils.expected_wheels(
|
||||
"spam", "0.1.0", single_python=True, single_arch=is_windows
|
||||
)
|
||||
actual_wheels_normalized = {packaging.utils.parse_wheel_filename(w) for w in actual_wheels}
|
||||
expected_wheels_normalized = {packaging.utils.parse_wheel_filename(w) for w in expected_wheels}
|
||||
assert actual_wheels_normalized == expected_wheels_normalized
|
||||
@@ -1,4 +1,5 @@
|
||||
from .base import TestProject
|
||||
from .c import new_c_project
|
||||
from .meson import new_meson_project
|
||||
from .setuptools import new_c_project
|
||||
|
||||
__all__ = ("TestProject", "new_c_project")
|
||||
__all__ = ("TestProject", "new_c_project", "new_meson_project")
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import jinja2
|
||||
|
||||
from .base import TestProject
|
||||
|
||||
SPAM_C_TEMPLATE = r"""
|
||||
#include <Python.h>
|
||||
|
||||
@@ -39,70 +35,3 @@ PyMODINIT_FUNC PyInit_spam(void)
|
||||
return PyModule_Create(&moduledef);
|
||||
}
|
||||
"""
|
||||
|
||||
SETUP_PY_TEMPLATE = r"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from setuptools import setup, Extension
|
||||
|
||||
{{ setup_py_add }}
|
||||
|
||||
libraries = []
|
||||
# Emscripten fails if you pass -lc...
|
||||
# See: https://github.com/emscripten-core/emscripten/issues/16680
|
||||
if sys.platform.startswith('linux') and "emscripten" not in os.environ.get("_PYTHON_HOST_PLATFORM", ""):
|
||||
libraries.extend(['m', 'c'])
|
||||
|
||||
|
||||
setup(
|
||||
ext_modules=[Extension(
|
||||
'spam',
|
||||
sources=['spam.c'],
|
||||
libraries=libraries,
|
||||
{{ setup_py_extension_args_add | indent(8) }}
|
||||
)],
|
||||
{{ setup_py_setup_args_add | indent(4) }}
|
||||
)
|
||||
"""
|
||||
|
||||
SETUP_CFG_TEMPLATE = r"""
|
||||
[metadata]
|
||||
name = spam
|
||||
version = 0.1.0
|
||||
|
||||
{{ setup_cfg_add }}
|
||||
"""
|
||||
|
||||
|
||||
def new_c_project(
|
||||
*,
|
||||
spam_c_top_level_add: str = "",
|
||||
spam_c_function_add: str = "",
|
||||
setup_py_add: str = "",
|
||||
setup_py_extension_args_add: str = "",
|
||||
setup_py_setup_args_add: str = "",
|
||||
setup_cfg_add: str = "",
|
||||
) -> TestProject:
|
||||
project = TestProject()
|
||||
|
||||
project.files.update(
|
||||
{
|
||||
"spam.c": jinja2.Template(SPAM_C_TEMPLATE),
|
||||
"setup.py": jinja2.Template(SETUP_PY_TEMPLATE),
|
||||
"setup.cfg": jinja2.Template(SETUP_CFG_TEMPLATE),
|
||||
}
|
||||
)
|
||||
|
||||
project.template_context.update(
|
||||
{
|
||||
"spam_c_top_level_add": spam_c_top_level_add,
|
||||
"spam_c_function_add": spam_c_function_add,
|
||||
"setup_py_add": setup_py_add,
|
||||
"setup_py_extension_args_add": setup_py_extension_args_add,
|
||||
"setup_py_setup_args_add": setup_py_setup_args_add,
|
||||
"setup_cfg_add": setup_cfg_add,
|
||||
}
|
||||
)
|
||||
|
||||
return project
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import jinja2
|
||||
|
||||
from .base import TestProject
|
||||
from .c import SPAM_C_TEMPLATE
|
||||
|
||||
PYPROJECT_TOML_TEMPLATE = r"""
|
||||
[build-system]
|
||||
requires = ["meson-python"]
|
||||
build-backend = "mesonpy"
|
||||
|
||||
[project]
|
||||
name = "spam"
|
||||
version = "0.1.0"
|
||||
|
||||
[tool.cibuildwheel.windows]
|
||||
config-settings = { "setup-args" = "--vsenv" }
|
||||
# building win32 wheels on a 64-bit CI machine with meson-python
|
||||
# requires a few extra steps outside cibuildwheel. See
|
||||
# https://github.com/pypa/cibuildwheel/pull/2718
|
||||
archs = ["auto64"]
|
||||
"""
|
||||
|
||||
MESON_BUILD_TEMPLATE = r"""
|
||||
project('spam', 'c',
|
||||
version: '0.1.0',
|
||||
default_options: ['warning_level=2'],
|
||||
)
|
||||
|
||||
py = import('python').find_installation(pure: false)
|
||||
|
||||
py.extension_module('spam',
|
||||
'spam.c',
|
||||
install: true,
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
def new_meson_project(
|
||||
*,
|
||||
spam_c_top_level_add: str = "",
|
||||
spam_c_function_add: str = "",
|
||||
) -> TestProject:
|
||||
project = TestProject()
|
||||
|
||||
project.files.update(
|
||||
{
|
||||
"spam.c": jinja2.Template(SPAM_C_TEMPLATE),
|
||||
"pyproject.toml": jinja2.Template(PYPROJECT_TOML_TEMPLATE),
|
||||
"meson.build": jinja2.Template(MESON_BUILD_TEMPLATE),
|
||||
}
|
||||
)
|
||||
|
||||
project.template_context.update(
|
||||
{
|
||||
"spam_c_top_level_add": spam_c_top_level_add,
|
||||
"spam_c_function_add": spam_c_function_add,
|
||||
}
|
||||
)
|
||||
|
||||
return project
|
||||
@@ -0,0 +1,71 @@
|
||||
import jinja2
|
||||
|
||||
from .base import TestProject
|
||||
from .c import SPAM_C_TEMPLATE
|
||||
|
||||
SETUP_PY_TEMPLATE = r"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from setuptools import setup, Extension
|
||||
|
||||
{{ setup_py_add }}
|
||||
|
||||
libraries = []
|
||||
# Emscripten fails if you pass -lc...
|
||||
# See: https://github.com/emscripten-core/emscripten/issues/16680
|
||||
if sys.platform.startswith('linux') and "emscripten" not in os.environ.get("_PYTHON_HOST_PLATFORM", ""):
|
||||
libraries.extend(['m', 'c'])
|
||||
|
||||
|
||||
setup(
|
||||
ext_modules=[Extension(
|
||||
'spam',
|
||||
sources=['spam.c'],
|
||||
libraries=libraries,
|
||||
{{ setup_py_extension_args_add | indent(8) }}
|
||||
)],
|
||||
{{ setup_py_setup_args_add | indent(4) }}
|
||||
)
|
||||
"""
|
||||
|
||||
SETUP_CFG_TEMPLATE = r"""
|
||||
[metadata]
|
||||
name = spam
|
||||
version = 0.1.0
|
||||
|
||||
{{ setup_cfg_add }}
|
||||
"""
|
||||
|
||||
|
||||
def new_c_project(
|
||||
*,
|
||||
spam_c_top_level_add: str = "",
|
||||
spam_c_function_add: str = "",
|
||||
setup_py_add: str = "",
|
||||
setup_py_extension_args_add: str = "",
|
||||
setup_py_setup_args_add: str = "",
|
||||
setup_cfg_add: str = "",
|
||||
) -> TestProject:
|
||||
project = TestProject()
|
||||
|
||||
project.files.update(
|
||||
{
|
||||
"spam.c": jinja2.Template(SPAM_C_TEMPLATE),
|
||||
"setup.py": jinja2.Template(SETUP_PY_TEMPLATE),
|
||||
"setup.cfg": jinja2.Template(SETUP_CFG_TEMPLATE),
|
||||
}
|
||||
)
|
||||
|
||||
project.template_context.update(
|
||||
{
|
||||
"spam_c_top_level_add": spam_c_top_level_add,
|
||||
"spam_c_function_add": spam_c_function_add,
|
||||
"setup_py_add": setup_py_add,
|
||||
"setup_py_extension_args_add": setup_py_extension_args_add,
|
||||
"setup_py_setup_args_add": setup_py_setup_args_add,
|
||||
"setup_cfg_add": setup_cfg_add,
|
||||
}
|
||||
)
|
||||
|
||||
return project
|
||||
Reference in New Issue
Block a user