Skip to content

Commit 7af62ca

Browse files
committed
Add legacy Android build support & patches
Enable building older Python releases for Android using a legacy patched cross-build flow and add necessary patches and tooling. Changes include: - Add android/android-env.sh to configure NDK/r27d toolchain, environment variables, and cross-compile flags. - Add multiple patches (bldlibrary, grp, python_for_build_deps, soname, sysroot_paths) to fix cross-compilation, linking and packaging issues on Android. - Update build scripts to be version-aware: - .github workflow now packages armeabi-v7a for Python < 3.13. - android/build-all.sh selects ABIs based on Python version (3.13+ uses official tool, older versions include armeabi-v7a and x86). - android/build.sh applies patches and either uses the legacy cross-build flow (for <= 3.12) with prebuilt libs and custom configure/make/install steps or uses CPython's Android/android.py for 3.13+. - Set CCSHARED, CONFIG_SITE, sysroot include/lib paths and other flags needed for current NDKs and cross-compilation. - Add ABI-to-host mappings for armeabi-v7a and x86 in android/abi-to-host.sh. - Improve NDK detection in android/package-for-dart.sh to prefer locally installed NDKs. - Update android/README.md to document the two build flows (3.12 legacy vs 3.13+ official) and ABI support. These changes allow building and packaging older Python versions for a wider set of Android ABIs and fix several cross-compile/link/runtime issues encountered when targeting Android.
1 parent a6b8971 commit 7af62ca

File tree

12 files changed

+436
-49
lines changed

12 files changed

+436
-49
lines changed

.github/workflows/build-python.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ jobs:
7979
tar -czf dist/python-android-mobile-forge-$PYTHON_VERSION_SHORT.tar.gz install support
8080
bash ./package-for-dart.sh install "$PYTHON_VERSION" arm64-v8a
8181
bash ./package-for-dart.sh install "$PYTHON_VERSION" x86_64
82+
read version_major version_minor < <(echo "$PYTHON_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1 \2/')
83+
version_int=$((version_major * 100 + version_minor))
84+
if [ $version_int -lt 313 ]; then
85+
bash ./package-for-dart.sh install "$PYTHON_VERSION" armeabi-v7a
86+
fi
8287
- uses: actions/upload-artifact@v4
8388
with:
8489
name: python-android

android/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Python for Android
22

3-
Scripts and CI jobs for building Python 3.13 for Android.
3+
Scripts and CI jobs for building Python for Android.
44

55
* Can be run on both Linux and macOS.
6-
* Builds Python 3.13.x only.
7-
* Creates Python installation with a structure suitable for https://github.com/flet-dev/mobile-forge
8-
* Uses CPython's official `Android/android.py` build flow.
6+
* Python 3.12 uses the legacy patched cross-build flow.
7+
* Python 3.13+ uses CPython's official `Android/android.py` build flow.
8+
* Creates Python installation with a structure suitable for https://github.com/flet-dev/mobile-forge.
99

1010
## Usage
1111

12-
To build Python for a specific ABI (`arm64-v8a` or `x86_64`):
12+
To build Python for a specific ABI:
1313

1414
```
1515
./build.sh 3.13.12 arm64-v8a
@@ -21,7 +21,9 @@ To build all ABIs:
2121
./build-all.sh 3.13.12
2222
```
2323

24-
For Python 3.13+, official CPython Android tooling currently supports `arm64-v8a` and `x86_64`.
24+
ABI support:
25+
* Python 3.12: `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86`
26+
* Python 3.13+: `arm64-v8a`, `x86_64`
2527

2628
## Credits
2729

android/abi-to-host.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
case ${abi:?} in
2+
armeabi-v7a)
3+
HOST=arm-linux-androideabi
4+
;;
25
arm64-v8a)
36
HOST=aarch64-linux-android
47
;;
8+
x86)
9+
HOST=i686-linux-android
10+
;;
511
x86_64)
612
HOST=x86_64-linux-android
713
;;

android/android-env.sh

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# This script must be sourced with the following variables already set:
2+
: ${HOST:?} # GNU target triplet
3+
4+
# You may also override the following:
5+
: ${api_level:=24} # Minimum Android API level the build will run on
6+
: ${PREFIX:-} # Path in which to find required libraries
7+
8+
NDK_VERSION=r27d
9+
10+
# Print all messages on stderr so they're visible when running within build-wheel.
11+
log() {
12+
echo "$1" >&2
13+
}
14+
15+
fail() {
16+
log "$1"
17+
exit 1
18+
}
19+
20+
# When moving to a new version of the NDK, carefully review the following:
21+
#
22+
# * https://developer.android.com/ndk/downloads/revision_history
23+
#
24+
# * https://android.googlesource.com/platform/ndk/+/ndk-rXX-release/docs/BuildSystemMaintainers.md
25+
# where XX is the NDK version. Do a diff against the version you're upgrading from, e.g.:
26+
# https://android.googlesource.com/platform/ndk/+/ndk-r25-release..ndk-r26-release/docs/BuildSystemMaintainers.md
27+
if [[ -z "${NDK_HOME-}" ]]; then
28+
NDK_HOME=$HOME/ndk/$NDK_VERSION
29+
echo "NDK_HOME environment variable is not set."
30+
if [ ! -d $NDK_HOME ]; then
31+
echo "Installing NDK $NDK_VERSION to $NDK_HOME"
32+
33+
if [ $(uname) = "Darwin" ]; then
34+
seven_zip=$downloads/7zip/7zz
35+
if ! test -f $seven_zip; then
36+
echo "Installing 7-zip"
37+
mkdir -p $(dirname $seven_zip)
38+
cd $(dirname $seven_zip)
39+
curl -#OL https://www.7-zip.org/a/7z2301-mac.tar.xz
40+
tar -xf 7z2301-mac.tar.xz
41+
cd -
42+
fi
43+
44+
ndk_dmg=android-ndk-$NDK_VERSION-darwin.dmg
45+
if ! test -f $downloads/$ndk_dmg; then
46+
echo ">>> Downloading $ndk_dmg"
47+
curl -#L -o $downloads/$ndk_dmg https://dl.google.com/android/repository/$ndk_dmg
48+
fi
49+
50+
cd $downloads
51+
$seven_zip x -snld $ndk_dmg
52+
mkdir -p $(dirname $NDK_HOME)
53+
mv Android\ NDK\ */AndroidNDK*.app/Contents/NDK $NDK_HOME
54+
rm -rf Android\ NDK\ *
55+
cd -
56+
else
57+
ndk_zip=android-ndk-$NDK_VERSION-linux.zip
58+
if ! test -f $downloads/$ndk_zip; then
59+
echo ">>> Downloading $ndk_zip"
60+
curl -#L -o $downloads/$ndk_zip https://dl.google.com/android/repository/$ndk_zip
61+
fi
62+
cd $downloads
63+
unzip -oq $ndk_zip
64+
mkdir -p $(dirname $NDK_HOME)
65+
mv android-ndk-$NDK_VERSION $NDK_HOME
66+
cd -
67+
echo "NDK installed to $NDK_HOME"
68+
fi
69+
else
70+
echo "NDK $NDK_VERSION is already installed in $NDK_HOME"
71+
fi
72+
else
73+
echo "NDK home: $NDK_HOME"
74+
fi
75+
76+
if [ $HOST = "arm-linux-androideabi" ]; then
77+
clang_triplet=armv7a-linux-androideabi
78+
else
79+
clang_triplet=$HOST
80+
fi
81+
82+
# These variables are based on BuildSystemMaintainers.md above, and
83+
# $NDK_HOME/build/cmake/android.toolchain.cmake.
84+
toolchain=$(echo $NDK_HOME/toolchains/llvm/prebuilt/*)
85+
export AR="$toolchain/bin/llvm-ar"
86+
export AS="$toolchain/bin/llvm-as"
87+
export CC="$toolchain/bin/${clang_triplet}${api_level}-clang"
88+
export CXX="${CC}++"
89+
export LD="$toolchain/bin/ld"
90+
export NM="$toolchain/bin/llvm-nm"
91+
export RANLIB="$toolchain/bin/llvm-ranlib"
92+
export READELF="$toolchain/bin/llvm-readelf"
93+
export STRIP="$toolchain/bin/llvm-strip"
94+
95+
# The quotes make sure the wildcard in the `toolchain` assignment has been expanded.
96+
for path in "$AR" "$AS" "$CC" "$CXX" "$LD" "$NM" "$RANLIB" "$READELF" "$STRIP"; do
97+
if ! [ -e "$path" ]; then
98+
fail "$path does not exist"
99+
fi
100+
done
101+
102+
export CFLAGS="-D__BIONIC_NO_PAGE_SIZE_MACRO"
103+
export LDFLAGS="-Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,-z,max-page-size=16384"
104+
105+
# Unlike Linux, Android does not implicitly use a dlopened library to resolve
106+
# relocations in subsequently-loaded libraries, even if RTLD_GLOBAL is used
107+
# (https://github.com/android/ndk/issues/1244). So any library that fails to
108+
# build with this flag, would also fail to load at runtime.
109+
LDFLAGS="$LDFLAGS -Wl,--no-undefined"
110+
111+
# Many packages get away with omitting -lm on Linux, but Android is stricter.
112+
LDFLAGS="$LDFLAGS -lm"
113+
114+
# -mstackrealign is included where necessary in the clang launcher scripts which are
115+
# pointed to by $CC, so we don't need to include it here.
116+
if [ $HOST = "arm-linux-androideabi" ]; then
117+
CFLAGS="$CFLAGS -march=armv7-a -mthumb"
118+
fi
119+
120+
if [ -n "${PREFIX:-}" ]; then
121+
abs_prefix=$(realpath $PREFIX)
122+
CFLAGS="$CFLAGS -I$abs_prefix/include"
123+
LDFLAGS="$LDFLAGS -L$abs_prefix/lib"
124+
125+
export PKG_CONFIG="pkg-config --define-prefix"
126+
export PKG_CONFIG_LIBDIR="$abs_prefix/lib/pkgconfig"
127+
fi
128+
129+
# When compiling C++, some build systems will combine CFLAGS and CXXFLAGS, and some will
130+
# use CXXFLAGS alone.
131+
export CXXFLAGS=$CFLAGS
132+
133+
# Use the same variable name as conda-build
134+
if [ $(uname) = "Darwin" ]; then
135+
export CPU_COUNT=$(sysctl -n hw.ncpu)
136+
else
137+
export CPU_COUNT=$(nproc)
138+
fi

android/build-all.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
set -euo pipefail
33

44
python_version=${1:?}
5-
abis="arm64-v8a x86_64"
5+
read version_major version_minor < <(
6+
echo "$python_version" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1 \2/'
7+
)
8+
version_int=$((version_major * 100 + version_minor))
9+
10+
if [ $version_int -ge 313 ]; then
11+
abis="arm64-v8a x86_64"
12+
else
13+
abis="arm64-v8a armeabi-v7a x86_64 x86"
14+
fi
615

716
for abi in $abis; do
817
bash ./build.sh $python_version $abi

0 commit comments

Comments
 (0)