From dc30bfb527d471e335c988d8d1b666bb003eda0c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 18 Jun 2026 11:41:17 -0400 Subject: [PATCH] fix: make CIBW_ARCHS parsing case-insensitive and platform-aware (#2920) Architecture name lookup in `parse_config` was a case-sensitive StrEnum lookup. Since the enum carries both lowercase `arm64` (macOS/Android) and uppercase `ARM64` (Windows), `parse_config("arm64", "windows")` silently returned the macOS member and later failed with a confusing "Invalid archs option" error, while lowercase `amd64` failed to parse at all. Resolve names case-insensitively, preferring a member valid for the target platform before falling back to any case-insensitive match (which keeps the clear ConfigurationError for genuinely invalid names). Closes #2373 Assisted-by: ClaudeCode:claude-opus-4.8 --- cibuildwheel/architecture.py | 24 ++++++++++++++++++++---- unit_test/architecture_test.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/cibuildwheel/architecture.py b/cibuildwheel/architecture.py index 4fe43741..55a37caf 100644 --- a/cibuildwheel/architecture.py +++ b/cibuildwheel/architecture.py @@ -107,13 +107,29 @@ class Architecture(StrEnum): case "auto32": result |= cls.bitness_archs(platform=platform, bitness="32") case _: - try: - result.add(cls(arch_str)) - except ValueError as e: + if arch := cls._parse_arch_name(arch_str, platform=platform): + result.add(arch) + else: msg = f"Invalid architecture '{arch_str}'" - raise errors.ConfigurationError(msg) from e + raise errors.ConfigurationError(msg) return result + @classmethod + def _parse_arch_name(cls, arch_str: str, platform: PlatformName) -> Self | None: + """Resolve an architecture name case-insensitively. + + The same value (e.g. "arm64"/"ARM64") can map to different members + depending on the platform, so prefer a member valid for ``platform`` + before falling back to any case-insensitive match. + """ + for arch in cls.all_archs(platform): + if arch.value.lower() == arch_str.lower(): + return arch + for arch in cls: + if arch.value.lower() == arch_str.lower(): + return arch + return None + @classmethod def native_arch(cls, platform: PlatformName) -> Self | None: native_machine = platform_module.machine() diff --git a/unit_test/architecture_test.py b/unit_test/architecture_test.py index 8d16b3d8..337ebe2d 100644 --- a/unit_test/architecture_test.py +++ b/unit_test/architecture_test.py @@ -7,6 +7,7 @@ import sys import pytest import cibuildwheel.architecture +import cibuildwheel.errors from cibuildwheel.architecture import Architecture, arch_synonym TYPE_CHECKING = False @@ -142,3 +143,33 @@ def test_arch_synonym( arch: str, from_platform: PlatformName, to_platform: PlatformName, expected: str | None ) -> None: assert arch_synonym(arch, from_platform, to_platform) == expected + + +@pytest.mark.parametrize( + ("config", "platform", "expected"), + [ + # Case-insensitive, platform-resolved names (issue #2373). + ("arm64", "windows", {Architecture.ARM64}), + ("ARM64", "windows", {Architecture.ARM64}), + ("amd64", "windows", {Architecture.AMD64}), + ("AMD64", "windows", {Architecture.AMD64}), + ("X86", "windows", {Architecture.x86}), + ("x86", "windows", {Architecture.x86}), + # macOS resolves the lowercase arm64 even when given uppercase. + ("ARM64", "macos", {Architecture.arm64}), + ("arm64", "macos", {Architecture.arm64}), + ("X86_64", "macos", {Architecture.x86_64}), + # Multiple, mixed-case archs in one config string. + ("arm64 amd64", "windows", {Architecture.ARM64, Architecture.AMD64}), + ], +) +def test_arch_parse_config_case_insensitive( + config: str, platform: PlatformName, expected: set[Architecture] +) -> None: + assert Architecture.parse_config(config, platform) == expected + + +@pytest.mark.parametrize("platform", ["windows", "macos", "linux"]) +def test_arch_parse_config_invalid(platform: PlatformName) -> None: + with pytest.raises(cibuildwheel.errors.ConfigurationError): + Architecture.parse_config("nonexistent", platform)