diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index 0ac2f1a1a..69c164b27 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -75,24 +75,24 @@ class Architecture(StrEnum): def parse_config(config: str, platform: PlatformName) -> "set[Architecture]": result = set() for arch_str in re.split(r"[\s,]+", config): - if arch_str == "auto": - result |= Architecture.auto_archs(platform=platform) - elif arch_str == "native": - native_arch = Architecture.native_arch(platform=platform) - if native_arch: - result.add(native_arch) - elif arch_str == "all": - result |= Architecture.all_archs(platform=platform) - elif arch_str == "auto64": - result |= Architecture.bitness_archs(platform=platform, bitness="64") - elif arch_str == "auto32": - result |= Architecture.bitness_archs(platform=platform, bitness="32") - else: - try: - result.add(Architecture(arch_str)) - except ValueError as e: - msg = f"Invalid architecture '{arch_str}'" - raise errors.ConfigurationError(msg) from e + match arch_str: + case "auto": + result |= Architecture.auto_archs(platform=platform) + case "native": + if native_arch := Architecture.native_arch(platform=platform): + result.add(native_arch) + case "all": + result |= Architecture.all_archs(platform=platform) + case "auto64": + result |= Architecture.bitness_archs(platform=platform, bitness="64") + case "auto32": + result |= Architecture.bitness_archs(platform=platform, bitness="32") + case _: + try: + result.add(Architecture(arch_str)) + except ValueError as e: + msg = f"Invalid architecture '{arch_str}'" + raise errors.ConfigurationError(msg) from e return result @staticmethod @@ -142,19 +142,12 @@ def auto_archs(platform: PlatformName) -> "set[Architecture]": return set() # can't build anything on this platform result = {native_arch} - if platform == "linux": - if Architecture.x86_64 in result: - # x86_64 machines can run i686 containers - result.add(Architecture.i686) - elif Architecture.aarch64 in result and _check_aarch32_el0(): - result.add(Architecture.armv7l) - - elif platform == "windows" and Architecture.AMD64 in result: - result.add(Architecture.x86) - - elif platform == "ios" and native_arch == Architecture.arm64_iphonesimulator: - # Also build the device wheel if we're on ARM64. - result.add(Architecture.arm64_iphoneos) + match platform: + case "windows" if Architecture.AMD64 in result: + result.add(Architecture.x86) + case "ios" if native_arch == Architecture.arm64_iphonesimulator: + # Also build the device wheel if we're on ARM64. + result.add(Architecture.arm64_iphoneos) return result @@ -183,14 +176,35 @@ def all_archs(platform: PlatformName) -> "set[Architecture]": @staticmethod def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> "set[Architecture]": - archs_32 = {Architecture.i686, Architecture.x86, Architecture.armv7l} - auto_archs = Architecture.auto_archs(platform) - - if bitness == "64": - return auto_archs - archs_32 - if bitness == "32": - return auto_archs & archs_32 - typing.assert_never(bitness) + # This map maps 64-bit architectures to their 32-bit equivalents. + archs_map = { + Architecture.x86_64: Architecture.i686, + Architecture.AMD64: Architecture.x86, + Architecture.aarch64: Architecture.armv7l, + } + native_arch = Architecture.native_arch(platform) + + if native_arch is None: + return set() # can't build anything on this platform + + if native_arch == Architecture.wasm32: + return {native_arch} if bitness == "32" else set() + + match bitness: + case "64": + return {native_arch} if native_arch not in archs_map.values() else set() + case "32": + if native_arch in archs_map.values(): + return {native_arch} + elif native_arch in archs_map and platform in {"linux", "windows"}: + if native_arch == Architecture.aarch64 and not _check_aarch32_el0(): + # If we're on aarch64, skip if we cannot build armv7l wheels. + return set() + return {archs_map[native_arch]} + else: + return set() + case _: + typing.assert_never(bitness) def allowed_architectures_check( diff --git a/docs/options.md b/docs/options.md index c91b0b8eb..bd99997cc 100644 --- a/docs/options.md +++ b/docs/options.md @@ -191,10 +191,10 @@ Options: - Windows: `AMD64` `x86` `ARM64` - Pyodide: `wasm32` - iOS: `arm64_iphoneos` `arm64_iphonesimulator` `x86_64_iphonesimulator` -- `auto`: The default archs for your machine - see the table below. - - `auto64`: Just the 64-bit auto archs - - `auto32`: Just the 32-bit auto archs -- `native`: the native arch of the build machine - Matches [`platform.machine()`](https://docs.python.org/3/library/platform.html#platform.machine). +- `auto`: The recommended archs for your machine - see the table below. +- `auto64`: The 64-bit arch(s) supported by your machine (includes device and simulator for iOS) +- `auto32`: The 32-bit arch supported by your machine +- `native`: the native arch of the build machine - matches [`platform.machine()`](https://docs.python.org/3/library/platform.html#platform.machine). - `all` : expands to all the architectures supported on this OS. You may want to use [`build`](#build-skip) with this option to target specific architectures via build selectors. @@ -205,16 +205,34 @@ Default: `auto` | Runner | `native` | `auto` | `auto64` | `auto32` | |---|---|---|---|---| -| Linux / Intel | `x86_64` | `x86_64` `i686` | `x86_64` | `i686` | -| Windows / Intel | `AMD64` | `AMD64` `x86` | `AMD64` | `x86` | +| Linux / Intel 64-bit | `x86_64` | `x86_64` | `x86_64` | `i686` | +| Linux / Intel 32-bit | `i686` | `i686` | | `i686` | +| Linux / Arm 64-bit | `aarch64` | `aarch64` | `aarch64` | `armv7l`¹ | +| Linux / Arm 32-bit | `armv7l` | `armv7l` | | `armv7l` | +| Windows / Intel 64-bit | `AMD64` | `AMD64` `x86` | `AMD64` | `x86` | +| Windows / Intel 32-bit | `x86` | `x86` | | `x86` | | Windows / ARM64 | `ARM64` | `ARM64` | `ARM64` | | | macOS / Intel | `x86_64` | `x86_64` | `x86_64` | | | macOS / Apple Silicon | `arm64` | `arm64` | `arm64` | | | iOS on macOS / Intel | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` | | | iOS on macOS / Apple Silicon | `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` | +¹: This will only be included if the runner supports it. + If not listed above, `auto` is the same as `native`. +!!! warning + The `auto` option only includes 32-bit architectures if they are + commonly built. `cibuildwheel` 3.0 removed 32-bit Linux builds from `auto`, + and a future release may remove 32-bit Windows builds from `auto` as well. + If you know you need them, please include `auto32`. Note that modern + manylinux image do not support 32-bit builds. If you want to avoid 32-bit + Windows builds today, feel free to use `auto64`. + +!!! note + Pyodide currently ignores the architecture setting, as it always builds for + `wasm32`. + [setup-qemu-action]: https://github.com/docker/setup-qemu-action [binfmt]: https://hub.docker.com/r/tonistiigi/binfmt @@ -238,6 +256,10 @@ This option can also be set using the [command-line option](#command-line) # On an Linux Intel runner with qemu installed, build Intel and ARM wheels [tool.cibuildwheel.linux] archs = ["auto", "aarch64"] + + # Build all 32-bit and 64-bit wheels natively buildable on the image + [tool.cibuildwheel] + archs = ["auto64", "auto32"] ``` !!! tab examples "Environment variables" @@ -250,12 +272,13 @@ This option can also be set using the [command-line option](#command-line) # On an Linux Intel runner with qemu installed, build Intel and ARM wheels CIBW_ARCHS_LINUX: "auto aarch64" + + # Build all 32-bit and 64-bit wheels natively buildable on the image + CIBW_ARCHS: "auto64 auto32" ``` Separate multiple archs with a space. - - It is generally recommended to use the environment variable or command-line option for Linux, as selecting archs often depends on your specific runner having qemu installed. diff --git a/test/utils.py b/test/utils.py index 71071abe0..782934bdf 100644 --- a/test/utils.py +++ b/test/utils.py @@ -175,6 +175,7 @@ def expected_wheels( include_universal2: bool = False, single_python: bool = False, single_arch: bool = False, + full_auto: bool = False, ) -> list[str]: """ Returns the expected wheels from a run of cibuildwheel. @@ -194,7 +195,7 @@ def expected_wheels( architectures = [machine_arch] if not single_arch: - if platform == "linux": + if platform == "linux" and full_auto: if machine_arch == "x86_64": architectures.append("i686") elif ( diff --git a/unit_test/architecture_test.py b/unit_test/architecture_test.py index 934b2655d..98efe91d8 100644 --- a/unit_test/architecture_test.py +++ b/unit_test/architecture_test.py @@ -34,8 +34,8 @@ def test_arch_auto(platform_machine): arch_set = Architecture.auto_archs("linux") expected = { "32": {Architecture.i686}, - "64": {Architecture.x86_64, Architecture.i686}, - "arm": {Architecture.aarch64, Architecture.armv7l}, + "64": {Architecture.x86_64}, + "arm": {Architecture.aarch64}, } assert arch_set == expected[machine_name] @@ -94,5 +94,24 @@ def test_arch_auto_no_aarch32(monkeypatch): arch_set = Architecture.parse_config("auto64", "linux") assert arch_set == {Architecture.aarch64} + monkeypatch.setattr(cibuildwheel.architecture, "_check_aarch32_el0", lambda: True) + arch_set = Architecture.parse_config("auto32", "linux") + assert arch_set == {Architecture.armv7l} + + monkeypatch.setattr(cibuildwheel.architecture, "_check_aarch32_el0", lambda: False) arch_set = Architecture.parse_config("auto32", "linux") - assert len(arch_set) == 0 + assert arch_set == set() + + +def test_arch_native_on_ios(monkeypatch): + monkeypatch.setattr(sys, "platform", "darwin") + monkeypatch.setattr(platform_module, "machine", lambda: "arm64") + arch_set = Architecture.parse_config("native", platform="ios") + assert arch_set == {Architecture.arm64_iphonesimulator} + + +def test_arch_auto_on_ios(monkeypatch): + monkeypatch.setattr(sys, "platform", "darwin") + monkeypatch.setattr(platform_module, "machine", lambda: "arm64") + arch_set = Architecture.parse_config("auto", platform="ios") + assert arch_set == {Architecture.arm64_iphonesimulator, Architecture.arm64_iphoneos} diff --git a/unit_test/main_tests/main_platform_test.py b/unit_test/main_tests/main_platform_test.py index 084207368..620f86e4e 100644 --- a/unit_test/main_tests/main_platform_test.py +++ b/unit_test/main_tests/main_platform_test.py @@ -79,7 +79,7 @@ def test_archs_default(platform, intercepted_build_args): options = intercepted_build_args.args[0] if platform == "linux": - assert options.globals.architectures == {Architecture.x86_64, Architecture.i686} + assert options.globals.architectures == {Architecture.x86_64} elif platform == "windows": assert options.globals.architectures == {Architecture.AMD64, Architecture.x86} else: diff --git a/unit_test/option_prepare_test.py b/unit_test/option_prepare_test.py index 34f26eee6..d2a1a0826 100644 --- a/unit_test/option_prepare_test.py +++ b/unit_test/option_prepare_test.py @@ -51,6 +51,7 @@ def ignore_context_call(*args, **kwargs): @pytest.mark.usefixtures("mock_build_container", "fake_package_dir") def test_build_default_launches(monkeypatch): monkeypatch.setattr(sys, "argv", [*sys.argv, "--platform=linux"]) + monkeypatch.setenv("CIBW_ARCHS", "auto64 auto32") monkeypatch.delenv("CIBW_ENABLE", raising=False) main() @@ -105,6 +106,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path): manylinux-x86_64-image = "manylinux_2_28" musllinux-x86_64-image = "musllinux_1_2" enable = ["pypy", "pypy-eol", "graalpy", "cpython-freethreading"] +archs = ["auto64", "auto32"] # Before Python 3.10, use manylinux2014 [[tool.cibuildwheel.overrides]] diff --git a/unit_test/options_test.py b/unit_test/options_test.py index 9e5a1f089..8bf7e5691 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -25,6 +25,7 @@ [tool.cibuildwheel] build = ["cp38-*", "cp313-*"] skip = ["*musllinux*"] +archs = ["auto64", "auto32"] environment = {FOO="BAR"} test-command = "pyproject"