Repository: pypa/cibuildwheel

Issue:
Drop linux i686 from auto archs for v3.0 ?
(I am sorry for not thinking to discuss this sooner!)

I realised we have a situation in v3.0 where manylinux images are split - most archs are by default on `manylinux_2_28`, but i686 is still on `manylinux2014`. This is unlikely to remedy itself in the future - i686 is legacy at this point, with even Microsoft dropping support for it in Windows 11.

Should we remove i686 from the `auto` keyword on x86_64 machines? 

I think @henryiii suggested something similar a few weeks back. I'm curious if @mayeut has an opinion on this.


Discussion:
I'd rather not change this now.
Debian is still supporting i686 & there are more than 20% of active manylinux packages still producing i686 wheels.
Changing the default behavior of auto will not break for the vast majority of users but will likely break something in those 20% packages.
If we are considering i686 to be a thing of the past (on any OS), then I'd probably go with a new enable flag to disable by default but not change the meaning of auto arch.
That's cool. I'd be curious how much use those i686 packages are getting, maybe some PyPI download stats can find the answer. I noticed that numpy doesn't post i686 wheels any more. But given you're opposed and we're so close to v3.0, I'm inclined to continue as we are then. 
I wouldn't change the meaning of auto, I would just set auto64 as the default on at least Linux. I wonder how many of those projects actually want 32-bit support and how many just do it because it's the default. Fine not to make a last-minute change though.
Or add a new "native" mode, in case someone runs cibuildwheel from a 32-bit machine
> I wouldn't change the meaning of auto, I would just set auto64 as the default on at least Linux.
> Or add a new "native" mode

I've not checked but native 32 bits should already be supported (although not tested obviously).
Having a "default" that depends on the platform & native arch would make sense.
Something like (so similar to native except on pyodide/ios & maybe android to come):

| Runner | `native` | `default` | `auto` | `auto64` | `auto32` |
|---|---|---|---|---|---|
| Linux / Intel | `x86_64` |`x86_64` |  `x86_64` `i686` | `x86_64` | `i686` |
| Linux / Intel | `i686 ` |`i686 ` | `i686` |  | `i686` |
| Linux / ARM | `aarch64 ` |`aarch64` |  `aarch64` `armv7hf` | `aarch64` | `armv7hf` |
| Linux / ARM | `armv7hf ` |`armv7hf` |  `armv7hf` |  | `armv7hf` |
| Windows / Intel | `AMD64` | `AMD64` | `AMD64` `x86` | `AMD64` | `x86` |
| Windows / Intel | `x86 ` | `x86 ` | `x86` |   | `x86` |
| Windows / ARM64 | `ARM64` | `ARM64` | `ARM64` | `ARM64` | |
| macOS / Intel | `x86_64` | `x86_64` | `x86_64` | `x86_64` |  |
| macOS / Apple Silicon | `arm64` | `arm64` | `arm64` | `arm64` |  |
| iOS on macOS / Intel | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` |  |
| iOS on macOS / Apple Silicon | `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` |

I am a bit hesitant... if we end up with a situation where the 'auto' keyword is not the default, this feels quite confusing to me. I think the important decision is what cibuildwheel does by default, and then the definition of 'auto' can flow from this.

If we wanted to we could make a new keyword like 'all-native' that could include all the archs that are supported by the CPU. 
I don’t think “auto” needs to be the default. Especially if we added a “default”. It would be weird to have “auto” as the default and not “default”, but we could switch to it over time. Also, if someone specifies "auto" explicitly, we shouldn't change what that means, while we can change a default.

We could remove the need for a separate setting if we made “native” in iOS still mean both simulator and device, as it special, as there is no native device platform. then we could move the default to “native”, with the option of somebody setting “auto” to get back to the old default.

I wouldn’t change the default in 3.0, but we could warn that it’s changing soon if that’s what we want to do.
I'm going to propose something, PR incoming.
In thinking about #2453, I had not realised that actually i686 doesn't appear in https://peps.python.org/pep-0011/ - I'm struggling to find what actually is the status of i686 support in CPython.
As noted in https://github.com/pypa/cibuildwheel/pull/2453#discussion_r2134600368 I'm not sure about moving the default away from `auto` and thus making `archs = "auto"` the way to target more architectures.

How about a new keyword `host` - meaning all the architectures the host CPU natively supports. And `host32` for just the 32-bit variants?

| Runner | `native`/`auto` | `host` | `host32` |
|--- |--- |--- |--- |
| Linux / Intel | `x86_64` | `x86_64` `i686` | `i686` |
| Linux / ARM | `aarch64` | `aarch64` `armv7l` | `armv7l` |
| Windows / Intel | `AMD64` | `AMD64` `x86` | `x86` |
| Windows / ARM64 | `ARM64` | `ARM64` | |
| macOS / Intel | `x86_64` | `x86_64` | |
| macOS / Apple Silicon | `arm64` | `arm64` | |
| iOS on macOS / Intel | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` | |
| iOS on macOS / Apple Silicon | `arm64_iphoneos` `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator`| |
I really like `auto32` and `auto64`; we should not require someone who doesn't work on building cibuildwheel to know the names of the architectures; it's really weird that some platforms share the same names (unix and macOS Intel 64-bit), while others are different (Windows), and on ARM they are all different. `auto64` and `auto32` get rid of that and let you just specify what you want. I personally don't bother to look up the names and just use `auto32/auto64` if I want to split the 64-bit and 32-bit builds.

There are two options, I believe:

## Option 1

We move the default to "native". We leave "auto"/"auto64"/"auto32" alone. This doesn't break anyone that is explicitly setting the architectures, it just drops the 32-bit builds from the default.

Not everyone pins cibuildwheel, especially for minor versions, so I like this solution.

The downside is we are kind of relying on the handy coincidence that both 32-bit builds happen to be going away. If there was some platform that had two architectures that were both popular, we might want to build for both by default.

## Option 2

I think we could change the meaning of "auto": it could mean "build the best architecture" available for the current system. The default of "auto" could remain, and anyone setting "auto" explicitly would get the 32-bit win/linux builds removed. "auto32" would remain the same, it would built the best 32-bit architecture available for the current system.

If you want to support both cibuildwheel before and after we introduce this, setting `tool.cibuildwheel.archs = ["auto64", "auto32"]` would be identical before and after this change.

| Runner | `auto` | `auto64` | `auto32` |
|---|---|---|---|
| Linux / Intel 64 | `x86_64` | `x86_64` | `i686` |
| Linux / Intel 32 | `i686 ` | `i686` |  | `i686` |
| Linux / ARM 64 |  `aarch64` | `aarch64` | `armv7hf` |
| Linux / ARM 32 |  `armv7hf` |  | `armv7hf` |
| Windows / Intel 64 | `AMD64` | `AMD64` | `x86` |
| Windows / Intel 32 | `x86` |   | `x86` |
| Windows / ARM64 | `ARM64` | `ARM64` | |
| macOS / Intel |  `x86_64` | `x86_64` |  |
| macOS / Apple Silicon | `arm64` | `arm64` |  |
| iOS on macOS / Intel | `x86_64_iphonesimulator` | `x86_64_iphonesimulator` |  |
| iOS on macOS / Apple Silicon | `arm64_iphoneos` `arm64_iphonesimulator` | `arm64_iphoneos` `arm64_iphonesimulator` | |


---

I originally thought of and liked option 1, but I think I really like option 2.

I don't like introducing new terms (we already have enough), or deprecating/changing the names of terms in heavy use. Even if `"auto"` became just the best arch, instead of all arches, I think `auto32` is still clear. Before, `auto` was the union of `auto64 | auto32`, now (option 2) it is equivalent to `auto64` on a 64-bit system, and `auto32` on a 32-bit system, which I really like.
If everyone likes option 2, do we want to do it in 3.1, or would we like to go ahead and do it now, and extend the RC period a couple of days? I don't like the mixed manylinux images either, so it really is a good time to make such a change if we are all in agreement on how to do it. People upgrading to 3.0 will be more likely to be checking for missing wheels than some minor upgrade, and 3.0's change of the default manylinux images for 64-bit only will possibly confuse people who are not expecting both images to build by default.

BTW, NumPy still makes Win32 wheels, but not 32-bit Linux. Maybe we could do the Linux drop first?
I like option 2 as well :)

So I think we're saying-

> `auto` - the default architectures for this machine, as decided by cibw maintainer team
> `auto64` - the 64-bit architecture for this machine
> `auto32` - the 32-bit architecture variant for this machine, if it exists.
> 
> All the archs that the machine _natively_ supports is `auto64 auto32`.

It's fewer keywords, and when used, it looks like what it does. Nice idea henryiii!

I also like that we keep some flexibility in our definition of `auto` - like, say if we need to do something with x86_64 architecture variants, we can flex our definitions to adapt, which would be harder if the default is `native`.

I'm still interested in doing it now, if we can reach a consensus. Like you say, while users are looking at manylinux images to upgrade to manylinux_2_28, it's a really good time to make a backward-incompatible change.
Hm. I think that dropping Linux 32 bits from default is reasonable. From long time I do not see 32-bits linux python. 

For windows. I know that many people installed 32-bits python on 64-bits system in the past. But do not know if it still happens. 

Maybe the best option could be to check PyPi download stat to check if the feeling, that it is not popular, is correct.
Windows 32-bit is still pretty common as far as what packages provide. NumPy still provides a 32-bit Windows build. IIRC they might have tried to drop it, but I see it there at the moment. Python still provides a 32-bit installer. It's not the default anymore, that changed a while back, but it's still officially provided. So I think it's too soon to drop 32-bit Windows. Windows also isn't a problem, while Linux is, due to it missing from the newer manylinux images.

So I'd be in favor of dropping 32-bit linux from `"auto"` for 3.0. I'd want @mayeut's signoff to makes such a change, though, as the manylinux expert.
I'll try to get some pypi download numbers later today...
> Before, auto was the union of auto64 | auto32, now (option 2) it is equivalent to auto64 on a 64-bit system, and auto32 on a 32-bit system, which I really like.

I'm OK with option2

Here are some raw download stats for last week. I don't know what to make of the `None` system/cpu downloads which are quite high.

sandbox % pypinfo -l 50 --percent -sd -8 --all '' system cpu
Served from cache: False
Data processed: 347.06 GiB
Data billed: 347.06 GiB
Estimated cost: $1.70

| system_name          | cpu                           | percent | download_count |
| -------------------- | ----------------------------- | ------- | -------------- |
| Linux                |                        x86_64 |  83.94% | 13,143,364,262 |
| Linux                |                       aarch64 |   6.70% |  1,048,980,697 |
| Linux                |                          None |   3.52% |    551,221,230 |
| Windows              |                         AMD64 |   2.57% |    402,572,596 |
| None                 |                          None |   1.89% |    296,434,236 |
| Darwin               |                         arm64 |   0.97% |    152,531,177 |
| Darwin               |                        x86_64 |   0.24% |     37,073,703 |
| Windows              |                          None |   0.06% |      9,655,397 |
| Darwin               |                          None |   0.06% |      8,676,662 |
| Windows              |                         ARM64 |   0.01% |      1,667,179 |
| Linux                |                        armv7l |   0.01% |      1,576,664 |
| Linux                |                       ppc64le |   0.01% |      1,078,393 |
| Linux                |                         s390x |   0.01% |      1,037,887 |
| Linux                |                          i686 |   0.00% |        737,600 |
| FreeBSD              |                         amd64 |   0.00% |        649,537 |
| Linux                |                        armv8l |   0.00% |        551,699 |
| Windows              |                           x86 |   0.00% |        395,471 |
| Windows              |                               |   0.00% |        182,580 |
| OS/390               |                         8,561 |   0.00% |         47,494 |
| Linux                |                          mips |   0.00% |         45,791 |
| Linux                |                        armv6l |   0.00% |         38,564 |
| OS/390               |                         3,931 |   0.00% |         35,299 |
| Linux                |                       riscv64 |   0.00% |         34,130 |
| Emscripten           |                        wasm32 |   0.00% |         27,333 |
| CYGWIN_NT-10.0-20348 |                        x86_64 |   0.00% |         26,587 |
| Linux                |                           ppc |   0.00% |         24,350 |
| Darwin               |                    iPhone15,2 |   0.00% |         14,985 |
| Linux                |     arm-unknown-linux-gnueabi |   0.00% |         14,572 |
| Android              |                       aarch64 |   0.00% |         13,522 |
| Linux                |                   loongarch64 |   0.00% |         11,451 |
| MSYS_NT-10.0-20348   |                        x86_64 |   0.00% |         11,283 |
| Linux                |           x86_64-pc-linux-gnu |   0.00% |         10,293 |
| CYGWIN_NT-10.0-19045 |                        x86_64 |   0.00% |         10,283 |
| Linux                |             i686-pc-linux-gnu |   0.00% |         10,280 |
| iOS                  |                         arm64 |   0.00% |         10,034 |
| CYGWIN_NT-10.0-17763 |                        x86_64 |   0.00% |          9,411 |
| OpenBSD              |                         amd64 |   0.00% |          8,940 |
| CYGWIN_NT-10.0-26100 |                        x86_64 |   0.00% |          6,483 |
| SunOS                |                         sun4v |   0.00% |          6,051 |
| Linux                |     aarch64-unknown-linux-gnu |   0.00% |          5,928 |
| SunOS                |                         i86pc |   0.00% |          5,084 |
| Linux                | aarch64-unknown-linux-gnueabi |   0.00% |          4,377 |
| Darwin               |                    iPhone16,1 |   0.00% |          4,269 |
| CYGWIN_NT-10.0-22631 |                        x86_64 |   0.00% |          4,127 |
| Linux                |   powerpc-e500v2-linux-gnuspe |   0.00% |          3,994 |
| FreeBSD              |                          None |   0.00% |          3,990 |
| Darwin               |                    iPhone16,2 |   0.00% |          3,733 |
| Linux                |    arm-cortexa9-linux-gnueabi |   0.00% |          3,519 |
| Darwin               |                    iPhone13,1 |   0.00% |          3,385 |
| Darwin               |                    iPhone13,2 |   0.00% |          3,381 |
| Total                |                               |         | 15,658,849,893 |


Does it mean that windows 32bits is less popular than linux? 
> Does it mean that windows 32bits is less popular than linux?

Maybe... but it's worth remembering that these figures don't compare all that well between OSs, because Linux has a crazy amount of CI running on it, whereas Windows/macOS might be a higher proportion of actual human beings and developer machines. Still, it's a very interesting figure.

> I don't know what to make of the `None` system/cpu downloads which are quite high.

I was curious about this too, I did a similar analysis, but this time using the arch that was mentioned in the filename of manylinux wheels-

<img width="973" alt="Image" src="https://github.com/user-attachments/assets/454b7a50-62bf-4b9a-a360-a5b312855b4e" />

Results look similar to yours, although the i686 proportion is 0.1%, rather than 0.00%.
I'd like to leave Windows 32 there for now, since even for 3.14 a 32-bit download of Python is provided by default on Windows. And proportionally, there are more 32-bit Windows downloads than 32-bit linux (linux just dominates over all, probably due to servers, CI, and HPC clusters). And we aren't running into a strong reason to drop it like linux, and 3.0 is just around the corner. Though I wouldn't oppose dropping it if others felt strongly.
There are more aarch64 downloads than 32-bit on Windows? NumPy, for instance, didn't even provide ARM wheels for Windows till a couple of days ago.



Patch:
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.


Test patch:
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"