diff --git a/README.md b/README.md
index 5e86ca72b..a2f970fa1 100644
--- a/README.md
+++ b/README.md
@@ -22,20 +22,20 @@ Python wheels are great. Building them across **Mac, Linux, Windows**, on **mult
What does it do?
----------------
-| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux
musllinux x86_64 | manylinux
musllinux i686 | manylinux
musllinux aarch64 | manylinux
musllinux ppc64le | manylinux
musllinux s390x | Pyodide |
-|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|-----|
-| CPython 3.6 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.7 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁴ |
-| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
-| PyPy 3.7 v7.3 | ✅ | N/A | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
-| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
-| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
-| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
+| | macOS Intel | macOS Apple Silicon | Windows 64bit | Windows 32bit | Windows Arm64 | manylinux
musllinux x86_64 | manylinux
musllinux i686 | manylinux
musllinux aarch64 | manylinux
musllinux ppc64le | manylinux
musllinux s390x | musllinux armv7l | Pyodide |
+|----------------|----|-----|-----|-----|-----|----|-----|----|-----|-----|---|-----|
+| CPython 3.6 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.7 | ✅ | N/A | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.8 | ✅ | ✅ | ✅ | ✅ | N/A | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁴ |
+| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | N/A |
+| PyPy 3.7 v7.3 | ✅ | N/A | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A |
+| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A |
+| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A |
+| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A | N/A |
¹ PyPy is only supported for manylinux wheels.
² Windows arm64 support is experimental.
diff --git a/bin/generate_schema.py b/bin/generate_schema.py
index 2c90fe925..41f4ad4cd 100755
--- a/bin/generate_schema.py
+++ b/bin/generate_schema.py
@@ -137,6 +137,9 @@
musllinux-aarch64-image:
type: string
description: Specify alternative manylinux / musllinux container images
+ musllinux-armv7l-image:
+ type: string
+ description: Specify alternative manylinux / musllinux container images
musllinux-i686-image:
type: string
description: Specify alternative manylinux / musllinux container images
diff --git a/bin/update_docker.py b/bin/update_docker.py
index 9be3ae03d..a455afb72 100755
--- a/bin/update_docker.py
+++ b/bin/update_docker.py
@@ -65,6 +65,7 @@ class Image:
Image("musllinux_1_2", "aarch64", "quay.io/pypa/musllinux_1_2_aarch64", None),
Image("musllinux_1_2", "ppc64le", "quay.io/pypa/musllinux_1_2_ppc64le", None),
Image("musllinux_1_2", "s390x", "quay.io/pypa/musllinux_1_2_s390x", None),
+ Image("musllinux_1_2", "armv7l", "quay.io/pypa/musllinux_1_2_armv7l", None),
]
config = configparser.ConfigParser()
diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py
index c6c4b623f..0f622def9 100644
--- a/cibuildwheel/architecture.py
+++ b/cibuildwheel/architecture.py
@@ -37,6 +37,7 @@ class Architecture(Enum):
aarch64 = "aarch64"
ppc64le = "ppc64le"
s390x = "s390x"
+ armv7l = "armv7l"
# mac archs
universal2 = "universal2"
@@ -132,6 +133,7 @@ def all_archs(platform: PlatformName) -> set[Architecture]:
Architecture.aarch64,
Architecture.ppc64le,
Architecture.s390x,
+ Architecture.armv7l,
},
"macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2},
"windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64},
@@ -140,17 +142,15 @@ def all_archs(platform: PlatformName) -> set[Architecture]:
return all_archs_map[platform]
@staticmethod
- # pylint: disable-next=inconsistent-return-statements
def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> set[Architecture]:
- archs_32 = {Architecture.i686, Architecture.x86}
+ archs_32 = {Architecture.i686, Architecture.x86, Architecture.armv7l}
auto_archs = Architecture.auto_archs(platform)
if bitness == "64":
return auto_archs - archs_32
- elif bitness == "32":
+ if bitness == "32":
return auto_archs & archs_32
- else:
- assert_never(bitness)
+ assert_never(bitness)
def allowed_architectures_check(
diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py
index 863536c92..ce6b930d1 100644
--- a/cibuildwheel/linux.py
+++ b/cibuildwheel/linux.py
@@ -35,6 +35,7 @@
Architecture.aarch64: OCIPlatform.ARM64,
Architecture.ppc64le: OCIPlatform.PPC64LE,
Architecture.s390x: OCIPlatform.S390X,
+ Architecture.armv7l: OCIPlatform.ARMV7,
}
diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py
index 312e2f559..0b32fe35b 100644
--- a/cibuildwheel/logger.py
+++ b/cibuildwheel/logger.py
@@ -27,7 +27,8 @@
"musllinux_i686": "musllinux i686",
"musllinux_aarch64": "musllinux aarch64",
"musllinux_ppc64le": "musllinux ppc64le",
- "musllinux_s390x": "manylinux s390x",
+ "musllinux_s390x": "musllinux s390x",
+ "musllinux_armv7l": "musllinux armv7l",
"win32": "Windows 32bit",
"win_amd64": "Windows 64bit",
"win_arm64": "Windows on ARM 64bit",
diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py
index bdd663b30..a93079f14 100644
--- a/cibuildwheel/oci_container.py
+++ b/cibuildwheel/oci_container.py
@@ -38,6 +38,7 @@
class OCIPlatform(Enum):
i386 = "linux/386"
AMD64 = "linux/amd64"
+ ARMV7 = "linux/arm/v7"
ARM64 = "linux/arm64"
PPC64LE = "linux/ppc64le"
S390X = "linux/s390x"
@@ -208,7 +209,11 @@ def _get_platform_args(self, *, oci_platform: OCIPlatform | None = None) -> tupl
"inspect",
self.image,
"--format",
- "{{.Os}}/{{.Architecture}}",
+ (
+ "{{.Os}}/{{.Architecture}}/{{.Variant}}"
+ if len(oci_platform.value.split("/")) == 3
+ else "{{.Os}}/{{.Architecture}}"
+ ),
capture_stdout=True,
).strip()
if image_platform == oci_platform.value:
@@ -235,7 +240,7 @@ def __enter__(self) -> Self:
platform_args = self._get_platform_args()
simulate_32_bit = False
- if self.oci_platform == OCIPlatform.i386:
+ if self.oci_platform in {OCIPlatform.i386, OCIPlatform.ARMV7}:
# If the architecture running the image is already the right one
# or the image entrypoint takes care of enforcing this, then we don't need to
# simulate this
@@ -246,13 +251,14 @@ def __enter__(self) -> Self:
*run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
).strip()
except subprocess.CalledProcessError:
- # The image might have been built with amd64 architecture
- # Let's try that
- platform_args = self._get_platform_args(oci_platform=OCIPlatform.AMD64)
- container_machine = call(
- *run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
- ).strip()
- simulate_32_bit = container_machine != "i686"
+ if self.oci_platform == OCIPlatform.i386:
+ # The image might have been built with amd64 architecture
+ # Let's try that
+ platform_args = self._get_platform_args(oci_platform=OCIPlatform.AMD64)
+ container_machine = call(
+ *run_cmd, *platform_args, self.image, *ctr_cmd, capture_stdout=True
+ ).strip()
+ simulate_32_bit = container_machine not in {"i686", "armv7l", "armv8l"}
shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"]
diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml
index 2c8e1e5c1..0a27238ca 100644
--- a/cibuildwheel/resources/build-platforms.toml
+++ b/cibuildwheel/resources/build-platforms.toml
@@ -102,6 +102,15 @@ python_configurations = [
{ identifier = "cp312-musllinux_s390x", version = "3.12", path_str = "/opt/python/cp312-cp312" },
{ identifier = "cp313-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313" },
{ identifier = "cp313t-musllinux_s390x", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
+ { identifier = "cp36-musllinux_armv7l", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
+ { identifier = "cp37-musllinux_armv7l", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
+ { identifier = "cp38-musllinux_armv7l", version = "3.8", path_str = "/opt/python/cp38-cp38" },
+ { identifier = "cp39-musllinux_armv7l", version = "3.9", path_str = "/opt/python/cp39-cp39" },
+ { identifier = "cp310-musllinux_armv7l", version = "3.10", path_str = "/opt/python/cp310-cp310" },
+ { identifier = "cp311-musllinux_armv7l", version = "3.11", path_str = "/opt/python/cp311-cp311" },
+ { identifier = "cp312-musllinux_armv7l", version = "3.12", path_str = "/opt/python/cp312-cp312" },
+ { identifier = "cp313-musllinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313" },
+ { identifier = "cp313t-musllinux_armv7l", version = "3.13", path_str = "/opt/python/cp313-cp313t" },
]
[macos]
diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json
index 976751a55..8e2508bc9 100644
--- a/cibuildwheel/resources/cibuildwheel.schema.json
+++ b/cibuildwheel/resources/cibuildwheel.schema.json
@@ -312,6 +312,11 @@
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_AARCH64_IMAGE"
},
+ "musllinux-armv7l-image": {
+ "type": "string",
+ "description": "Specify alternative manylinux / musllinux container images",
+ "title": "CIBW_MUSLLINUX_ARMV7L_IMAGE"
+ },
"musllinux-i686-image": {
"type": "string",
"description": "Specify alternative manylinux / musllinux container images",
@@ -542,6 +547,9 @@
"musllinux-aarch64-image": {
"$ref": "#/properties/musllinux-aarch64-image"
},
+ "musllinux-armv7l-image": {
+ "$ref": "#/properties/musllinux-armv7l-image"
+ },
"musllinux-i686-image": {
"$ref": "#/properties/musllinux-i686-image"
},
@@ -630,6 +638,9 @@
"musllinux-aarch64-image": {
"$ref": "#/properties/musllinux-aarch64-image"
},
+ "musllinux-armv7l-image": {
+ "$ref": "#/properties/musllinux-armv7l-image"
+ },
"musllinux-i686-image": {
"$ref": "#/properties/musllinux-i686-image"
},
diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml
index 984af48ea..21bac7a0c 100644
--- a/cibuildwheel/resources/defaults.toml
+++ b/cibuildwheel/resources/defaults.toml
@@ -37,6 +37,7 @@ musllinux-i686-image = "musllinux_1_2"
musllinux-aarch64-image = "musllinux_1_2"
musllinux-ppc64le-image = "musllinux_1_2"
musllinux-s390x-image = "musllinux_1_2"
+musllinux-armv7l-image = "musllinux_1_2"
[tool.cibuildwheel.linux]
diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg
index 9a18d48d4..6a094a8ba 100644
--- a/cibuildwheel/resources/pinned_docker_images.cfg
+++ b/cibuildwheel/resources/pinned_docker_images.cfg
@@ -52,3 +52,6 @@ manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.09.28-3
manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.09.28-3
+[armv7l]
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_armv7l:2024.09.22-4
+
diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py
index b9db4268b..bfbe50c7a 100644
--- a/cibuildwheel/util.py
+++ b/cibuildwheel/util.py
@@ -83,6 +83,7 @@
"aarch64",
"ppc64le",
"s390x",
+ "armv7l",
)
DEFAULT_CIBW_CACHE_PATH: Final[Path] = user_cache_path(appname="cibuildwheel", appauthor="pypa")
diff --git a/docs/options.md b/docs/options.md
index 5cb238784..fa2fb7003 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -288,20 +288,20 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat