Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/CI-wheels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ jobs:
name: wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl

build_wheels_android:
name: Build Android wheels
runs-on: ubuntu-26.04

steps:
- uses: actions/checkout@v7

- name: Build Android wheels

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this working straight on ubuntu-26.04 while we have to setup QEMU for normal linux wheels? Should we drop QEMU from normal linux wheels?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android wheels are cross-compiled with the NDK on the host, so nothing arm64 has to run and QEMU is not involved. The Linux aarch64 wheels are different: cibuildwheel builds them inside an arm64 manylinux container, so on an x86_64 runner QEMU is what allows that container to run at all. Dropping QEMU would only work with native arm64 runners (or by cross-compiling Linux aarch64 ourselves), so I would keep it for the Linux job.

uses: pypa/cibuildwheel@v4.3
env:
CIBW_PLATFORM: android
CIBW_BUILD: "cp313-android_arm64_v8a cp313-android_x86_64 cp314-android_arm64_v8a cp314-android_x86_64 cp315-android_arm64_v8a cp315-android_x86_64"
LIBZIM_DL_VERSION: "9.8.2-1"
CIBW_BUILD_VERBOSITY: "3"

- uses: actions/upload-artifact@v7
with:
name: wheels-android
path: ./wheelhouse/*.whl

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add a CI job building the documentation on every PR/push so this kind of breakage is caught automatically (#279)
- Add support for Python 3.15 (#281)
- Add Android wheels support via cibuildwheel for arm64_v8a and x86_64 with CPython 3.13+ (#167)
- Build wheels against the stable ABI (abi3) so a single wheel per platform covers all supported CPython versions (#263)

## [3.13.1] - 2026-10-05
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ classifiers = [
"Typing :: Stubs Only",
"Operating System :: MacOS",
"Operating System :: POSIX",
"Operating System :: Android",
]

[project.urls]
Expand Down Expand Up @@ -106,6 +107,7 @@ libzim = [
"*.pyi",
"libzim.9.dylib",
"libzim.so.9",
"libzim.so",
"zim-9.dll",
"icuuc74.dll",
"icutu74.dll",
Expand Down Expand Up @@ -141,6 +143,9 @@ archs = ["x86_64", "arm64"]
# > requires changing wheel names
test-skip = "*_x86_64"

[tool.cibuildwheel.android]
archs = ["arm64_v8a", "x86_64"]

[tool.hatch.build]
exclude = [
"/.github",
Expand Down
49 changes: 39 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import zipfile
from ctypes.util import find_library
from pathlib import Path
from typing import ClassVar

from Cython.Build import cythonize
from Cython.Distutils.build_ext import new_build_ext as build_ext
Expand All @@ -49,11 +50,20 @@ class Config:
# windows
_msvc_debug: bool = bool(os.getenv("MSVC_DEBUG"))

supported_platforms = { # noqa: RUF012
supported_platforms: ClassVar[dict[str, list[str]]] = {
"Darwin": ["x86_64", "arm64"],
"Linux": ["x86_64", "aarch64"],
"Linux-musl": ["x86_64", "aarch64"],
"Windows": ["amd64"],
"Android": ["arm64_v8a", "x86_64"],
}

_android_triplets: ClassVar[frozenset[str]] = frozenset(
{"aarch64-linux-android", "x86_64-linux-android"}
)
_android_arch_map: ClassVar[dict[str, str]] = {
"aarch64-linux-android": "arm64_v8a",
"x86_64-linux-android": "x86_64",
}

base_dir: pathlib.Path = Path(__file__).parent
Expand Down Expand Up @@ -92,19 +102,27 @@ def is_nightly(self) -> bool:

@property
def platform(self) -> str:
"""Platform building for: Darwin, Linux"""
"""Platform building for: Darwin, Linux, Android"""
if os.getenv("CIBW_HOST_TRIPLET") in self._android_triplets:
return "Android"
return sysplatform.system()

@property
def platform_libc(self) -> str:
"""Platform adjusted for libc variant: Darwin, Linux, Linux-musl"""
if self.platform == "Android":
return "Android"
if self.platform == "Linux" and self.is_musl:
return "Linux-musl"
return self.platform

@property
def arch(self) -> str:
# macOS x86_64|arm64 - linux x86_64|aarch64
# macOS x86_64|arm64 - linux x86_64|aarch64 - android arm64_v8a|x86_64

host_triplet = os.getenv("CIBW_HOST_TRIPLET", "")
if host_triplet in self._android_triplets:
return self._android_arch_map[host_triplet]

# when using cibuildwheel on macOS to cross-compile,
# `_PYTHON_HOST_PLATFORM` contains
Expand Down Expand Up @@ -133,6 +151,7 @@ def libzim_fname(self):
"Darwin": f"libzim.{self.libzim_major}.dylib",
"Linux": f"libzim.so.{self.libzim_major}",
"Windows": f"zim-{self.libzim_major}.dll",
"Android": "libzim.so",
}[self.platform]

@property
Expand All @@ -148,6 +167,8 @@ def archive_format(self):
@property
def is_musl(self) -> bool:
"""whether running on a musl system (Alpine)"""
if self.platform == "Android":
return False
ps = subprocess.run(
["/usr/bin/env", "ldd", "--version"],
capture_output=True,
Expand Down Expand Up @@ -182,13 +203,19 @@ def get_download_filename(self, arch: str | None = None) -> str:
if self.platform == "Windows" and arch == "amd64":
arch = "x86_64"

lzplatform = {"Darwin": "macos", "Linux": "linux", "Windows": "win"}.get(
self.platform
)
if self.platform == "Android":
android_arch_map = {"arm64_v8a": "arm64", "x86_64": "x86_64"}
arch = android_arch_map.get(arch, arch)
lzplatform = "android"
variant = ""
else:
lzplatform = {"Darwin": "macos", "Linux": "linux", "Windows": "win"}.get(
self.platform
)

variant = ""
if self.platform == "Linux":
variant = "-musl" if self.is_musl else "-manylinux"
variant = ""
if self.platform == "Linux":
variant = "-musl" if self.is_musl else "-manylinux"

if self.is_latest_nightly:
version_suffix = ""
Expand Down Expand Up @@ -441,7 +468,9 @@ def get_cython_extension() -> list[Extension]:
include_dirs.append("include")
library_dirs = ["libzim"]

if config.platform != "Windows":
if config.platform == "Android":
runtime_library_dirs = ["$ORIGIN/libzim/"]
elif config.platform != "Windows":
runtime_library_dirs = (
[f"@loader_path/libzim/{config.libzim_fname}"]
if config.platform == "Darwin"
Expand Down
Loading