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)