Repository: pypa/cibuildwheel

Issue:
Update PyPy versions, add macos arm64 PyPy
### Description

PyPy has released version 7.3.10 which includes macos arm64 versions. This release only has python 3.8 and 3.9.

### Build log

_No response_

### CI config

_No response_


Discussion:
Also CPython 3.11.1 is out - would make a nice update & release. :)

FYI, I'm on vacation this week so if it's up to me, likely will be early next week. Maybe someone else will be faster. :)
Update available in #1371
I'll prepare a PR with macos arm64 PyPy

Resolving patch:
diff --git a/bin/update_pythons.py b/bin/update_pythons.py
--- a/bin/update_pythons.py
+++ b/bin/update_pythons.py
@@ -159,8 +159,8 @@ def update_version_windows(self, spec: Specifier) -> ConfigWinCP:
         )
 
     def update_version_macos(self, spec: Specifier) -> ConfigMacOS:
-        if self.arch != "64":
-            msg = "Other archs not supported yet on macOS"
+        if self.arch not in {"64", "ARM64"}:
+            msg = f"'{self.arch}' arch not supported yet on macOS"
             raise RuntimeError(msg)
 
         releases = [r for r in self.releases if spec.contains(r["python_version"])]
@@ -172,12 +172,14 @@ def update_version_macos(self, spec: Specifier) -> ConfigMacOS:
 
         release = releases[-1]
         version = release["python_version"]
-        identifier = f"pp{version.major}{version.minor}-macosx_x86_64"
+        arch = "x86_64" if self.arch == "64" else self.arch.lower()
+        identifier = f"pp{version.major}{version.minor}-macosx_{arch}"
 
+        arch = "x64" if self.arch == "64" else self.arch.lower()
         (url,) = (
             rf["download_url"]
             for rf in release["files"]
-            if "" in rf["platform"] == "darwin" and rf["arch"] == "x64"
+            if "" in rf["platform"] == "darwin" and rf["arch"] == arch
         )
 
         return ConfigMacOS(
@@ -251,6 +253,7 @@ def __init__(self) -> None:
 
         self.macos_cpython = CPythonVersions()
         self.macos_pypy = PyPyVersions("64")
+        self.macos_pypy_arm64 = PyPyVersions("ARM64")
 
     def update_config(self, config: dict[str, str]) -> None:
         identifier = config["identifier"]
@@ -261,11 +264,14 @@ def update_config(self, config: dict[str, str]) -> None:
         config_update: AnyConfig | None = None
 
         # We need to use ** in update due to MyPy (probably a bug)
-        if "macos" in identifier:
+        if "macosx" in identifier:
             if identifier.startswith("cp"):
                 config_update = self.macos_cpython.update_version_macos(identifier, version, spec)
             elif identifier.startswith("pp"):
-                config_update = self.macos_pypy.update_version_macos(spec)
+                if "macosx_x86_64" in identifier:
+                    config_update = self.macos_pypy.update_version_macos(spec)
+                elif "macosx_arm64" in identifier:
+                    config_update = self.macos_pypy_arm64.update_version_macos(spec)
         elif "win32" in identifier:
             if identifier.startswith("cp"):
                 config_update = self.windows_32.update_version_windows(spec)
diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py
--- a/cibuildwheel/logger.py
+++ b/cibuildwheel/logger.py
@@ -126,6 +126,10 @@ def step_end_with_error(self, error: BaseException | str) -> None:
         self.step_end(success=False)
         self.error(error)
 
+    def quiet(self, message: str) -> None:
+        c = self.colors
+        print(f"{c.gray}{message}{c.end}", file=sys.stderr)
+
     def notice(self, message: str) -> None:
         if self.fold_mode == "github":
             print(f"::notice::{message}\n", file=sys.stderr)
diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py
--- a/cibuildwheel/macos.py
+++ b/cibuildwheel/macos.py
@@ -84,7 +84,31 @@ def get_python_configurations(
     ]
 
     # skip builds as required by BUILD/SKIP
-    return [c for c in python_configurations if build_selector(c.identifier)]
+    python_configurations = [c for c in python_configurations if build_selector(c.identifier)]
+
+    # filter-out some cross-compilation configs with PyPy:
+    # can't build arm64 on x86_64
+    # rosetta allows to build x86_64 on arm64
+    if platform.machine() == "x86_64":
+        python_configurations_before = set(python_configurations)
+        python_configurations = [
+            c
+            for c in python_configurations
+            if not (c.identifier.startswith("pp") and c.identifier.endswith("arm64"))
+        ]
+        removed_elements = python_configurations_before - set(python_configurations)
+        if removed_elements:
+            ids = ", ".join(c.identifier for c in removed_elements)
+            log.quiet(
+                unwrap(
+                    f"""
+                    Note: {ids}  {'was' if len(removed_elements) == 1 else 'were'}
+                    selected, but can't be built on x86_64 so will be skipped automatically.
+                    """
+                )
+            )
+
+    return python_configurations
 
 
 def install_cpython(tmp: Path, version: str, url: str) -> Path:


Test patch:
diff --git a/test/test_macos_archs.py b/test/test_macos_archs.py
--- a/test/test_macos_archs.py
+++ b/test/test_macos_archs.py
@@ -65,7 +65,7 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
     actual_wheels = utils.cibuildwheel_run(
         project_dir,
         add_env={
-            "CIBW_BUILD": "cp39-*",
+            "CIBW_BUILD": "cp39-*" if build_universal2 else "*p39-*",
             "CIBW_TEST_COMMAND": '''python -c "import platform; print('running tests on ' + platform.machine())"''',
             "CIBW_ARCHS": "universal2" if build_universal2 else "x86_64 arm64",
             "CIBW_BUILD_VERBOSITY": "3",
@@ -76,7 +76,8 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
 
     assert DEPLOYMENT_TARGET_TOO_LOW_WARNING not in captured.err
 
-    if platform.machine() == "x86_64":
+    platform_machine = platform.machine()
+    if platform_machine == "x86_64":
         # ensure that tests were run on only x86_64
         assert "running tests on x86_64" in captured.out
         assert "running tests on arm64" not in captured.out
@@ -89,15 +90,24 @@ def test_cross_compiled_test(tmp_path, capfd, build_universal2):
             assert (
                 "While arm64 wheels can be built on x86_64, they cannot be tested" in captured.err
             )
-    elif platform.machine() == "arm64":
+    elif platform_machine == "arm64":
         # ensure that tests were run on both x86_64 and arm64
         assert "running tests on x86_64" in captured.out
         assert "running tests on arm64" in captured.out
+        assert (
+            "While universal2 wheels can be built on x86_64, the arm64 part of them cannot currently be tested"
+            not in captured.err
+        )
+        assert (
+            "While arm64 wheels can be built on x86_64, they cannot be tested" not in captured.err
+        )
 
     if build_universal2:
         expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w and "universal2" in w]
     else:
-        expected_wheels = [w for w in ALL_MACOS_WHEELS if "cp39" in w and "universal2" not in w]
+        expected_wheels = [w for w in ALL_MACOS_WHEELS if "p39-" in w and "universal2" not in w]
+        if platform_machine == "x86_64":
+            expected_wheels = [w for w in expected_wheels if not ("pp39" in w and "arm64" in w)]
 
     assert set(actual_wheels) == set(expected_wheels)
 
diff --git a/test/utils.py b/test/utils.py
--- a/test/utils.py
+++ b/test/utils.py
@@ -177,7 +177,14 @@ def expected_wheels(
 
         if platform == "macos" and machine_arch == "arm64":
             # arm64 macs are only supported by cp38+
-            python_abi_tags = ["cp38-cp38", "cp39-cp39", "cp310-cp310", "cp311-cp311"]
+            python_abi_tags = [
+                "cp38-cp38",
+                "cp39-cp39",
+                "cp310-cp310",
+                "cp311-cp311",
+                "pp38-pypy38_pp73",
+                "pp39-pypy39_pp73",
+            ]
 
     wheels = []