Files
cibuildwheel/cibuildwheel/architecture.py
T

238 lines
8.2 KiB
Python
Raw Normal View History

2021-01-22 09:33:22 -05:00
import platform as platform_module
import re
import shutil
import subprocess
2022-09-12 23:01:56 -04:00
import sys
import typing
2023-04-18 12:38:21 -04:00
from collections.abc import Set
2025-02-12 13:02:30 -05:00
from enum import StrEnum, auto
from typing import Final, Literal
2021-01-22 09:33:22 -05:00
from cibuildwheel import errors
2023-04-18 23:05:34 -04:00
from .typing import PlatformName
2021-01-22 09:33:22 -05:00
2023-05-08 21:05:24 +01:00
PRETTY_NAMES: Final[dict[PlatformName, str]] = {
"linux": "Linux",
"macos": "macOS",
"windows": "Windows",
2024-05-28 05:31:36 -07:00
"pyodide": "Pyodide",
"android": "Android",
"ios": "iOS",
2023-05-08 21:05:24 +01:00
}
2021-01-22 09:33:22 -05:00
ARCH_SYNONYMS: Final[list[dict[PlatformName, str | None]]] = [
{"linux": "x86_64", "macos": "x86_64", "windows": "AMD64", "android": "x86_64"},
{"linux": "i686", "macos": None, "windows": "x86"},
{"linux": "aarch64", "macos": "arm64", "windows": "ARM64", "android": "arm64_v8a"},
]
2022-09-12 23:01:56 -04:00
2021-01-22 09:33:22 -05:00
def arch_synonym(arch: str, from_platform: PlatformName, to_platform: PlatformName) -> str | None:
for arch_synonym_ in ARCH_SYNONYMS:
if arch == arch_synonym_.get(from_platform):
return arch_synonym_.get(to_platform, arch)
return arch
def _check_aarch32_el0() -> bool:
"""Check if running armv7l natively on aarch64 is supported"""
if not sys.platform.startswith("linux"):
return False
if platform_module.machine() != "aarch64":
return False
executable = shutil.which("linux32")
if executable is None:
return False
check = subprocess.run([executable, "uname", "-m"], check=False, capture_output=True, text=True)
return check.returncode == 0 and check.stdout.startswith("armv")
@typing.final
2025-02-12 13:02:30 -05:00
class Architecture(StrEnum):
# mac/linux/android archs
2025-02-12 13:02:30 -05:00
x86_64 = auto()
# linux archs
2025-02-12 13:02:30 -05:00
i686 = auto()
aarch64 = auto()
ppc64le = auto()
s390x = auto()
armv7l = auto()
2025-04-28 18:05:03 +02:00
riscv64 = auto()
2021-01-22 09:33:22 -05:00
# mac archs
2025-02-12 13:02:30 -05:00
universal2 = auto()
arm64 = auto()
2021-01-22 09:33:22 -05:00
# windows archs
2025-02-12 13:02:30 -05:00
x86 = auto()
2021-05-03 11:45:43 -04:00
AMD64 = "AMD64"
ARM64 = "ARM64"
2021-01-22 09:33:22 -05:00
2024-05-28 05:31:36 -07:00
# WebAssembly
2025-02-12 13:02:30 -05:00
wasm32 = auto()
2022-11-26 15:54:08 +00:00
# android archs
arm64_v8a = auto()
# iOS "multiarch" architectures that include both
# the CPU architecture and the ABI.
arm64_iphoneos = auto()
arm64_iphonesimulator = auto()
x86_64_iphonesimulator = auto()
2021-01-22 09:33:22 -05:00
@staticmethod
def parse_config(config: str, platform: PlatformName) -> "set[Architecture]":
2021-01-22 09:33:22 -05:00
result = set()
2021-05-03 11:45:43 -04:00
for arch_str in re.split(r"[\s,]+", config):
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
2021-01-22 09:33:22 -05:00
return result
@staticmethod
def native_arch(platform: PlatformName) -> "Architecture | None":
native_machine = platform_module.machine()
native_architecture = Architecture(native_machine)
2022-09-12 23:01:56 -04:00
# Cross-platform support. Used for --print-build-identifiers or docker builds.
host_platform: PlatformName = (
2022-09-12 23:01:56 -04:00
"windows"
if sys.platform.startswith("win")
else ("macos" if sys.platform.startswith("darwin") else "linux")
)
if platform == "pyodide":
return Architecture.wasm32
elif platform == "ios":
# Can only build for iOS on macOS. The "native" architecture is the
# simulator for the macOS native platform.
if host_platform == "macos":
if native_architecture == Architecture.x86_64:
return Architecture.x86_64_iphonesimulator
else:
return Architecture.arm64_iphonesimulator
else:
return None
2022-09-12 23:01:56 -04:00
# we might need to rename the native arch to the machine we're running
# on, as the same arch can have different names on different platforms
if host_platform != platform:
synonym = arch_synonym(native_machine, host_platform, platform)
if synonym is None:
# can't build anything on this platform
return None
native_architecture = Architecture(synonym)
2024-05-28 05:31:36 -07:00
return native_architecture
@staticmethod
def auto_archs(platform: PlatformName) -> "set[Architecture]":
2024-05-28 05:31:36 -07:00
native_arch = Architecture.native_arch(platform)
if native_arch is None:
return set() # can't build anything on this platform
result = {native_arch}
2022-09-12 23:01:56 -04:00
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)
2021-01-22 09:33:22 -05:00
return result
@staticmethod
def all_archs(platform: PlatformName) -> "set[Architecture]":
all_archs_map = {
"linux": {
Architecture.x86_64,
Architecture.i686,
Architecture.aarch64,
Architecture.ppc64le,
Architecture.s390x,
2024-10-01 16:33:57 +02:00
Architecture.armv7l,
2025-04-28 18:05:03 +02:00
Architecture.riscv64,
},
"macos": {Architecture.x86_64, Architecture.arm64, Architecture.universal2},
"windows": {Architecture.x86, Architecture.AMD64, Architecture.ARM64},
2024-05-28 05:31:36 -07:00
"pyodide": {Architecture.wasm32},
"android": {Architecture.x86_64, Architecture.arm64_v8a},
"ios": {
Architecture.x86_64_iphonesimulator,
Architecture.arm64_iphonesimulator,
Architecture.arm64_iphoneos,
},
}
return all_archs_map[platform]
2021-01-22 09:33:22 -05:00
2021-01-31 20:41:18 -05:00
@staticmethod
def bitness_archs(platform: PlatformName, bitness: Literal["64", "32"]) -> "set[Architecture]":
# 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)
2021-01-31 20:41:18 -05:00
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)
2021-01-31 20:41:18 -05:00
2021-01-22 09:33:22 -05:00
def allowed_architectures_check(
platform: PlatformName,
2023-04-18 12:38:21 -04:00
architectures: Set[Architecture],
2021-01-22 09:33:22 -05:00
) -> None:
allowed_architectures = Architecture.all_archs(platform)
2021-05-03 11:45:43 -04:00
msg = f"{PRETTY_NAMES[platform]} only supports {sorted(allowed_architectures)} at the moment."
2021-01-22 09:33:22 -05:00
2021-05-03 11:45:43 -04:00
if platform != "linux":
msg += " If you want to set emulation architectures on Linux, use CIBW_ARCHS_LINUX instead."
2021-01-22 09:33:22 -05:00
if not architectures <= allowed_architectures:
2021-05-03 11:45:43 -04:00
msg = f"Invalid archs option {architectures}. " + msg
2021-01-22 09:33:22 -05:00
raise ValueError(msg)
if not architectures:
2021-05-03 11:45:43 -04:00
msg = "Empty archs option set. " + msg
2021-01-22 09:33:22 -05:00
raise ValueError(msg)