From 119d620d38b1b71379765f0bc21c3d2aa27df23c Mon Sep 17 00:00:00 2001 From: pawank925 <229111348+pawank925@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:02:14 +0530 Subject: [PATCH 1/4] Add Android wheels support via cibuildwheel Build Android arm64_v8a and x86_64 wheels with CPython 3.13+. Download prebuilt libzim Android binaries and adjust packaging/rpaths for Android toolchain. Add opt-in Android job to CI wheels workflow. Closes #167 --- .github/workflows/CI-wheels.yaml | 21 ++++++++++++++++ CHANGELOG.md | 1 + pyproject.toml | 6 +++++ setup.py | 43 +++++++++++++++++++++++++------- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI-wheels.yaml b/.github/workflows/CI-wheels.yaml index c89bc1f..c3330ea 100644 --- a/.github/workflows/CI-wheels.yaml +++ b/.github/workflows/CI-wheels.yaml @@ -38,6 +38,27 @@ jobs: name: wheels-${{ matrix.os }} path: ./wheelhouse/*.whl + build_wheels_android: + name: Build Android wheels (experimental) + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-26.04 + + steps: + - uses: actions/checkout@v7 + + - name: Build Android wheels + 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index d962176..27c4cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ## [3.13.1] - 2026-10-05 diff --git a/pyproject.toml b/pyproject.toml index 49de6f5..7538b85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ classifiers = [ "Typing :: Stubs Only", "Operating System :: MacOS", "Operating System :: POSIX", + "Operating System :: Android", ] [project.urls] @@ -106,6 +107,7 @@ libzim = [ "*.pyi", "libzim.9.dylib", "libzim.so.9", + "libzim.so", "zim-9.dll", "icuuc74.dll", "icutu74.dll", @@ -141,6 +143,10 @@ archs = ["x86_64", "arm64"] # > requires changing wheel names test-skip = "*_x86_64" +[tool.cibuildwheel.android] +archs = ["arm64_v8a", "x86_64"] +environment = { LIBZIM_DL_VERSION = "9.8.2-1" } + [tool.hatch.build] exclude = [ "/.github", diff --git a/setup.py b/setup.py index 04c7080..a3a5450 100755 --- a/setup.py +++ b/setup.py @@ -54,6 +54,12 @@ class Config: "Linux": ["x86_64", "aarch64"], "Linux-musl": ["x86_64", "aarch64"], "Windows": ["amd64"], + "Android": ["arm64_v8a", "x86_64"], + } + + android_triplets = { # noqa: RUF012 + "aarch64-linux-android": "arm64_v8a", + "x86_64-linux-android": "x86_64", } base_dir: pathlib.Path = Path(__file__).parent @@ -92,19 +98,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_triplets[host_triplet] # when using cibuildwheel on macOS to cross-compile, # `_PYTHON_HOST_PLATFORM` contains @@ -133,6 +147,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 @@ -148,6 +163,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, @@ -182,13 +199,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 = "" @@ -434,7 +457,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" From faef95e34705e2a7f338cf2df87e0e0ed24c2e77 Mon Sep 17 00:00:00 2001 From: pawank925 <229111348+pawank925@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:05:37 +0530 Subject: [PATCH 2/4] Address Android wheels review feedback --- .github/workflows/CI-wheels.yaml | 3 +-- pyproject.toml | 1 - setup.py | 9 +++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI-wheels.yaml b/.github/workflows/CI-wheels.yaml index c3330ea..3280c26 100644 --- a/.github/workflows/CI-wheels.yaml +++ b/.github/workflows/CI-wheels.yaml @@ -39,8 +39,7 @@ jobs: path: ./wheelhouse/*.whl build_wheels_android: - name: Build Android wheels (experimental) - if: github.event_name == 'workflow_dispatch' + name: Build Android wheels runs-on: ubuntu-26.04 steps: diff --git a/pyproject.toml b/pyproject.toml index 7538b85..ad1955f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,7 +145,6 @@ test-skip = "*_x86_64" [tool.cibuildwheel.android] archs = ["arm64_v8a", "x86_64"] -environment = { LIBZIM_DL_VERSION = "9.8.2-1" } [tool.hatch.build] exclude = [ diff --git a/setup.py b/setup.py index a3a5450..4af9a71 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,8 @@ class Config: "Android": ["arm64_v8a", "x86_64"], } - android_triplets = { # noqa: RUF012 + _android_triplets = frozenset({"aarch64-linux-android", "x86_64-linux-android"}) + _android_arch_map = { "aarch64-linux-android": "arm64_v8a", "x86_64-linux-android": "x86_64", } @@ -99,7 +100,7 @@ def is_nightly(self) -> bool: @property def platform(self) -> str: """Platform building for: Darwin, Linux, Android""" - if os.getenv("CIBW_HOST_TRIPLET") in self.android_triplets: + if os.getenv("CIBW_HOST_TRIPLET") in self._android_triplets: return "Android" return sysplatform.system() @@ -117,8 +118,8 @@ def arch(self) -> str: # 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_triplets[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 From 1ac9e8a21d60afb7f2daa3afbd632b488db3049a Mon Sep 17 00:00:00 2001 From: pawank925 <229111348+pawank925@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:17:07 +0530 Subject: [PATCH 3/4] Fix ruff RUF012 for Android triplet constants --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4af9a71..66cbcaf 100755 --- a/setup.py +++ b/setup.py @@ -57,8 +57,10 @@ class Config: "Android": ["arm64_v8a", "x86_64"], } - _android_triplets = frozenset({"aarch64-linux-android", "x86_64-linux-android"}) - _android_arch_map = { + _android_triplets: frozenset[str] = frozenset( + {"aarch64-linux-android", "x86_64-linux-android"} + ) + _android_arch_map: dict[str, str] = { "aarch64-linux-android": "arm64_v8a", "x86_64-linux-android": "x86_64", } From 0370d9642cb4586ea91928be93e85997e6de5677 Mon Sep 17 00:00:00 2001 From: pawank925 <229111348+pawank925@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:22:50 +0530 Subject: [PATCH 4/4] Use ClassVar for mutable class attrs --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 66cbcaf..47df395 100755 --- a/setup.py +++ b/setup.py @@ -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 @@ -49,7 +50,7 @@ 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"], @@ -57,10 +58,10 @@ class Config: "Android": ["arm64_v8a", "x86_64"], } - _android_triplets: frozenset[str] = frozenset( + _android_triplets: ClassVar[frozenset[str]] = frozenset( {"aarch64-linux-android", "x86_64-linux-android"} ) - _android_arch_map: dict[str, str] = { + _android_arch_map: ClassVar[dict[str, str]] = { "aarch64-linux-android": "arm64_v8a", "x86_64-linux-android": "x86_64", }