Android updates (#2568)

* Add Python 3.14 for Android

* Simplify GitHub Actions workflows

* Update tests

* Allow running `patchelf` even when the environment's `bin` directory is not on the PATH

* Search in both the environment's bin directory and the PATH

* Use `sysconfig.get_path`

* Fix lint warning
This commit is contained in:
Malcolm Smith
2025-09-08 15:58:03 +01:00
committed by GitHub
parent 3f076ba487
commit 0826975570
13 changed files with 149 additions and 167 deletions
-8
View File
@@ -105,14 +105,6 @@ jobs:
/usr/local/share/powershell /usr/local/share/powershell
df -h df -h
# https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
- name: Enable KVM for Android emulator
if: runner.os == 'Linux' && runner.arch == 'X64'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# for oci_container unit tests # for oci_container unit tests
- name: Set up QEMU - name: Set up QEMU
if: runner.os == 'Linux' if: runner.os == 'Linux'
+3 -3
View File
@@ -29,9 +29,9 @@ While cibuildwheel itself requires a recent Python version to run (we support th
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A | | CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A | | CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A | | CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | ✅⁴ | | CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | ✅⁴ |
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | N/A | | CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | ✅⁴ |
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A | | CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | N/A |
| PyPy 3.8 v7.3 | ✅ | ✅ | ✅ | N/A | 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 | N/A | N/A |
| PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | 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 | N/A | N/A |
| PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | N/A | ✅¹ | ✅¹ | ✅¹ | 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 | N/A | N/A |
+40 -69
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import copy
import difflib import difflib
import logging import logging
import operator import operator
@@ -34,47 +33,20 @@ RESOURCES_DIR: Final[Path] = DIR / "cibuildwheel/resources"
ArchStr = Literal["32", "64", "ARM64"] ArchStr = Literal["32", "64", "ARM64"]
class ConfigWinCP(TypedDict): class Config(TypedDict):
identifier: str identifier: str
version: str version: str
arch: str
class ConfigWinPP(TypedDict): class ConfigUrl(Config):
identifier: str
version: str
arch: str
url: str url: str
class ConfigWinGP(TypedDict): class ConfigPyodide(Config):
identifier: str
version: str
url: str
class ConfigApple(TypedDict):
identifier: str
version: str
url: str
class ConfigAndroid(TypedDict):
identifier: str
version: str
url: str
class ConfigPyodide(TypedDict):
identifier: str
version: str
default_pyodide_version: str default_pyodide_version: str
node_version: str node_version: str
AnyConfig = ConfigWinCP | ConfigWinPP | ConfigWinGP | ConfigApple | ConfigAndroid | ConfigPyodide
# The following set of "Versions" classes allow the initial call to the APIs to # The following set of "Versions" classes allow the initial call to the APIs to
# be cached and reused in the `update_version_*` methods. # be cached and reused in the `update_version_*` methods.
@@ -106,7 +78,7 @@ class WindowsVersions:
self.version_dict = {Version(v): v for v in cp_info["versions"]} self.version_dict = {Version(v): v for v in cp_info["versions"]}
def update_version_windows(self, spec: Specifier) -> ConfigWinCP | None: def update_version_windows(self, spec: Specifier) -> Config | None:
# Specifier.filter selects all non pre-releases that match the spec, # Specifier.filter selects all non pre-releases that match the spec,
# unless there are only pre-releases, then it selects pre-releases # unless there are only pre-releases, then it selects pre-releases
# instead (like pip) # instead (like pip)
@@ -121,10 +93,9 @@ class WindowsVersions:
flags = "t" if self.free_threaded else "" flags = "t" if self.free_threaded else ""
version = versions[0] version = versions[0]
identifier = f"cp{version.major}{version.minor}{flags}-{self.arch}" identifier = f"cp{version.major}{version.minor}{flags}-{self.arch}"
return ConfigWinCP( return Config(
identifier=identifier, identifier=identifier,
version=self.version_dict[version], version=self.version_dict[version],
arch=self.arch_str,
) )
@@ -146,7 +117,7 @@ class GraalPyVersions:
self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r] self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r]
def update_version(self, identifier: str, spec: Specifier) -> AnyConfig: def update_version(self, identifier: str, spec: Specifier) -> ConfigUrl:
if "x86_64" in identifier or "amd64" in identifier: if "x86_64" in identifier or "amd64" in identifier:
arch = "x86_64" arch = "x86_64"
elif "arm64" in identifier or "aarch64" in identifier: elif "arm64" in identifier or "aarch64" in identifier:
@@ -172,11 +143,9 @@ class GraalPyVersions:
if "macosx" in identifier: if "macosx" in identifier:
arch = "x86_64" if "x86_64" in identifier else "arm64" arch = "x86_64" if "x86_64" in identifier else "arm64"
config = ConfigApple
platform = "macos" platform = "macos"
elif "win" in identifier: elif "win" in identifier:
arch = "aarch64" if "arm64" in identifier else "x86_64" arch = "aarch64" if "arm64" in identifier else "x86_64"
config = ConfigWinGP
platform = "windows" platform = "windows"
else: else:
msg = "GraalPy provides downloads for macOS and Windows and is included for manylinux" msg = "GraalPy provides downloads for macOS and Windows and is included for manylinux"
@@ -191,7 +160,7 @@ class GraalPyVersions:
and rf["name"].startswith(f"graalpy-{gpversion.major}") and rf["name"].startswith(f"graalpy-{gpversion.major}")
) )
return config( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{version.major}.{version.minor}", version=f"{version.major}.{version.minor}",
url=url, url=url,
@@ -223,7 +192,7 @@ class PyPyVersions:
] ]
return urls[0] if urls else "" return urls[0] if urls else ""
def update_version_windows(self, spec: Specifier) -> ConfigWinCP: def update_version_windows(self, spec: Specifier) -> ConfigUrl:
releases = [r for r in self.releases if spec.contains(r["python_version"])] releases = [r for r in self.releases if spec.contains(r["python_version"])]
releases = sorted(releases, key=operator.itemgetter("pypy_version")) releases = sorted(releases, key=operator.itemgetter("pypy_version"))
releases = [r for r in releases if self.get_arch_file(r)] releases = [r for r in releases if self.get_arch_file(r)]
@@ -239,14 +208,13 @@ class PyPyVersions:
identifier = f"pp{version.major}{version.minor}-{version_arch}" identifier = f"pp{version.major}{version.minor}-{version_arch}"
url = self.get_arch_file(release) url = self.get_arch_file(release)
return ConfigWinPP( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{version.major}.{version.minor}", version=f"{version.major}.{version.minor}",
arch=self.arch,
url=url, url=url,
) )
def update_version_macos(self, spec: Specifier) -> ConfigApple: def update_version_macos(self, spec: Specifier) -> ConfigUrl:
if self.arch not in {"64", "ARM64"}: if self.arch not in {"64", "ARM64"}:
msg = f"'{self.arch}' arch not supported yet on macOS" msg = f"'{self.arch}' arch not supported yet on macOS"
raise RuntimeError(msg) raise RuntimeError(msg)
@@ -270,7 +238,7 @@ class PyPyVersions:
if "" in rf["platform"] == "darwin" and rf["arch"] == arch if "" in rf["platform"] == "darwin" and rf["arch"] == arch
) )
return ConfigApple( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{version.major}.{version.minor}", version=f"{version.major}.{version.minor}",
url=url, url=url,
@@ -298,16 +266,11 @@ class CPythonVersions:
uri = int(release["resource_uri"].rstrip("/").split("/")[-1]) uri = int(release["resource_uri"].rstrip("/").split("/")[-1])
self.versions_dict[version] = uri self.versions_dict[version] = uri
def update_version_macos( def update_version(self, identifier: str, spec: Specifier, file_ident: str) -> ConfigUrl | None:
self, identifier: str, version: Version, spec: Specifier
) -> ConfigApple | None:
# see note above on Specifier.filter # see note above on Specifier.filter
unsorted_versions = spec.filter(self.versions_dict) unsorted_versions = spec.filter(self.versions_dict)
sorted_versions = sorted(unsorted_versions, reverse=True) sorted_versions = sorted(unsorted_versions, reverse=True)
macver = "x10.9" if version <= Version("3.8.9999") else "11"
file_ident = f"macos{macver}.pkg"
for new_version in sorted_versions: for new_version in sorted_versions:
# Find the first patch version that contains the requested file # Find the first patch version that contains the requested file
uri = self.versions_dict[new_version] uri = self.versions_dict[new_version]
@@ -319,7 +282,7 @@ class CPythonVersions:
urls = [rf["url"] for rf in file_info if file_ident in rf["url"]] urls = [rf["url"] for rf in file_info if file_ident in rf["url"]]
if urls: if urls:
return ConfigApple( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=f"{new_version.major}.{new_version.minor}", version=f"{new_version.major}.{new_version.minor}",
url=urls[0], url=urls[0],
@@ -327,9 +290,17 @@ class CPythonVersions:
return None return None
def update_version_macos(
self, identifier: str, version: Version, spec: Specifier
) -> ConfigUrl | None:
macver = "x10.9" if version <= Version("3.8.9999") else "11"
return self.update_version(identifier, spec, f"macos{macver}.pkg")
class AndroidVersions: def update_version_android(self, identifier: str, spec: Specifier) -> ConfigUrl | None:
# This should be replaced with official python.org downloads once they're available. return self.update_version(identifier, spec, android_triplet(identifier))
class MavenVersions:
MAVEN_URL = "https://repo.maven.apache.org/maven2/com/chaquo/python/python" MAVEN_URL = "https://repo.maven.apache.org/maven2/com/chaquo/python/python"
def __init__(self) -> None: def __init__(self) -> None:
@@ -343,18 +314,16 @@ class AndroidVersions:
assert isinstance(version_str, str), version_str assert isinstance(version_str, str), version_str
self.versions.append(Version(version_str)) self.versions.append(Version(version_str))
def update_version_android( def update_version_android(self, identifier: str, spec: Specifier) -> ConfigUrl | None:
self, identifier: str, version: Version, spec: Specifier
) -> ConfigAndroid | None:
sorted_versions = sorted(spec.filter(self.versions), reverse=True) sorted_versions = sorted(spec.filter(self.versions), reverse=True)
# Return a config using the highest version for the given specifier. # Return a config using the highest version for the given specifier.
if sorted_versions: if sorted_versions:
max_version = sorted_versions[0] max_version = sorted_versions[0]
triplet = android_triplet(identifier) triplet = android_triplet(identifier)
return ConfigAndroid( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=str(version), version=f"{max_version.major}.{max_version.minor}",
url=f"{self.MAVEN_URL}/{max_version}/python-{max_version}-{triplet}.tar.gz", url=f"{self.MAVEN_URL}/{max_version}/python-{max_version}-{triplet}.tar.gz",
) )
else: else:
@@ -390,11 +359,11 @@ class CPythonIOSVersions:
if filename.endswith("-iOS-support"): if filename.endswith("-iOS-support"):
self.versions_dict[version][int(build[1:])] = asset["browser_download_url"] self.versions_dict[version][int(build[1:])] = asset["browser_download_url"]
def update_version_ios(self, identifier: str, version: Version) -> ConfigApple | None: def update_version_ios(self, identifier: str, version: Version) -> ConfigUrl | None:
# Return a config using the highest build number for the given version. # Return a config using the highest build number for the given version.
urls = [url for _, url in sorted(self.versions_dict.get(version, {}).items())] urls = [url for _, url in sorted(self.versions_dict.get(version, {}).items())]
if urls: if urls:
return ConfigApple( return ConfigUrl(
identifier=identifier, identifier=identifier,
version=str(version), version=str(version),
url=urls[-1], url=urls[-1],
@@ -450,11 +419,11 @@ class AllVersions:
self.windows_t_arm64 = WindowsVersions("ARM64", True) self.windows_t_arm64 = WindowsVersions("ARM64", True)
self.windows_pypy_64 = PyPyVersions("64") self.windows_pypy_64 = PyPyVersions("64")
self.macos_cpython = CPythonVersions() self.cpython = CPythonVersions()
self.macos_pypy = PyPyVersions("64") self.macos_pypy = PyPyVersions("64")
self.macos_pypy_arm64 = PyPyVersions("ARM64") self.macos_pypy_arm64 = PyPyVersions("ARM64")
self.android = AndroidVersions() self.maven = MavenVersions()
self.ios_cpython = CPythonIOSVersions() self.ios_cpython = CPythonIOSVersions()
self.graalpy = GraalPyVersions() self.graalpy = GraalPyVersions()
@@ -466,13 +435,12 @@ class AllVersions:
version = Version(config["version"]) version = Version(config["version"])
spec = Specifier(f"=={version.major}.{version.minor}.*") spec = Specifier(f"=={version.major}.{version.minor}.*")
log.info("Reading in %r -> %s @ %s", str(identifier), spec, version) log.info("Reading in %r -> %s @ %s", str(identifier), spec, version)
orig_config = copy.copy(config) config_update: Config | None = None
config_update: AnyConfig | None = None
# We need to use ** in update due to MyPy (probably a bug) # We need to use ** in update due to MyPy (probably a bug)
if "macosx" in identifier: if "macosx" in identifier:
if identifier.startswith("cp"): if identifier.startswith("cp"):
config_update = self.macos_cpython.update_version_macos(identifier, version, spec) config_update = self.cpython.update_version_macos(identifier, version, spec)
elif identifier.startswith("pp"): elif identifier.startswith("pp"):
if "macosx_x86_64" in identifier: if "macosx_x86_64" in identifier:
config_update = self.macos_pypy.update_version_macos(spec) config_update = self.macos_pypy.update_version_macos(spec)
@@ -498,7 +466,10 @@ class AllVersions:
elif "win_arm64" in identifier and identifier.startswith("cp"): elif "win_arm64" in identifier and identifier.startswith("cp"):
config_update = self.windows_arm64.update_version_windows(spec) config_update = self.windows_arm64.update_version_windows(spec)
elif "android" in identifier: elif "android" in identifier:
config_update = self.android.update_version_android(identifier, version, spec) # Python 3.13 is released by Chaquopy on Maven Central.
# Python 3.14 and newer have official releases on python.org.
versions = self.maven if identifier.startswith("cp313") else self.cpython
config_update = versions.update_version_android(identifier, spec)
elif "ios" in identifier: elif "ios" in identifier:
config_update = self.ios_cpython.update_version_ios(identifier, version) config_update = self.ios_cpython.update_version_ios(identifier, version)
elif "pyodide" in identifier: elif "pyodide" in identifier:
@@ -507,10 +478,10 @@ class AllVersions:
) )
assert config_update is not None, f"{identifier} not found!" assert config_update is not None, f"{identifier} not found!"
config.update(**config_update) if config_update != config:
log.info(" Updated %s to %s", config, config_update)
if config != orig_config: config.clear()
log.info(" Updated %s to %s", orig_config, config) config.update(**config_update)
@click.command() @click.command()
+18 -7
View File
@@ -6,7 +6,7 @@ import re
import shlex import shlex
import shutil import shutil
import subprocess import subprocess
import sys import sysconfig
from collections.abc import Iterable, Iterator from collections.abc import Iterable, Iterator
from dataclasses import dataclass from dataclasses import dataclass
from os.path import relpath from os.path import relpath
@@ -205,8 +205,8 @@ def setup_env(
build_env = build_options.environment.as_dictionary(build_env) build_env = build_options.environment.as_dictionary(build_env)
build_env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" build_env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
for command in ["python", "pip"]: for command in ["python", "pip"]:
which = call("which", command, env=build_env, capture_stdout=True).strip() command_path = call("which", command, env=build_env, capture_stdout=True).strip()
if which != f"{venv_dir}/bin/{command}": if command_path != f"{venv_dir}/bin/{command}":
msg = ( msg = (
f"{command} available on PATH doesn't match our installed instance. If you " f"{command} available on PATH doesn't match our installed instance. If you "
f"have modified PATH, ensure that you don't overwrite cibuildwheel's entry " f"have modified PATH, ensure that you don't overwrite cibuildwheel's entry "
@@ -508,17 +508,28 @@ def repair_default(
new_soname = soname_with_hash(src_path) new_soname = soname_with_hash(src_path)
dst_path = libs_dir / new_soname dst_path = libs_dir / new_soname
shutil.copyfile(src_path, dst_path) shutil.copyfile(src_path, dst_path)
call("patchelf", "--set-soname", new_soname, dst_path) call(which("patchelf"), "--set-soname", new_soname, dst_path)
for path in paths_to_patch: for path in paths_to_patch:
call("patchelf", "--replace-needed", old_soname, new_soname, path) call(which("patchelf"), "--replace-needed", old_soname, new_soname, path)
call( call(
"patchelf", which("patchelf"),
"--set-rpath", "--set-rpath",
f"${{ORIGIN}}/{relpath(libs_dir, path.parent)}", f"${{ORIGIN}}/{relpath(libs_dir, path.parent)}",
path, path,
) )
call(sys.executable, "-m", "wheel", "pack", unpacked_dir, "-d", repaired_wheel_dir) call(which("wheel"), "pack", unpacked_dir, "-d", repaired_wheel_dir)
# If cibuildwheel was called without activating its environment, its scripts directory
# will not be on the PATH.
def which(cmd: str) -> str:
scripts_dir = sysconfig.get_path("scripts")
result = shutil.which(cmd, path=scripts_dir + os.pathsep + os.environ["PATH"])
if result is None:
msg = f"Couldn't find {cmd!r} in {scripts_dir} or on the PATH"
raise errors.FatalError(msg)
return result
def elf_file_filter(paths: Iterable[Path]) -> Iterator[tuple[Path, ELFFile]]: def elf_file_filter(paths: Iterable[Path]) -> Iterator[tuple[Path, ELFFile]]:
+8 -1
View File
@@ -54,10 +54,17 @@ def get_nuget_args(
@dataclasses.dataclass(frozen=True, kw_only=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
arch: str
identifier: str identifier: str
url: str | None = None url: str | None = None
@property
def arch(self) -> str:
return {
"win32": "32",
"win_amd64": "64",
"win_arm64": "ARM64",
}[self.identifier.split("-")[-1]]
def all_python_configurations() -> list[PythonConfiguration]: def all_python_configurations() -> list[PythonConfiguration]:
config_dicts = resources.read_python_configs("windows") config_dicts = resources.read_python_configs("windows")
+33 -31
View File
@@ -185,37 +185,37 @@ python_configurations = [
[windows] [windows]
python_configurations = [ python_configurations = [
{ identifier = "cp38-win32", version = "3.8.10", arch = "32" }, { identifier = "cp38-win32", version = "3.8.10" },
{ identifier = "cp38-win_amd64", version = "3.8.10", arch = "64" }, { identifier = "cp38-win_amd64", version = "3.8.10" },
{ identifier = "cp39-win32", version = "3.9.13", arch = "32" }, { identifier = "cp39-win32", version = "3.9.13" },
{ identifier = "cp39-win_amd64", version = "3.9.13", arch = "64" }, { identifier = "cp39-win_amd64", version = "3.9.13" },
{ identifier = "cp310-win32", version = "3.10.11", arch = "32" }, { identifier = "cp310-win32", version = "3.10.11" },
{ identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" }, { identifier = "cp310-win_amd64", version = "3.10.11" },
{ identifier = "cp311-win32", version = "3.11.9", arch = "32" }, { identifier = "cp311-win32", version = "3.11.9" },
{ identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" }, { identifier = "cp311-win_amd64", version = "3.11.9" },
{ identifier = "cp312-win32", version = "3.12.10", arch = "32" }, { identifier = "cp312-win32", version = "3.12.10" },
{ identifier = "cp312-win_amd64", version = "3.12.10", arch = "64" }, { identifier = "cp312-win_amd64", version = "3.12.10" },
{ identifier = "cp313-win32", version = "3.13.7", arch = "32" }, { identifier = "cp313-win32", version = "3.13.7" },
{ identifier = "cp313t-win32", version = "3.13.7", arch = "32" }, { identifier = "cp313t-win32", version = "3.13.7" },
{ identifier = "cp313-win_amd64", version = "3.13.7", arch = "64" }, { identifier = "cp313-win_amd64", version = "3.13.7" },
{ identifier = "cp313t-win_amd64", version = "3.13.7", arch = "64" }, { identifier = "cp313t-win_amd64", version = "3.13.7" },
{ identifier = "cp314-win32", version = "3.14.0-rc2", arch = "32" }, { identifier = "cp314-win32", version = "3.14.0-rc2" },
{ identifier = "cp314t-win32", version = "3.14.0-rc2", arch = "32" }, { identifier = "cp314t-win32", version = "3.14.0-rc2" },
{ identifier = "cp314-win_amd64", version = "3.14.0-rc2", arch = "64" }, { identifier = "cp314-win_amd64", version = "3.14.0-rc2" },
{ identifier = "cp314t-win_amd64", version = "3.14.0-rc2", arch = "64" }, { identifier = "cp314t-win_amd64", version = "3.14.0-rc2" },
{ identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" }, { identifier = "cp39-win_arm64", version = "3.9.10" },
{ identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" }, { identifier = "cp310-win_arm64", version = "3.10.11" },
{ identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" }, { identifier = "cp311-win_arm64", version = "3.11.9" },
{ identifier = "cp312-win_arm64", version = "3.12.10", arch = "ARM64" }, { identifier = "cp312-win_arm64", version = "3.12.10" },
{ identifier = "cp313-win_arm64", version = "3.13.7", arch = "ARM64" }, { identifier = "cp313-win_arm64", version = "3.13.7" },
{ identifier = "cp313t-win_arm64", version = "3.13.7", arch = "ARM64" }, { identifier = "cp313t-win_arm64", version = "3.13.7" },
{ identifier = "cp314-win_arm64", version = "3.14.0-rc2", arch = "ARM64" }, { identifier = "cp314-win_arm64", version = "3.14.0-rc2" },
{ identifier = "cp314t-win_arm64", version = "3.14.0-rc2", arch = "ARM64" }, { identifier = "cp314t-win_arm64", version = "3.14.0-rc2" },
{ identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" }, { identifier = "pp38-win_amd64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
{ identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" }, { identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
{ identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip" }, { identifier = "pp310-win_amd64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip" },
{ identifier = "pp311-win_amd64", version = "3.11", arch = "64", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.20-win64.zip" }, { identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.20-win64.zip" },
{ identifier = "gp311_242-win_amd64", version = "3.11", arch = "64", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip" }, { identifier = "gp311_242-win_amd64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip" },
] ]
[pyodide] [pyodide]
@@ -228,6 +228,8 @@ python_configurations = [
python_configurations = [ python_configurations = [
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-aarch64-linux-android.tar.gz" }, { identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-aarch64-linux-android.tar.gz" },
{ identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-x86_64-linux-android.tar.gz" }, { identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.7/python-3.13.7-x86_64-linux-android.tar.gz" },
{ identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc2-aarch64-linux-android.tar.gz" },
{ identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.0/python-3.14.0rc2-x86_64-linux-android.tar.gz" },
] ]
[ios] [ios]
+1 -1
View File
@@ -60,7 +60,7 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat
| Python 3.11 | cp311-macosx_x86_64<br/>cp311-macosx_universal2<br/>cp311-macosx_arm64 | cp311-win_amd64<br/>cp311-win32<br/>cp311-win_arm64 | cp311-manylinux_x86_64<br/>cp311-manylinux_i686<br/>cp311-musllinux_x86_64<br/>cp311-musllinux_i686 | cp311-manylinux_aarch64<br/>cp311-manylinux_ppc64le<br/>cp311-manylinux_s390x<br/>cp311-manylinux_armv7l<br/>cp311-manylinux_riscv64<br/>cp311-musllinux_aarch64<br/>cp311-musllinux_ppc64le<br/>cp311-musllinux_s390x<br/>cp311-musllinux_armv7l<br/>cp311-musllinux_riscv64 | | | | | Python 3.11 | cp311-macosx_x86_64<br/>cp311-macosx_universal2<br/>cp311-macosx_arm64 | cp311-win_amd64<br/>cp311-win32<br/>cp311-win_arm64 | cp311-manylinux_x86_64<br/>cp311-manylinux_i686<br/>cp311-musllinux_x86_64<br/>cp311-musllinux_i686 | cp311-manylinux_aarch64<br/>cp311-manylinux_ppc64le<br/>cp311-manylinux_s390x<br/>cp311-manylinux_armv7l<br/>cp311-manylinux_riscv64<br/>cp311-musllinux_aarch64<br/>cp311-musllinux_ppc64le<br/>cp311-musllinux_s390x<br/>cp311-musllinux_armv7l<br/>cp311-musllinux_riscv64 | | | |
| Python 3.12 | cp312-macosx_x86_64<br/>cp312-macosx_universal2<br/>cp312-macosx_arm64 | cp312-win_amd64<br/>cp312-win32<br/>cp312-win_arm64 | cp312-manylinux_x86_64<br/>cp312-manylinux_i686<br/>cp312-musllinux_x86_64<br/>cp312-musllinux_i686 | cp312-manylinux_aarch64<br/>cp312-manylinux_ppc64le<br/>cp312-manylinux_s390x<br/>cp312-manylinux_armv7l<br/>cp312-manylinux_riscv64<br/>cp312-musllinux_aarch64<br/>cp312-musllinux_ppc64le<br/>cp312-musllinux_s390x<br/>cp312-musllinux_armv7l<br/>cp312-musllinux_riscv64 | | | cp312-pyodide_wasm32 | | Python 3.12 | cp312-macosx_x86_64<br/>cp312-macosx_universal2<br/>cp312-macosx_arm64 | cp312-win_amd64<br/>cp312-win32<br/>cp312-win_arm64 | cp312-manylinux_x86_64<br/>cp312-manylinux_i686<br/>cp312-musllinux_x86_64<br/>cp312-musllinux_i686 | cp312-manylinux_aarch64<br/>cp312-manylinux_ppc64le<br/>cp312-manylinux_s390x<br/>cp312-manylinux_armv7l<br/>cp312-manylinux_riscv64<br/>cp312-musllinux_aarch64<br/>cp312-musllinux_ppc64le<br/>cp312-musllinux_s390x<br/>cp312-musllinux_armv7l<br/>cp312-musllinux_riscv64 | | | cp312-pyodide_wasm32 |
| Python 3.13 | cp313-macosx_x86_64<br/>cp313-macosx_universal2<br/>cp313-macosx_arm64 | cp313-win_amd64<br/>cp313-win32<br/>cp313-win_arm64 | cp313-manylinux_x86_64<br/>cp313-manylinux_i686<br/>cp313-musllinux_x86_64<br/>cp313-musllinux_i686 | cp313-manylinux_aarch64<br/>cp313-manylinux_ppc64le<br/>cp313-manylinux_s390x<br/>cp313-manylinux_armv7l<br/>cp313-manylinux_riscv64<br/>cp313-musllinux_aarch64<br/>cp313-musllinux_ppc64le<br/>cp313-musllinux_s390x<br/>cp313-musllinux_armv7l<br/>cp313-musllinux_riscv64 | cp313-android_arm64_v8a<br/>cp313-android_x86_64 | cp313-ios_arm64_iphoneos<br/>cp313-ios_arm64_iphonesimulator<br/>cp313-ios_x86_64_iphonesimulator | cp313-pyodide_wasm32 | | Python 3.13 | cp313-macosx_x86_64<br/>cp313-macosx_universal2<br/>cp313-macosx_arm64 | cp313-win_amd64<br/>cp313-win32<br/>cp313-win_arm64 | cp313-manylinux_x86_64<br/>cp313-manylinux_i686<br/>cp313-musllinux_x86_64<br/>cp313-musllinux_i686 | cp313-manylinux_aarch64<br/>cp313-manylinux_ppc64le<br/>cp313-manylinux_s390x<br/>cp313-manylinux_armv7l<br/>cp313-manylinux_riscv64<br/>cp313-musllinux_aarch64<br/>cp313-musllinux_ppc64le<br/>cp313-musllinux_s390x<br/>cp313-musllinux_armv7l<br/>cp313-musllinux_riscv64 | cp313-android_arm64_v8a<br/>cp313-android_x86_64 | cp313-ios_arm64_iphoneos<br/>cp313-ios_arm64_iphonesimulator<br/>cp313-ios_x86_64_iphonesimulator | cp313-pyodide_wasm32 |
| Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64 | cp314-ios_arm64_iphoneos<br/>cp314-ios_arm64_iphonesimulator<br/>cp314-ios_x86_64_iphonesimulator | | | Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64 | cp314-android_arm64_v8a<br/>cp314-android_x86_64 | cp314-ios_arm64_iphoneos<br/>cp314-ios_arm64_iphonesimulator<br/>cp314-ios_x86_64_iphonesimulator | |
| PyPy3.8 v7.3 | pp38-macosx_x86_64<br/>pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64<br/>pp38-manylinux_i686 | pp38-manylinux_aarch64 | | | | | PyPy3.8 v7.3 | pp38-macosx_x86_64<br/>pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64<br/>pp38-manylinux_i686 | pp38-manylinux_aarch64 | | | |
| PyPy3.9 v7.3 | pp39-macosx_x86_64<br/>pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64<br/>pp39-manylinux_i686 | pp39-manylinux_aarch64 | | | | | PyPy3.9 v7.3 | pp39-macosx_x86_64<br/>pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64<br/>pp39-manylinux_i686 | pp39-manylinux_aarch64 | | | |
| PyPy3.10 v7.3 | pp310-macosx_x86_64<br/>pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64<br/>pp310-manylinux_i686 | pp310-manylinux_aarch64 | | | | | PyPy3.10 v7.3 | pp310-macosx_x86_64<br/>pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64<br/>pp310-manylinux_i686 | pp310-manylinux_aarch64 | | | |
+8 -5
View File
@@ -205,7 +205,6 @@ It also requires the following commands to be on the `PATH`:
* `curl` * `curl`
* `java` (or set the `JAVA_HOME` environment variable) * `java` (or set the `JAVA_HOME` environment variable)
* `patchelf` (if the wheel links against any external libraries)
### Android version compatibility ### Android version compatibility
@@ -214,6 +213,11 @@ minimum supported [API level](https://developer.android.com/tools/releases/platf
for generated wheels. This will default to the minimum API level of the selected Python for generated wheels. This will default to the minimum API level of the selected Python
version. version.
If the [`repair-wheel-command`](options.md#repair-wheel-command) adds any libraries to
the wheel, then `ANDROID_API_LEVEL` must be at least 24. This is already the default
when building for Python 3.14 and later, but you may need to set it when building for
Python 3.13.
### Build frontend support ### Build frontend support
Android builds only support the `build` frontend. In principle, support for the Android builds only support the `build` frontend. In principle, support for the
@@ -234,10 +238,9 @@ nested virtualization. CI platforms known to meet this requirement are:
* GitHub Actions Linux x86_64 * GitHub Actions Linux x86_64
On Linux, the emulator needs access to the KVM virtualization interface. This may On Linux, the emulator needs access to the KVM virtualization interface. This may
require adding your user to a group, or [changing your udev require adding your user to a group, or changing your udev rules. On GitHub
rules](https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/). Actions, cibuildwheel will do this automatically using the commands shown
If the emulator fails to start, try running `$ANDROID_HOME/emulator/emulator [here](https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/).
-accel-check`.
The Android test environment can't support running shell scripts, so the The Android test environment can't support running shell scripts, so the
[`test-command`](options.md#test-command) must be a Python command  see its [`test-command`](options.md#test-command) must be a Python command  see its
+5 -14
View File
@@ -35,8 +35,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
platform: android platform: android
- os: android-arm - os: android-arm
runs-on: macos-latest # GitHub Actions doesnt currently support the Android emulator on any ARM
# runner. So we build on a non-ARM runner, which will skip the tests.
runs-on: ubuntu-latest
platform: android platform: android
archs: arm64_v8a
- os: ios - os: ios
runs-on: macos-latest runs-on: macos-latest
platform: ios platform: ios
@@ -47,23 +50,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
# GitHub Actions can't currently run the Android emulator on macOS.
- name: Skip Android tests on macOS
if: matrix.os == 'android-arm'
run: echo "CIBW_TEST_COMMAND=" >> "$GITHUB_ENV"
# https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
- name: Enable KVM for Android emulator
if: matrix.os == 'android-intel'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v3.1.4 uses: pypa/cibuildwheel@v3.1.4
env: env:
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }} CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
CIBW_ARCHS: ${{ matrix.archs || 'auto' }}
# Can also be configured directly, using `with:` # Can also be configured directly, using `with:`
# with: # with:
# package-dir: . # package-dir: .
+13 -1
View File
@@ -1,7 +1,9 @@
import os import os
import platform import platform
import re import re
import sys
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path
from shutil import rmtree from shutil import rmtree
from subprocess import CalledProcessError from subprocess import CalledProcessError
from textwrap import dedent from textwrap import dedent
@@ -353,11 +355,21 @@ def test_libcxx(tmp_path, capfd):
project_dir = tmp_path / "project" project_dir = tmp_path / "project"
output_dir = tmp_path / "output" output_dir = tmp_path / "output"
# cibuildwheel should be able to run `patchelf` and `wheel` even when its
# environment's `bin` directory is not on the PATH.
non_venv_path = ":".join(
item for item in os.environ["PATH"].split(":") if Path(item) != Path(sys.executable).parent
)
# A C++ package should include libc++, and the extension module should be able to # A C++ package should include libc++, and the extension module should be able to
# find it using DT_RUNPATH. # find it using DT_RUNPATH.
new_c_project(setup_py_extension_args_add="language='c++'").generate(project_dir) new_c_project(setup_py_extension_args_add="language='c++'").generate(project_dir)
script = 'import spam; print(", ".join(f"{s}: {spam.filter(s)}" for s in ["ham", "spam"]))' script = 'import spam; print(", ".join(f"{s}: {spam.filter(s)}" for s in ["ham", "spam"]))'
cp313_test_env = {**cp313_env, "CIBW_TEST_COMMAND": f"python -c '{script}'"} cp313_test_env = {
**cp313_env,
"CIBW_TEST_COMMAND": f"python -c '{script}'",
"PATH": non_venv_path,
}
# Including external libraries requires API level 24. # Including external libraries requires API level 24.
with pytest.raises(CalledProcessError): with pytest.raises(CalledProcessError):
+1 -3
View File
@@ -100,9 +100,7 @@ def test_ios_platforms(tmp_path, build_config, monkeypatch, capfd):
) )
# The expected wheels were produced. # The expected wheels were produced.
expected_wheels = utils.expected_wheels( expected_wheels = utils.expected_wheels("spam", "0.1.0", platform="ios")
"spam", "0.1.0", platform="ios", python_abi_tags=["cp313-cp313", "cp314-cp314"]
)
assert set(actual_wheels) == set(expected_wheels) assert set(actual_wheels) == set(expected_wheels)
# The user was notified that the cross-build tool was found. # The user was notified that the cross-build tool was found.
+14 -3
View File
@@ -261,9 +261,20 @@ def _expected_wheels(
musllinux_versions = ["musllinux_1_2"] musllinux_versions = ["musllinux_1_2"]
if platform == "pyodide" and python_abi_tags is None: if platform == "pyodide" and python_abi_tags is None:
python_abi_tags = ["cp312-cp312", "cp313-cp313"] python_abi_tags = [
elif platform in {"android", "ios"} and python_abi_tags is None: "cp312-cp312",
python_abi_tags = ["cp313-cp313"] "cp313-cp313",
]
elif platform == "android" and python_abi_tags is None: # noqa: SIM114
python_abi_tags = [
"cp313-cp313",
"cp314-cp314",
]
elif platform == "ios" and python_abi_tags is None:
python_abi_tags = [
"cp313-cp313",
"cp314-cp314",
]
elif python_abi_tags is None: elif python_abi_tags is None:
python_abi_tags = [ python_abi_tags = [
"cp38-cp38", "cp38-cp38",
+5 -21
View File
@@ -27,12 +27,8 @@ def patched_environment(
def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "32"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration(version="irrelevant", identifier="cp314-win32", url=None)
configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):
@@ -43,11 +39,9 @@ def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "64"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration( configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None version="irrelevant", identifier="cp313-win_amd64", url=None
) )
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
@@ -62,11 +56,9 @@ def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
detect_ci_provider() == CIProvider.azure_pipelines, reason="arm64 not recognised on azure" detect_ci_provider() == CIProvider.azure_pipelines, reason="arm64 not recognised on azure"
) )
def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "ARM64"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration( configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None version="irrelevant", identifier="cp312-win_arm64", url=None
) )
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
@@ -78,24 +70,16 @@ def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_env_set(tmp_path: Path) -> None: def test_env_set(tmp_path: Path) -> None:
arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": "x64"} environment = {"VSCMD_ARG_TGT_ARCH": "x64"}
configuration = PythonConfiguration(version="irrelevant", identifier="cp313t-win32", url=None)
configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"): with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"):
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": ""} environment = {"VSCMD_ARG_TGT_ARCH": ""}
configuration = PythonConfiguration(version="irrelevant", identifier="cp312-win32", url=None)
configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):