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:
@@ -105,14 +105,6 @@ jobs:
|
||||
/usr/local/share/powershell
|
||||
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
|
||||
- name: Set up QEMU
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
@@ -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.10 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
|
||||
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
|
||||
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | ✅⁴ |
|
||||
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | N/A |
|
||||
| CPython 3.14 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | N/A |
|
||||
| CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | N/A | N/A | ✅⁴ |
|
||||
| CPython 3.13³ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁵ | ✅ | ✅ | ✅⁴ |
|
||||
| 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.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 |
|
||||
|
||||
+40
-69
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import copy
|
||||
import difflib
|
||||
import logging
|
||||
import operator
|
||||
@@ -34,47 +33,20 @@ RESOURCES_DIR: Final[Path] = DIR / "cibuildwheel/resources"
|
||||
ArchStr = Literal["32", "64", "ARM64"]
|
||||
|
||||
|
||||
class ConfigWinCP(TypedDict):
|
||||
class Config(TypedDict):
|
||||
identifier: str
|
||||
version: str
|
||||
arch: str
|
||||
|
||||
|
||||
class ConfigWinPP(TypedDict):
|
||||
identifier: str
|
||||
version: str
|
||||
arch: str
|
||||
class ConfigUrl(Config):
|
||||
url: str
|
||||
|
||||
|
||||
class ConfigWinGP(TypedDict):
|
||||
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
|
||||
class ConfigPyodide(Config):
|
||||
default_pyodide_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
|
||||
# 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"]}
|
||||
|
||||
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,
|
||||
# unless there are only pre-releases, then it selects pre-releases
|
||||
# instead (like pip)
|
||||
@@ -121,10 +93,9 @@ class WindowsVersions:
|
||||
flags = "t" if self.free_threaded else ""
|
||||
version = versions[0]
|
||||
identifier = f"cp{version.major}{version.minor}{flags}-{self.arch}"
|
||||
return ConfigWinCP(
|
||||
return Config(
|
||||
identifier=identifier,
|
||||
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]
|
||||
|
||||
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:
|
||||
arch = "x86_64"
|
||||
elif "arm64" in identifier or "aarch64" in identifier:
|
||||
@@ -172,11 +143,9 @@ class GraalPyVersions:
|
||||
|
||||
if "macosx" in identifier:
|
||||
arch = "x86_64" if "x86_64" in identifier else "arm64"
|
||||
config = ConfigApple
|
||||
platform = "macos"
|
||||
elif "win" in identifier:
|
||||
arch = "aarch64" if "arm64" in identifier else "x86_64"
|
||||
config = ConfigWinGP
|
||||
platform = "windows"
|
||||
else:
|
||||
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}")
|
||||
)
|
||||
|
||||
return config(
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{version.major}.{version.minor}",
|
||||
url=url,
|
||||
@@ -223,7 +192,7 @@ class PyPyVersions:
|
||||
]
|
||||
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 = sorted(releases, key=operator.itemgetter("pypy_version"))
|
||||
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}"
|
||||
url = self.get_arch_file(release)
|
||||
|
||||
return ConfigWinPP(
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{version.major}.{version.minor}",
|
||||
arch=self.arch,
|
||||
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"}:
|
||||
msg = f"'{self.arch}' arch not supported yet on macOS"
|
||||
raise RuntimeError(msg)
|
||||
@@ -270,7 +238,7 @@ class PyPyVersions:
|
||||
if "" in rf["platform"] == "darwin" and rf["arch"] == arch
|
||||
)
|
||||
|
||||
return ConfigApple(
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{version.major}.{version.minor}",
|
||||
url=url,
|
||||
@@ -298,16 +266,11 @@ class CPythonVersions:
|
||||
uri = int(release["resource_uri"].rstrip("/").split("/")[-1])
|
||||
self.versions_dict[version] = uri
|
||||
|
||||
def update_version_macos(
|
||||
self, identifier: str, version: Version, spec: Specifier
|
||||
) -> ConfigApple | None:
|
||||
def update_version(self, identifier: str, spec: Specifier, file_ident: str) -> ConfigUrl | None:
|
||||
# see note above on Specifier.filter
|
||||
unsorted_versions = spec.filter(self.versions_dict)
|
||||
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:
|
||||
# Find the first patch version that contains the requested file
|
||||
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"]]
|
||||
if urls:
|
||||
return ConfigApple(
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{new_version.major}.{new_version.minor}",
|
||||
url=urls[0],
|
||||
@@ -327,9 +290,17 @@ class CPythonVersions:
|
||||
|
||||
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:
|
||||
# This should be replaced with official python.org downloads once they're available.
|
||||
def update_version_android(self, identifier: str, spec: Specifier) -> ConfigUrl | None:
|
||||
return self.update_version(identifier, spec, android_triplet(identifier))
|
||||
|
||||
|
||||
class MavenVersions:
|
||||
MAVEN_URL = "https://repo.maven.apache.org/maven2/com/chaquo/python/python"
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -343,18 +314,16 @@ class AndroidVersions:
|
||||
assert isinstance(version_str, str), version_str
|
||||
self.versions.append(Version(version_str))
|
||||
|
||||
def update_version_android(
|
||||
self, identifier: str, version: Version, spec: Specifier
|
||||
) -> ConfigAndroid | None:
|
||||
def update_version_android(self, identifier: str, spec: Specifier) -> ConfigUrl | None:
|
||||
sorted_versions = sorted(spec.filter(self.versions), reverse=True)
|
||||
|
||||
# Return a config using the highest version for the given specifier.
|
||||
if sorted_versions:
|
||||
max_version = sorted_versions[0]
|
||||
triplet = android_triplet(identifier)
|
||||
return ConfigAndroid(
|
||||
return ConfigUrl(
|
||||
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",
|
||||
)
|
||||
else:
|
||||
@@ -390,11 +359,11 @@ class CPythonIOSVersions:
|
||||
if filename.endswith("-iOS-support"):
|
||||
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.
|
||||
urls = [url for _, url in sorted(self.versions_dict.get(version, {}).items())]
|
||||
if urls:
|
||||
return ConfigApple(
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=str(version),
|
||||
url=urls[-1],
|
||||
@@ -450,11 +419,11 @@ class AllVersions:
|
||||
self.windows_t_arm64 = WindowsVersions("ARM64", True)
|
||||
self.windows_pypy_64 = PyPyVersions("64")
|
||||
|
||||
self.macos_cpython = CPythonVersions()
|
||||
self.cpython = CPythonVersions()
|
||||
self.macos_pypy = PyPyVersions("64")
|
||||
self.macos_pypy_arm64 = PyPyVersions("ARM64")
|
||||
|
||||
self.android = AndroidVersions()
|
||||
self.maven = MavenVersions()
|
||||
self.ios_cpython = CPythonIOSVersions()
|
||||
|
||||
self.graalpy = GraalPyVersions()
|
||||
@@ -466,13 +435,12 @@ class AllVersions:
|
||||
version = Version(config["version"])
|
||||
spec = Specifier(f"=={version.major}.{version.minor}.*")
|
||||
log.info("Reading in %r -> %s @ %s", str(identifier), spec, version)
|
||||
orig_config = copy.copy(config)
|
||||
config_update: AnyConfig | None = None
|
||||
config_update: Config | None = None
|
||||
|
||||
# We need to use ** in update due to MyPy (probably a bug)
|
||||
if "macosx" in identifier:
|
||||
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"):
|
||||
if "macosx_x86_64" in identifier:
|
||||
config_update = self.macos_pypy.update_version_macos(spec)
|
||||
@@ -498,7 +466,10 @@ class AllVersions:
|
||||
elif "win_arm64" in identifier and identifier.startswith("cp"):
|
||||
config_update = self.windows_arm64.update_version_windows(spec)
|
||||
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:
|
||||
config_update = self.ios_cpython.update_version_ios(identifier, version)
|
||||
elif "pyodide" in identifier:
|
||||
@@ -507,10 +478,10 @@ class AllVersions:
|
||||
)
|
||||
|
||||
assert config_update is not None, f"{identifier} not found!"
|
||||
config.update(**config_update)
|
||||
|
||||
if config != orig_config:
|
||||
log.info(" Updated %s to %s", orig_config, config)
|
||||
if config_update != config:
|
||||
log.info(" Updated %s to %s", config, config_update)
|
||||
config.clear()
|
||||
config.update(**config_update)
|
||||
|
||||
|
||||
@click.command()
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
from collections.abc import Iterable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from os.path import relpath
|
||||
@@ -205,8 +205,8 @@ def setup_env(
|
||||
build_env = build_options.environment.as_dictionary(build_env)
|
||||
build_env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
for command in ["python", "pip"]:
|
||||
which = call("which", command, env=build_env, capture_stdout=True).strip()
|
||||
if which != f"{venv_dir}/bin/{command}":
|
||||
command_path = call("which", command, env=build_env, capture_stdout=True).strip()
|
||||
if command_path != f"{venv_dir}/bin/{command}":
|
||||
msg = (
|
||||
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 "
|
||||
@@ -508,17 +508,28 @@ def repair_default(
|
||||
new_soname = soname_with_hash(src_path)
|
||||
dst_path = libs_dir / new_soname
|
||||
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:
|
||||
call("patchelf", "--replace-needed", old_soname, new_soname, path)
|
||||
call(which("patchelf"), "--replace-needed", old_soname, new_soname, path)
|
||||
call(
|
||||
"patchelf",
|
||||
which("patchelf"),
|
||||
"--set-rpath",
|
||||
f"${{ORIGIN}}/{relpath(libs_dir, path.parent)}",
|
||||
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]]:
|
||||
|
||||
@@ -54,10 +54,17 @@ def get_nuget_args(
|
||||
@dataclasses.dataclass(frozen=True, kw_only=True)
|
||||
class PythonConfiguration:
|
||||
version: str
|
||||
arch: str
|
||||
identifier: str
|
||||
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]:
|
||||
config_dicts = resources.read_python_configs("windows")
|
||||
|
||||
@@ -185,37 +185,37 @@ python_configurations = [
|
||||
|
||||
[windows]
|
||||
python_configurations = [
|
||||
{ identifier = "cp38-win32", version = "3.8.10", arch = "32" },
|
||||
{ identifier = "cp38-win_amd64", version = "3.8.10", arch = "64" },
|
||||
{ identifier = "cp39-win32", version = "3.9.13", arch = "32" },
|
||||
{ identifier = "cp39-win_amd64", version = "3.9.13", arch = "64" },
|
||||
{ identifier = "cp310-win32", version = "3.10.11", arch = "32" },
|
||||
{ identifier = "cp310-win_amd64", version = "3.10.11", arch = "64" },
|
||||
{ identifier = "cp311-win32", version = "3.11.9", arch = "32" },
|
||||
{ identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" },
|
||||
{ identifier = "cp312-win32", version = "3.12.10", arch = "32" },
|
||||
{ identifier = "cp312-win_amd64", version = "3.12.10", arch = "64" },
|
||||
{ identifier = "cp313-win32", version = "3.13.7", arch = "32" },
|
||||
{ identifier = "cp313t-win32", version = "3.13.7", arch = "32" },
|
||||
{ identifier = "cp313-win_amd64", version = "3.13.7", arch = "64" },
|
||||
{ identifier = "cp313t-win_amd64", version = "3.13.7", arch = "64" },
|
||||
{ identifier = "cp314-win32", version = "3.14.0-rc2", arch = "32" },
|
||||
{ identifier = "cp314t-win32", version = "3.14.0-rc2", arch = "32" },
|
||||
{ identifier = "cp314-win_amd64", version = "3.14.0-rc2", arch = "64" },
|
||||
{ identifier = "cp314t-win_amd64", version = "3.14.0-rc2", arch = "64" },
|
||||
{ identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" },
|
||||
{ identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" },
|
||||
{ identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" },
|
||||
{ identifier = "cp312-win_arm64", version = "3.12.10", arch = "ARM64" },
|
||||
{ identifier = "cp313-win_arm64", version = "3.13.7", arch = "ARM64" },
|
||||
{ identifier = "cp313t-win_arm64", version = "3.13.7", arch = "ARM64" },
|
||||
{ identifier = "cp314-win_arm64", version = "3.14.0-rc2", arch = "ARM64" },
|
||||
{ identifier = "cp314t-win_arm64", version = "3.14.0-rc2", arch = "ARM64" },
|
||||
{ identifier = "pp38-win_amd64", version = "3.8", arch = "64", 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 = "pp310-win_amd64", version = "3.10", arch = "64", 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 = "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 = "cp38-win32", version = "3.8.10" },
|
||||
{ identifier = "cp38-win_amd64", version = "3.8.10" },
|
||||
{ identifier = "cp39-win32", version = "3.9.13" },
|
||||
{ identifier = "cp39-win_amd64", version = "3.9.13" },
|
||||
{ identifier = "cp310-win32", version = "3.10.11" },
|
||||
{ identifier = "cp310-win_amd64", version = "3.10.11" },
|
||||
{ identifier = "cp311-win32", version = "3.11.9" },
|
||||
{ identifier = "cp311-win_amd64", version = "3.11.9" },
|
||||
{ identifier = "cp312-win32", version = "3.12.10" },
|
||||
{ identifier = "cp312-win_amd64", version = "3.12.10" },
|
||||
{ identifier = "cp313-win32", version = "3.13.7" },
|
||||
{ identifier = "cp313t-win32", version = "3.13.7" },
|
||||
{ identifier = "cp313-win_amd64", version = "3.13.7" },
|
||||
{ identifier = "cp313t-win_amd64", version = "3.13.7" },
|
||||
{ identifier = "cp314-win32", version = "3.14.0-rc2" },
|
||||
{ identifier = "cp314t-win32", version = "3.14.0-rc2" },
|
||||
{ identifier = "cp314-win_amd64", version = "3.14.0-rc2" },
|
||||
{ identifier = "cp314t-win_amd64", version = "3.14.0-rc2" },
|
||||
{ identifier = "cp39-win_arm64", version = "3.9.10" },
|
||||
{ identifier = "cp310-win_arm64", version = "3.10.11" },
|
||||
{ identifier = "cp311-win_arm64", version = "3.11.9" },
|
||||
{ identifier = "cp312-win_arm64", version = "3.12.10" },
|
||||
{ identifier = "cp313-win_arm64", version = "3.13.7" },
|
||||
{ identifier = "cp313t-win_arm64", version = "3.13.7" },
|
||||
{ identifier = "cp314-win_arm64", version = "3.14.0-rc2" },
|
||||
{ identifier = "cp314t-win_arm64", version = "3.14.0-rc2" },
|
||||
{ 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", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-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", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.20-win64.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]
|
||||
@@ -228,6 +228,8 @@ 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_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]
|
||||
|
||||
+1
-1
@@ -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.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.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.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 | | | |
|
||||
|
||||
+8
-5
@@ -205,7 +205,6 @@ It also requires the following commands to be on the `PATH`:
|
||||
|
||||
* `curl`
|
||||
* `java` (or set the `JAVA_HOME` environment variable)
|
||||
* `patchelf` (if the wheel links against any external libraries)
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
On Linux, the emulator needs access to the KVM virtualization interface. This may
|
||||
require adding your user to a group, or [changing your udev
|
||||
rules](https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/).
|
||||
If the emulator fails to start, try running `$ANDROID_HOME/emulator/emulator
|
||||
-accel-check`.
|
||||
require adding your user to a group, or changing your udev rules. On GitHub
|
||||
Actions, cibuildwheel will do this automatically using the commands shown
|
||||
[here](https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/).
|
||||
|
||||
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
|
||||
|
||||
@@ -35,8 +35,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
platform: android
|
||||
- os: android-arm
|
||||
runs-on: macos-latest
|
||||
# GitHub Actions doesn’t 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
|
||||
archs: arm64_v8a
|
||||
- os: ios
|
||||
runs-on: macos-latest
|
||||
platform: ios
|
||||
@@ -47,23 +50,11 @@ jobs:
|
||||
steps:
|
||||
- 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
|
||||
uses: pypa/cibuildwheel@v3.1.4
|
||||
env:
|
||||
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
|
||||
CIBW_ARCHS: ${{ matrix.archs || 'auto' }}
|
||||
# Can also be configured directly, using `with:`
|
||||
# with:
|
||||
# package-dir: .
|
||||
|
||||
+13
-1
@@ -1,7 +1,9 @@
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from subprocess import CalledProcessError
|
||||
from textwrap import dedent
|
||||
@@ -353,11 +355,21 @@ def test_libcxx(tmp_path, capfd):
|
||||
project_dir = tmp_path / "project"
|
||||
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
|
||||
# find it using DT_RUNPATH.
|
||||
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"]))'
|
||||
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.
|
||||
with pytest.raises(CalledProcessError):
|
||||
|
||||
+1
-3
@@ -100,9 +100,7 @@ def test_ios_platforms(tmp_path, build_config, monkeypatch, capfd):
|
||||
)
|
||||
|
||||
# The expected wheels were produced.
|
||||
expected_wheels = utils.expected_wheels(
|
||||
"spam", "0.1.0", platform="ios", python_abi_tags=["cp313-cp313", "cp314-cp314"]
|
||||
)
|
||||
expected_wheels = utils.expected_wheels("spam", "0.1.0", platform="ios")
|
||||
assert set(actual_wheels) == set(expected_wheels)
|
||||
|
||||
# The user was notified that the cross-build tool was found.
|
||||
|
||||
+14
-3
@@ -261,9 +261,20 @@ def _expected_wheels(
|
||||
musllinux_versions = ["musllinux_1_2"]
|
||||
|
||||
if platform == "pyodide" and python_abi_tags is None:
|
||||
python_abi_tags = ["cp312-cp312", "cp313-cp313"]
|
||||
elif platform in {"android", "ios"} and python_abi_tags is None:
|
||||
python_abi_tags = ["cp313-cp313"]
|
||||
python_abi_tags = [
|
||||
"cp312-cp312",
|
||||
"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:
|
||||
python_abi_tags = [
|
||||
"cp38-cp38",
|
||||
|
||||
@@ -27,12 +27,8 @@ def patched_environment(
|
||||
|
||||
|
||||
def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
arch = "32"
|
||||
environment: dict[str, str] = {}
|
||||
|
||||
configuration = PythonConfiguration(
|
||||
version="irrelevant", arch=arch, identifier="irrelevant", url=None
|
||||
)
|
||||
configuration = PythonConfiguration(version="irrelevant", identifier="cp314-win32", url=None)
|
||||
|
||||
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, 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:
|
||||
arch = "64"
|
||||
environment: dict[str, str] = {}
|
||||
|
||||
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)
|
||||
@@ -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"
|
||||
)
|
||||
def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
arch = "ARM64"
|
||||
environment: dict[str, str] = {}
|
||||
|
||||
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)
|
||||
@@ -78,24 +70,16 @@ def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
|
||||
def test_env_set(tmp_path: Path) -> None:
|
||||
arch = "32"
|
||||
environment = {"VSCMD_ARG_TGT_ARCH": "x64"}
|
||||
|
||||
configuration = PythonConfiguration(
|
||||
version="irrelevant", arch=arch, identifier="irrelevant", url=None
|
||||
)
|
||||
configuration = PythonConfiguration(version="irrelevant", identifier="cp313t-win32", url=None)
|
||||
|
||||
with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"):
|
||||
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
|
||||
|
||||
|
||||
def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
arch = "32"
|
||||
environment = {"VSCMD_ARG_TGT_ARCH": ""}
|
||||
|
||||
configuration = PythonConfiguration(
|
||||
version="irrelevant", arch=arch, identifier="irrelevant", url=None
|
||||
)
|
||||
configuration = PythonConfiguration(version="irrelevant", identifier="cp312-win32", url=None)
|
||||
|
||||
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
|
||||
with patched_environment(monkeypatch, environment):
|
||||
|
||||
Reference in New Issue
Block a user