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()
|
DIR = Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
|
BuildBackend = typing.Literal["setuptools", "meson"]
|
||||||
|
|
||||||
|
|
||||||
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]:
|
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]
|
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
|
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, "")
|
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)
|
project.generate(path)
|
||||||
|
|
||||||
|
|
||||||
@@ -126,8 +134,9 @@ def ci_service_for_config_file(config_file: Path) -> CIService:
|
|||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("config_files", nargs=-1, type=click.Path())
|
@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(
|
def run_example_ci_configs(
|
||||||
config_files: list[str],
|
config_files: list[str], build_backend: BuildBackend = "setuptools"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Test the example configs. If no files are specified, will test
|
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)
|
shell(f"git checkout --orphan {branch_name}", check=True)
|
||||||
|
|
||||||
example_project = Path("example_root")
|
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:
|
for config_file in config_file_paths:
|
||||||
service = ci_service_for_config_file(config_file)
|
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.)
|
(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}
|
### 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.
|
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
|
import pytest
|
||||||
|
|
||||||
from . import utils
|
from . import test_projects, utils
|
||||||
from .test_projects.c import new_c_project
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@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):
|
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_dir = tmp_path / "project"
|
||||||
project.generate(project_dir)
|
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 .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"""
|
SPAM_C_TEMPLATE = r"""
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
|
||||||
@@ -39,70 +35,3 @@ PyMODINIT_FUNC PyInit_spam(void)
|
|||||||
return PyModule_Create(&moduledef);
|
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