Test enable groups as specified by PR labels (#2357)

* Allow CIBW_ENABLE to control the wheels built in testing

* Set CIBW_ENABLE using PR labels

* Add docs

* Build everything on the main branch

* Add CIBW_ENABLE=all option

This was mostly for use in the `main` building case, because otherwise it's maybe a bit too easy to forget to update this file when adding an enable group

* Remove dead code

* Make unit tests robust to the value of CIBW_ENABLE

* Fix tests that explicitly choose pypy

* Fix test expectation

* Don't expect impossible wheels in expected_wheels

* Simplify logic in expected_wheels

* CircleCI- run with CIBW_ENABLE=all only on the main branch

* Azure pipelines - run with CIBW_ENABLE=all on main branch

* Update gitlab to run CIBW_ENABLE=all on main

* Set CIBW_ENABLE=all on travis - it only runs on main anyway

* Fix job name error on CircleCI

* Fix tests for graalpy

* Update the test configuration to use the label

* Remove duplication of default value. Make it affect sample build too

* Move the action to after deps are installed

* GraalPy workaround for this assumption

* Make unit test resilient to changing CIBW_ENABLE
This commit is contained in:
Joe Rickerby
2025-05-08 12:27:05 +01:00
committed by GitHub
parent edbd234b82
commit 04c9427a20
15 changed files with 204 additions and 82 deletions
+6
View File
@@ -1,4 +1,5 @@
import json
import os
import subprocess
from collections.abc import Generator
@@ -12,6 +13,9 @@ from cibuildwheel.venv import find_uv
from .utils import EMULATED_ARCHS, platform
# default to just cpython
DEFAULT_CIBW_ENABLE = "cpython-freethreading cpython-prerelease cpython-experimental-riscv64"
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
@@ -29,6 +33,8 @@ def pytest_addoption(parser: pytest.Parser) -> None:
help="macOS cp38 uses the universal2 installer",
)
os.environ.setdefault("CIBW_ENABLE", DEFAULT_CIBW_ENABLE)
def docker_warmup(request: pytest.FixtureRequest) -> None:
machine = request.config.getoption("--run-emulation", default=None)
+16 -9
View File
@@ -1,8 +1,10 @@
import os
import textwrap
import pytest
from cibuildwheel.logger import Logger
from cibuildwheel.selector import EnableGroup
from . import test_projects, utils
@@ -38,11 +40,13 @@ def test(tmp_path, build_frontend_env, capfd):
expected_wheels = utils.expected_wheels("spam", "0.1.0")
assert set(actual_wheels) == set(expected_wheels)
# Verify pip warning not shown
captured = capfd.readouterr()
for stream in (captured.err, captured.out):
assert "WARNING: Running pip as the 'root' user can result" not in stream
assert "A new release of pip available" not in stream
enable_groups = EnableGroup.parse_option_value(os.environ.get("CIBW_ENABLE", ""))
if EnableGroup.GraalPy not in enable_groups:
# Verify pip warning not shown
captured = capfd.readouterr()
for stream in (captured.err, captured.out):
assert "WARNING: Running pip as the 'root' user can result" not in stream
assert "A new release of pip available" not in stream
@pytest.mark.skip(reason="to keep test output clean")
@@ -61,16 +65,19 @@ def test_sample_build(tmp_path, capfd):
logger.step_end()
def test_build_identifiers(tmp_path):
@pytest.mark.parametrize(
"enable_setting", ["", "cpython-prerelease", "pypy", "cpython-freethreading"]
)
def test_build_identifiers(tmp_path, enable_setting, monkeypatch):
project_dir = tmp_path / "project"
basic_project.generate(project_dir)
monkeypatch.setenv("CIBW_ENABLE", enable_setting)
# check that the number of expected wheels matches the number of build
# identifiers
expected_wheels = utils.expected_wheels("spam", "0.1.0")
build_identifiers = utils.cibuildwheel_get_build_identifiers(
project_dir, prerelease_pythons=True
)
build_identifiers = utils.cibuildwheel_get_build_identifiers(project_dir)
assert len(expected_wheels) == len(build_identifiers), (
f"{expected_wheels} vs {build_identifiers}"
)
+17 -18
View File
@@ -35,36 +35,34 @@ def test_abi3(tmp_path):
project_dir = tmp_path / "project"
limited_api_project.generate(project_dir)
single_python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
# free_threaded, GraalPy, and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
# also limit the number of builds for test performance reasons
"CIBW_BUILD": f"cp39-* cp310-* pp310-* gp242-* {single_python_tag}-* cp313t-*"
"CIBW_BUILD": "cp39-* cp310-* pp310-* gp242-* cp312-* cp313t-*",
"CIBW_ENABLE": "all",
},
)
# check that the expected wheels are produced
expected_wheels = utils.expected_wheels("spam", "0.1.0")
if utils.platform == "pyodide":
# there's only 1 possible configuration for pyodide, the single_python_tag one
expected_wheels = [
w.replace(f"{single_python_tag}-{single_python_tag}", "cp310-abi3")
for w in expected_wheels
]
# there's only 1 possible configuration for pyodide, cp312
expected_wheels = utils.expected_wheels("spam", "0.1.0", python_abi_tags=["cp310-abi3"])
else:
expected_wheels = [
w.replace("cp310-cp310", "cp310-abi3")
for w in expected_wheels
if "-cp39" in w
or "-cp310" in w
or "-pp310" in w
or "-graalpy242" in w
or "-cp313t" in w
]
expected_wheels = utils.expected_wheels(
"spam",
"0.1.0",
python_abi_tags=[
"cp39-cp39",
"cp310-abi3", # <-- ABI3, works with 3.10 and 3.12
"cp313-cp313t",
"pp310-pypy310_pp73",
"graalpy311-graalpy242_311_native",
],
)
assert set(actual_wheels) == set(expected_wheels)
@@ -187,6 +185,7 @@ def test_abi_none(tmp_path, capfd):
"CIBW_TEST_COMMAND": f"{utils.invoke_pytest()} ./test",
# limit the number of builds for test performance reasons
"CIBW_BUILD": "cp38-* cp{}{}-* cp313t-* pp310-*".format(*utils.SINGLE_PYTHON_VERSION),
"CIBW_ENABLE": "all",
},
)
+28 -19
View File
@@ -23,6 +23,9 @@ from cibuildwheel.util.file import CIBW_CACHE_PATH
EMULATED_ARCHS: Final[list[str]] = sorted(
arch.value for arch in (Architecture.all_archs("linux") - Architecture.auto_archs("linux"))
)
PYPY_ARCHS = ["x86_64", "i686", "AMD64", "aarch64", "arm64"]
GRAALPY_ARCHS = ["x86_64", "AMD64", "aarch64", "arm64"]
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 12)
_AARCH64_CAN_RUN_ARMV7: Final[bool] = Architecture.aarch64.value not in EMULATED_ARCHS and {
@@ -46,7 +49,8 @@ else:
def cibuildwheel_get_build_identifiers(
project_path: Path, env: dict[str, str] | None = None, *, prerelease_pythons: bool = False
project_path: Path,
env: dict[str, str] | None = None,
) -> list[str]:
"""
Returns the list of build identifiers that cibuildwheel will try to build
@@ -55,9 +59,6 @@ def cibuildwheel_get_build_identifiers(
cmd = [sys.executable, "-m", "cibuildwheel", "--print-build-identifiers", str(project_path)]
if env is None:
env = os.environ.copy()
env["CIBW_ENABLE"] = "cpython-freethreading pypy graalpy"
if prerelease_pythons:
env["CIBW_ENABLE"] += " cpython-prerelease"
cmd_output = subprocess.run(
cmd,
@@ -121,8 +122,6 @@ def cibuildwheel_run(
_update_pip_cache_dir(env)
env["CIBW_ENABLE"] = " ".join(EnableGroup.all_groups())
if single_python:
env["CIBW_BUILD"] = "cp{}{}-*".format(*SINGLE_PYTHON_VERSION)
@@ -222,6 +221,8 @@ def _expected_wheels(
# {python tag} and {abi tag} are closely related to the python interpreter used to build the wheel
# so we'll merge them below as python_abi_tag
enable_groups = EnableGroup.parse_option_value(os.environ.get("CIBW_ENABLE", ""))
if manylinux_versions is None:
manylinux_versions = {
"armv7l": ["manylinux_2_17", "manylinux2014", "manylinux_2_31"],
@@ -243,25 +244,36 @@ def _expected_wheels(
"cp311-cp311",
"cp312-cp312",
"cp313-cp313",
"cp313-cp313t",
]
if machine_arch == "ARM64":
# no CPython 3.8 on Windows ARM64
python_abi_tags.pop(0)
if EnableGroup.CPythonFreeThreading in enable_groups:
python_abi_tags += [
"cp313-cp313t",
]
if machine_arch in ["x86_64", "i686", "AMD64", "aarch64", "arm64"]:
if EnableGroup.PyPy in enable_groups:
python_abi_tags += [
"pp38-pypy38_pp73",
"pp39-pypy39_pp73",
"pp310-pypy310_pp73",
"pp311-pypy311_pp73",
]
if machine_arch in ["x86_64", "AMD64", "aarch64", "arm64"]:
if EnableGroup.GraalPy in enable_groups:
python_abi_tags += [
"graalpy311-graalpy242_311_native",
]
if machine_arch == "ARM64" and platform == "windows":
# no CPython 3.8 on Windows ARM64
python_abi_tags = [t for t in python_abi_tags if not t.startswith("cp38")]
if machine_arch not in PYPY_ARCHS:
python_abi_tags = [tag for tag in python_abi_tags if not tag.startswith("pp")]
if machine_arch not in GRAALPY_ARCHS:
python_abi_tags = [tag for tag in python_abi_tags if not tag.startswith("graalpy")]
if single_python:
python_tag = "cp{}{}-".format(*SINGLE_PYTHON_VERSION)
python_abi_tags = [
@@ -272,13 +284,6 @@ def _expected_wheels(
)
]
if platform == "pyodide":
assert len(python_abi_tags) == 1
python_abi_tag = python_abi_tags[0]
platform_tag = "pyodide_2024_0_wasm32"
yield f"{package_name}-{package_version}-{python_abi_tag}-{platform_tag}.whl"
return
for python_abi_tag in python_abi_tags:
platform_tags = []
@@ -327,6 +332,10 @@ def _expected_wheels(
if include_universal2:
platform_tags.append(f"macosx_{min_macosx.replace('.', '_')}_universal2")
elif platform == "pyodide":
platform_tags = ["pyodide_2024_0_wasm32"]
else:
msg = f"Unsupported platform {platform!r}"
raise Exception(msg)