Skip to content

Android testbed fails to build for 32-bit targets #146541

Description

@robertkirkman

Bug report

Bug description:

Unfortunately, the Android testbed is not currently buildable for 32-bit targets because the 32-bit ABI names are missing from its build system.

The first error that occurs is this:

usage: android.py configure-host [-h] [-v] [--clean] HOST [args ...]
android.py configure-host: error: argument HOST: invalid choice: 'arm-linux-androideabi' (choose from 'aarch64-linux-android', 'x86_64-linux-android')

Another error that occurs is this:

Traceback (most recent call last):
  File "<frozen runpy>", line 196, in _run_module_as_main
  File "<frozen runpy>", line 87, in _run_code
  File "/home/ubuntu/cpython/cross-build/arm-linux-androideabi/../../Lib/sysconfig/__main__.py", line 248, in <module>
    _main()
    ~~~~~^^
  File "/home/ubuntu/cpython/cross-build/arm-linux-androideabi/../../Lib/sysconfig/__main__.py", line 235, in _main
    _generate_posix_vars()
    ~~~~~~~~~~~~~~~~~~~~^^
  File "/home/ubuntu/cpython/cross-build/arm-linux-androideabi/../../Lib/sysconfig/__main__.py", line 197, in _generate_posix_vars
    pybuilddir = _get_pybuilddir()
  File "/home/ubuntu/cpython/cross-build/arm-linux-androideabi/../../Lib/sysconfig/__main__.py", line 138, in _get_pybuilddir
    pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}'
                              ~~~~~~~~~~~~^^
  File "/home/ubuntu/cpython/cross-build/arm-linux-androideabi/../../Lib/sysconfig/__init__.py", line 703, in get_platform
    machine = {
              ~
    ...<4 lines>...
        "armv8l": "armeabi_v7a",
        ~~~~~~~~~~~~~~~~~~~~~~~~
    }[machine]
    ~^^^^^^^^^
KeyError: 'arm'

Applying several changes allows building the Android testbed for 32-bit targets and running it on them.

This Dockerfile can build the Android testbed for 32-bit ARM and 32-bit x86.

FROM ubuntu:24.04 AS build

# based on
# https://github.com/thunder-coding/containerTube/blob/93062c010527382b0eb5372e9fd0842cb9f9a3f5/python-android-builder/Dockerfile

# Install necessary packages we need to build Python
RUN <<DOCKEREOF
apt-get update && apt-get full-upgrade -y
# Needed for getting the sources and SDK
apt-get install -y git curl unzip
# For actually building Python
apt-get install -y build-essential python-is-python3 autoconf autoconf-archive m4 autotools-dev libffi-dev
# Needed for Android command line tools
apt-get install -y openjdk-21-jdk
# Needed for building zstd in cpython build system
apt-get install -y wget
DOCKEREOF

ADD https://dl.google.com/android/repository/commandlinetools-linux-14742923_latest.zip /root/cmdline-tools.zip
ADD https://github.com/python/cpython/archive/5992238986df094e890a89376970aab6058a0759.tar.gz /root/cpython.tar.gz
ADD https://github.com/beeware/cpython-android-source-deps/archive/refs/tags/zstd-1.5.7-1.tar.gz /root/cpython-android-source-deps.tar.gz

ENV ANDROID_HOME=/root/.android

WORKDIR /root

RUN <<DOCKEREOF
mkdir -p "$ANDROID_HOME/cmdline-tools"
unzip cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools"
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
mkdir -p cpython cpython-android-source-deps
tar xf cpython.tar.gz --strip-components=1 -C cpython
tar xf cpython-android-source-deps.tar.gz --strip-components=1 -C cpython-android-source-deps
DOCKEREOF

WORKDIR /root/cpython-android-source-deps

RUN <<DOCKEREOF
git apply -v << 'PATCHEOF'
--- a/android-env.sh
+++ b/android-env.sh
@@ -76,6 +76,13 @@ if [ "$HOST" = "arm-linux-androideabi" ]; then
     CFLAGS="$CFLAGS -march=armv7-a -mthumb"
 fi
 
+# prevents this error when linking to the zstd static library built for 32-bit x86:
+# ld.lld: error: relocation R_386_PC32 cannot be used
+# against symbol 'HUF_writeCTable_wksp'; recompile with -fPIC
+if [ "$HOST" = "i686-linux-android" ]; then
+    CFLAGS="$CFLAGS -fPIC"
+fi
+
 if [ -n "${PREFIX:-}" ]; then
     abs_prefix="$(realpath "$PREFIX")"
     CFLAGS="$CFLAGS -I$abs_prefix/include"
PATCHEOF
DOCKEREOF

RUN ./build.sh zstd 1.5.7 2 i686-linux-android

WORKDIR /root/cpython

RUN <<DOCKEREOF
cat > /root/git-ls-files.log << 'EOF'
README.md
android-env.sh
android.py
testbed/.gitignore
testbed/.idea/inspectionProfiles/Project_Default.xml
testbed/app/.gitignore
testbed/app/build.gradle.kts
testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt
testbed/app/src/main/AndroidManifest.xml
testbed/app/src/main/c/CMakeLists.txt
testbed/app/src/main/c/main_activity.c
testbed/app/src/main/java/org/python/testbed/MainActivity.kt
testbed/app/src/main/res/drawable-xxhdpi/ic_launcher.png
testbed/app/src/main/res/layout/activity_main.xml
testbed/app/src/main/res/values/strings.xml
testbed/build.gradle.kts
testbed/gradle.properties
testbed/gradle/wrapper/gradle-wrapper.properties
testbed/settings.gradle.kts
EOF
git apply -v << 'PATCHEOF'
--- a/Android/android.py
+++ b/Android/android.py
@@ -34,7 +34,7 @@
 TESTBED_DIR = ANDROID_DIR / "testbed"
 CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
 
-HOSTS = ["aarch64-linux-android", "x86_64-linux-android"]
+HOSTS = ["aarch64-linux-android", "x86_64-linux-android", "arm-linux-androideabi", "i686-linux-android"]
 APP_ID = "org.python.testbed"
 DECODE_ARGS = ("UTF-8", "backslashreplace")
 
@@ -743,7 +743,7 @@ def package(context):
 
         # Include all tracked files from the Android directory.
         for line in run(
-            ["git", "ls-files"],
+            ["cat", "/root/git-ls-files.log"],
             cwd=ANDROID_DIR, capture_output=True, text=True, log=False,
         ).stdout.splitlines():
             src = ANDROID_DIR / line
--- a/Android/testbed/app/build.gradle.kts
+++ b/Android/testbed/app/build.gradle.kts
@@ -16,6 +16,8 @@ val inSourceTree = (
 val KNOWN_ABIS = mapOf(
     "aarch64-linux-android" to "arm64-v8a",
     "x86_64-linux-android" to "x86_64",
+    "arm-linux-androideabi" to "armeabi-v7a",
+    "i686-linux-android" to "x86",
 )
 
 // Discover prefixes.
--- a/Lib/sysconfig/__init__.py
+++ b/Lib/sysconfig/__init__.py
@@ -697,12 +697,16 @@ def get_platform():
         # When Python is running on 32-bit ARM Android on a 64-bit ARM kernel,
         # 'os.uname().machine' is 'armv8l'. Such devices run the same userspace
         # code as 'armv7l' devices.
+        # During the build process of the Android testbed when targeting 32-bit ARM,
+        # '_PYTHON_HOST_PLATFORM' is 'arm-linux-androideabi', so 'machine' becomes
+        # 'arm'.
         machine = {
             "x86_64": "x86_64",
             "i686": "x86",
             "aarch64": "arm64_v8a",
             "armv7l": "armeabi_v7a",
             "armv8l": "armeabi_v7a",
+            "arm": "armeabi_v7a",
         }[machine]
     elif osname == "linux":
         # At least on Linux/Intel, 'machine' is the processor --
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -376,6 +376,7 @@ def test_get_platform(self):
             'aarch64': 'arm64_v8a',
             'armv7l': 'armeabi_v7a',
             'armv8l': 'armeabi_v7a',
+            'arm': 'armeabi_v7a',
         }.items():
             with self.subTest(machine):
                 self._set_uname(('Linux', 'localhost', '3.18.91+',
@@ -585,6 +586,7 @@ def test_android_ext_suffix(self):
             "aarch64": "aarch64-linux-android",
             "armv7l": "arm-linux-androideabi",
             "armv8l": "arm-linux-androideabi",
+            "arm": "arm-linux-androideabi",
         }[machine]
         self.assertEndsWith(suffix, f"-{expected_triplet}.so")
 
PATCHEOF
DOCKEREOF

WORKDIR /root/cpython/Android

RUN <<DOCKEREOF
./android.py configure-build
./android.py make-build
DOCKEREOF

RUN <<DOCKEREOF
./android.py configure-host arm-linux-androideabi
./android.py make-host arm-linux-androideabi
./android.py package arm-linux-androideabi
mv ../cross-build/arm-linux-androideabi/dist/python-*-arm-linux-androideabi.tar.gz \
    /root/python-arm-linux-androideabi.tar.gz
DOCKEREOF

WORKDIR /root/cpython

RUN <<DOCKEREOF
git apply -v << 'PATCHEOF'
--- a/Android/android.py
+++ b/Android/android.py
@@ -214,6 +214,8 @@ def unpack_deps(host, prefix_dir):
         download(f"{deps_url}/{name_ver}/{filename}")
         shutil.unpack_archive(filename)
         os.remove(filename)
+    shutil.copyfile("/root/cpython-android-source-deps/zstd/build/1.5.7/i686-linux-android/prefix/lib/libzstd.a",
+        "/root/cpython/cross-build/i686-linux-android/prefix/lib/libzstd.a")
 
 
 def download(url, target_dir="."):
PATCHEOF
DOCKEREOF

WORKDIR /root/cpython/Android

RUN <<DOCKEREOF
./android.py configure-host i686-linux-android
./android.py make-host i686-linux-android
./android.py package i686-linux-android
mv ../cross-build/i686-linux-android/dist/python-*-i686-linux-android.tar.gz \
    /root/python-i686-linux-android.tar.gz
DOCKEREOF

FROM scratch
COPY --from=build /root/python-arm-linux-androideabi.tar.gz /
COPY --from=build /root/python-i686-linux-android.tar.gz /

There is also an error specific to 32-bit x86 that occurs because of the https://github.com/beeware/cpython-android-source-deps repository compiling libzstd.a for 32-bit x86 without applying the -fPIC argument to the compiler:

ld.lld: error: relocation R_386_PC32 cannot be used against symbol 'HUF_writeCTable_wksp'; recompile with -fPIC
>>> defined in /root/cpython/cross-build/i686-linux-android/prefix/lib/libzstd.a(huf_compress.o)
>>> referenced by huf_compress.c
>>>               huf_compress.o:(HUF_optimalTableLog) in archive /root/cpython/cross-build/i686-linux-android/prefix/lib/libzstd.a

The way to fix that error would be in the cpython-android-source-deps repository, so I have opened a separate issue and PR for that here:

Then, I was able to use these commands to build, extract and run the python-arm-linux-androideabi.tar.gz on my 32-bit ARM device connected to ADB, with ID 4251484430323498.

docker build --output=. .
tar xf python-arm-linux-androideabi.tar.gz
./android.py test --connected 4251484430323498

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added a commit that references this issue on Mar 28, 2026
  2. mhsmith commented on Apr 2, 2026

    @mhsmith
    Member

    Please don't post any more Dockerfiles like this, it just makes the issue more confusing. Fixes should come in the form of PRs.

  3. robertkirkman commented on Apr 2, 2026

    @robertkirkman
    ContributorAuthor

    Since the 32-bit testbed cannot be run easily in GitHub Actions, without showing the Dockerfiles it is unclear to me how the code in my PRs can be tested and verified to work by others, but I will refrain from doing that going forward.

    Should I add code to the GitHub Actions CI that just builds the testbed for the 32-bit targets without attempting to run it? That would be able to validate changes affecting the build, just not changes affecting only runtime behavior.

  4. mhsmith commented on Apr 2, 2026

    @mhsmith
    Member

    We'll treat the 32-bit Android platforms the same as Python's other unsupported platforms: we won't add any automated testing, and we probably won't do any manual testing either. We'll just accept your changes if they look reasonable and they don't negatively impact any of the supported platforms.

  5. added 3 commits that reference this issue on Apr 2, 2026
  6. added a commit that references this issue on Apr 4, 2026
  7. added 2 commits that reference this issue on Apr 4, 2026
  8. added a commit that references this issue on Apr 4, 2026
  9. added a commit that references this issue on Apr 5, 2026
  10. added a commit that references this issue on Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    OS-androidtestsTests in the Lib/test dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions