Build armeabi-v7a (32-bit ARM) for Python 3.13/3.14#25
Merged
Conversation
CPython's official Android/android.py lists arm-linux-androideabi in its supported HOSTS for both 3.13 and 3.14, and beeware's cpython-android-source-deps publishes the matching 32-bit dependency tarballs. PEP 738 makes 64-bit Android the *tested* Tier-3 configuration; it does not prevent building 32-bit ARM. So the only blocker was a self-imposed ABI gate in build.sh. - build.sh: allow armeabi-v7a through the 3.13+ official-build ABI case. - build-all.sh: read android_abis from manifest.json (same source the CI packaging step uses) instead of a separate hardcoded version gate, so the build set and package set stay in lockstep. - manifest.json: add armeabi-v7a to 3.13 and 3.14. - README/android/README: correct the "3.13+ are 64-bit-only" note. Validated locally: 3.14.6 and 3.13.14 both build via android.py and package into Dart tarballs; libpython and extension modules are ELF32/ARM with no import failures.
ndonkoHenri
added a commit
to flet-dev/mobile-forge
that referenced
this pull request
Jun 29, 2026
Companion to flet-dev/python-build#25, which added `armeabi-v7a` (32-bit ARM) runtimes for Python 3.13/3.14. That release (**20260629**) now ships `["arm64-v8a", "x86_64", "armeabi-v7a"]` for **all three** minors, and drops `x86` (32-bit Intel) everywhere. This PR teaches mobile-forge to consume that: it makes the Android ABI set **manifest-driven** here too, so the same single source of truth flows across both repos and a future ABI change upstream needs only a release-pin bump here — no hardcoded list to keep in sync. Net effect: forge now builds **armeabi-v7a wheels for 3.13/3.14** (it already did for 3.12), and stops building the now-unshipped **x86**. ## Changes - **`setup.sh`** - Bump `PYTHON_BUILD_RELEASE` `20260614` → `20260629`. - New `resolve_android_abis()` — reads `pythons.<minor>.android_abis` from the pinned release's manifest using the same 3-tier parser as `resolve_full_version` (jq → python3 → scoped sed, cached under `downloads/`). Exports **`ANDROID_ABIS`** (env-overridable for the `PYTHON_BUILD_RUN_ID` branch-validation path; also pushed to `GITHUB_ENV`). - Android support-tree verification now **loops `$ANDROID_ABIS`** instead of the hardcoded `minor < 13 → check armeabi+x86` gate. - **`src/forge/cross.py`** — `ANDROID_HOST_ARCHS` reads `$ANDROID_ABIS` (falls back to `("arm64-v8a", "armeabi-v7a", "x86_64")` when forge runs without sourcing setup.sh), replacing the `>=3.13 → 64-bit-only` version gate. - **`make_dep_wheels.py`** — `get_targets("android")` reads `$ANDROID_ABIS` too, so the support-tree dep wheels (openssl/libffi/sqlite/xz/…) get produced for armeabi-v7a on 3.13/3.14. - **`.github/workflows/build-wheels-version.yml`** — drop the now-dead `i686-linux-android` Rust target (`arm-linux-androideabi` for armeabi was already present). No change needed elsewhere: `cross.py` already maps armeabi-v7a → `android-arm` (3.13+ official-build naming) and the wheel tag is the version-independent `android_24_armeabi_v7a`; the CI matrix keys on `android`/`iOS` (per-ABI fan-out happens inside forge); the support-tree dep-wheel drop step is ABI-agnostic. ## Behavioral note: x86 is dropped `20260629` ships no `x86` runtime for any minor (the mobile-forge tarball is `tar install support` right after `build-all.sh`, which builds only the manifest's ABIs). So bumping the pin necessarily stops x86 wheel production for 3.12 too. Already-published 3.12 x86 wheels become orphaned on the index — harmless, since flet/serious_python don't ship 32-bit Intel. ## Validation Verified locally: - All three manifest-parser tiers (jq / python3 / sed) return `arm64-v8a x86_64 armeabi-v7a` for 3.12/3.13/3.14 against the real 20260629 manifest. - `ANDROID_HOST_ARCHS` / `get_targets` honor `$ANDROID_ABIS` and fall back correctly. - armeabi-v7a → `_platform_identifier` `android-arm`, tag `android_24_armeabi_v7a`. - The support-tree binary path the verify loop checks (`install/android/<abi>/python-<full>/bin/python<minor>`) matches the tarball layout. - `bash -n` + `py_compile` clean; no new lint/format findings. Full validation comes via the follow-up `packages=ALL` build across python versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds
armeabi-v7a(32-bit ARM) Android builds for Python 3.13 and 3.14, so all three minors now carry the same ABI set.The repo previously treated 3.13+ as 64-bit-only, citing PEP 738. That's inaccurate: PEP 738 makes 64-bit Android the tested Tier-3 configuration, but CPython's official
Android/android.pystill listsarm-linux-androideabiin its supportedHOSTS(verified on bothv3.13.14andv3.14.6), and beeware'scpython-android-source-depspublishes the matching*-arm-linux-androideabi.tar.gzdependency assets for every version 3.13/3.14 pin. The only real blocker was a self-imposed ABI gate inbuild.sh.Changes
android/build.sh— allowarmeabi-v7athrough the 3.13+ official-build ABI case (the one blocker). Everything downstream (abi-to-host.sh→arm-linux-androideabi,extract_mobile_forge_deps.py,normalize_mobile_forge_install.py,package-for-dart.sh) was already ABI-agnostic and handledarmeabi-v7afor 3.12.android/build-all.sh— readandroid_abisfrommanifest.json(the same source the CI packaging loop uses) instead of a separate hardcoded3.13+ → 64-bitgate. Required for correctness: CI builds viabuild-all.shbut packages from the manifest, so the two must agree or CI would try to package an ABI it never built. Now the ABI set lives in exactly one place.manifest.json— addarmeabi-v7ato 3.13 and 3.14.README.md/android/README.md— correct the "3.13+ are 64-bit-only" note.Validation
Built and packaged locally via the official tooling — both green:
android-arm, 0 import failuresandroid-arm, 0 import failuresModules compiled with
armv7a-linux-androideabi24-clang -march=armv7-a; deps (openssl/libffi/sqlite/xz/bzip2) downloaded as*-arm-linux-androideabi.tar.gz;normalize+ packaging clean.libpython3.{13,14}.soand extension modules confirmedELF32 / Machine: ARM.Note for reviewers
This repo only produces the runtime tarball. Shipping 32-bit apps also requires the downstream consumers (serious_python, flet, the Dart bridge) to accept and bundle the
armeabi-v7aartifact — separate repos, out of scope here.