8000 Add support for building Android wheels by mhsmith · Pull Request #2349 · pypa/cibuildwheel · GitHub
[go: up one dir, main page]

Skip to content

Add support for building Android wheels #2349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 71 commits into from
Jul 23, 2025
Merged

Add support for building Android wheels #2349

merged 71 commits into from
Jul 23, 2025

Conversation

mhsmith
Copy link
Contributor
@mhsmith mhsmith commented Apr 4, 2025

The corresponding CPython PR, from which the Android Python releases in build-platforms.toml are being generated, is python/cpython#132870.

@henryiii
Copy link
Contributor
8000

Let us know if you need (the rest of) CI triggered. :)

@mhsmith
Copy link
Contributor Author
mhsmith commented May 4, 2025

@henryiii: This is close to being complete, so please enable the rest of CI.

@mhsmith mhsmith marked this pull request as ready for review May 6, 2025 20:21
@mhsmith
Copy link
Contributor Author
mhsmith commented May 6, 2025

To run the integration tests on most of the CI machines, it looks like I'll need to automate installation of the correct Python version. For macOS this can be done the same way as iOS, but for Linux there's no existing code to reuse, because the native Linux build uses Docker. So I'll probably implement something that uses python-build-standalone, unless anyone has another suggestion.

Apart from that, I think this PR is complete enough now that it's worth reviewing. @freakboy3742 and anyone else who's interested.

@henryiii
Copy link
Contributor
henryiii commented May 6, 2025

python-build-standalone is fine, in fact, I'd like to use that for pyodide in the future, too.

@joerick
Copy link
Contributor
joerick commented May 7, 2025

I've already written code to install a version of python-build-standalone in #2002, along with version pinning etc. I think it would work nicely here too.

Copy link
Contributor
@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

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

Comments inline; my two high level concerns are:

  1. Whether the Builder class actually delivers any benefit here; and
  2. The general approach around avoiding python -m build in order to get platform-specific build requirements.

@mhsmith mhsmith requested a review from agriyakhetarpal as a code owner July 11, 2025 21:20
@mhsmith
Copy link
Contributor Author
mhsmith commented Jul 12, 2025
I/TestRunner: started: testPython(org.python.testbed.PythonSuite)
W/.python.testbed: type=1400 audit(0.0:37): avc:  denied  { read } for  name="overcommit_memory" dev="proc" ino=47927 scontext=u:r:untrusted_app:s0:c108,c256,c512,c768 tcontext=u:object_r:proc_overcommit_memory:s0 tclass=file permissive=0 app=org.python.testbed
W/.python.testbed: type=1400 audit(0.0:38): avc:  granted  { execute } for  path="/data/data/org.python.testbed/files/python/lib/python3.13/lib-dynload/_opcode.cpython-313-aarch64-linux-android.so" dev="dm-37" ino=312725 scontext=u:r:untrusted_app:s0:c108,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c108,c256,c512,c768 tclass=file app=org.python.testbed
W/.python.testbed: type=1400 audit(0.0:39): avc:  granted  { execute } for  path="/data/data/org.python.testbed/files/python/lib/python3.13/lib-dynload/_json.cpython-313-aarch64-linux-android.so" dev="dm-37" ino=312720 scontext=u:r:untrusted_app:s0:c108,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c108,c256,c512,c768 tclass=file app=org.python.testbed
W/.python.testbed: type=1400 audit(0.0:40): avc:  granted  { execute } for  path="/data/data/org.python.testbed/files/python/lib/python3.13/lib-dynload/math.cpython-313-aarch64-linux-android.so" dev="dm-37" ino=312754 scontext=u:r:untrusted_app:s0:c108,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c108,c256,c512,c768 tclass=file app=org.python.testbed
W/.python.testbed: type=1400 audit(0.0:41): avc:  granted  { execute } for  path="/data/data/org.python.testbed/files/python/lib/python3.13/lib-dynload/_ctypes.cpython-313-aarch64-linux-android.so" dev="dm-37" ino=312710 scontext=u:r:untrusted_app:s0:c108,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c108,c256,c512,c768 tclass=file app=org.python.testbed
--------- beginning of kernel
W/audit   : audit_lost=21 audit_rate_limit=5 audit_backlog_limit=64
E/audit   : rate limit exceeded
I/TestRunner: finished: testPython(org.python.testbed.PythonSuite)
I/TestRunner: run finished: 1 tests, 0 failed, 0 ignored
Command "/Users/joerick/Library/Android/sdk/platform-tools/adb -s emulator-5554 logcat --pid 2032 --format tag" returned exit status 255

                                                             ✕ 96.05s
cibuildwheel: error: Command ['/private/var/folders/ld/k24nt7054698bctspqwrjq1r0000gn/T/cibw-run-396aywo3/cp313-android_arm64_v8a/python/android.py', 'test', '--managed', 'maxVersion', '--site-packages', '/private/var/folders/ld/k24nt7054698bctspqwrjq1r0000gn/T/cibw-run-396aywo3/cp313-android_arm64_v8a/site-packages', '--cwd', '/private/var/folders/ld/k24nt7054698bctspqwrjq1r0000gn/T/cibw-run-396aywo3/cp313-android_arm64_v8a/cwd', '-c', 'import pyinstrument', '--'] failed with code 1. 

This is very strange. It's evidently running Python, as we see it loading several standard library modules, but it doesn't show any Python stdout or stderr. Despite that, the test apparently passes, and then the logcat process returns non-zero. This non-zero return is normal when the emulator is shutting down, but since the testbed hasn't seen any output from Python, it treats this as a fatal error.

The same thing has happened in CI on this PR, but only once. I've never seen it in the CI of Python itself, which uses the same testbed and runs many times every day.

@joerick: Does it still fail with the current version of this PR, and does it fail the same way every time you try?

@mhsmith
Copy link
Contributor Author
mhsmith commented Jul 12, 2025

Ah, now I think I see. Your test script is just import pyinstrument, which I guess isn't supposed to produce any output. If you changed that to something likeimport pyinstrument; print(pyinstrument), then I think it would work.

I should probably change the testbed so it ignores a non-zero return from logcat if it's seen anything that looks like a valid logcat message, whether from Python or not.

That still doesn't explain why this problem happened once in CI in a test which actually was producing output. Maybe the emulator shut down before the output could be read. The output should have occurred before the "TestRunner: finished" message, but it came from a separate thread, so maybe the message ordering isn't guaranteed. If this happens again, I'll try making the testbed pause for a moment after the test script completes before reporting the result.

@henryiii henryiii requested review from freakboy3742 and Copilot July 16, 2025 18:14
Copy link
@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds comprehensive support for building and testing Android wheels in cibuildwheel.

  • Introduces a new Android platform implementation (platforms/android.py) with environment setup, build, repair, and test logic.
  • Extends core option handling, typing, packaging, and default schemas to recognize android.
  • Adds end-to-end tests, updates CI workflows, examples, and documentation to cover Android builds.

Reviewed Changes

Copilot reviewed 35 out of 37 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
cibuildwheel/platforms/android.py New Android build, repair, and test logic
cibuildwheel/options.py Make build_frontend always present, default to build
cibuildwheel/typing.py Include android in PlatformName literal
cibuildwheel/util/packaging.py Support ignoring version number in Android wheel tags
docs/platforms.md Add Android platform guide
docs/options.md Add Android-specific environment & option variables
docs/ci-services.md Update example description to include Android
examples/github-deploy.yml Extend GitHub Actions example for Android
azure-pipelines.yml Install JDK for Android SDK tools
.github/workflows/test.yml Enable KVM for Android emulator in CI
unit_test/options_test.py Adjust default frontend test expectations
unit_test/main_tests/main_options_test.py Test parsing of Android config settings
unit_test/architecture_test.py Add arch_synonym tests
test/utils.py Generate deterministic, Android-aware expected wheels
test/test_android.py End-to-end Android wheel build & test scenarios
Comments suppressed due to low confidence (4)

examples/github-deploy.yml:34

  • The example workflow doesn’t include steps to install or configure the Android SDK (ANDROID_HOME); consider adding commands to download the command-line tools, install necessary SDK packages, accept licenses, and set ANDROID_HOME before building Android wheels.
          - os: android-intel

cibuildwheel/platforms/android.py:1

  • [nitpick] The Android platform implementation exceeds 600 lines in one file. Splitting environment setup, toolchain generation, and build/test logic into smaller modules would improve readability and maintainability.
import csv

cibuildwheel/util/packaging.py:161

  • There are no unit tests verifying find_compatible_wheel behavior for Android wheel tags. Adding tests for Android identifier patterns (e.g., android_21_arm64_v8a) would ensure this logic remains correct.
            if platform.startswith(("manylinux", "musllinux", "macosx", "android", "ios")):

docs/platforms.md:317

  • [nitpick] The note about requiring test-sources for iOS was removed; consider reinstating guidance to copy test files into the testbed app via test-sources so users don’t miss this required configuration on iOS.
The iOS test environment can't support running shell scripts, so the [`test-command`](options.md#test-command) value must be specified as if it were a command line being passed to `python -m ...`.

@henryiii
Copy link
Contributor

@freakboy3742 I rerequeted a review from you since it's still "requesting changes".

@freakboy3742
Copy link
Contributor

@freakboy3742 I rerequeted a review from you since it's still "requesting changes".

Acknowledged - I'm currently at EuroPython; I'll try to take a look, but I might not get a chance until I return to my office next week.

Copy link
@ryanking13 ryanking13 left a comment

Choose a reason for hiding this comment

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

I didn't read the full change in detail, but Pyodide-side changes looks trivial and good.

Copy link
Contributor
@joerick joerick left a comment

Choose a reason for hiding this comment

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

This is coming along nicely!

@joerick
Copy link
Contributor
joerick commented Jul 19, 2025

Ah, now I think I see. Your test script is just import pyinstrument, which I guess isn't supposed to produce any output. If you changed that to something likeimport pyinstrument; print(pyinstrument), then I think it would work.

I just confirmed this on my end as well. I added a print statement and it works beautifully. It would be great to get this fixed before release, but if it's a really complicated one, we could document it as a known issue.

@mhsmith
Copy link
Contributor Author
mhsmith commented Jul 20, 2025

I've submitted a CPython PR to fix this (python/cpython#136845). Once that's merged, I'll update build-platforms.toml to pick it up.

@mhsmith
Copy link
Contributor Author
mhsmith commented Jul 22, 2025

I've submitted a CPython PR to fix this (python/cpython#136845). Once that's merged, I'll update build-platforms.toml to pick it up.

Done.

Copy link
Contributor
@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

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

This all looks good to me, and worked well in my local experiments with some simple projects.

The only thing that seems worthy of note is that we've now got three slightly different approaches to cross-platform build environment handling:

  • pyodide: running Python through node on the local machine to create a "native" venv
  • iOS: using an environment conversion script provided by the support package
  • Android: using an environment conversion script provided with cibuildwheel

There's enough commonality in the requirements of the three platforms that there's likely opportunity to standardise the approach that is used for cross-platform environments. However, I don't think that needs to block merging this PR - I'm just flagging it as an area for future consolidation.

Copy link
Contributor
@joerick joerick left a comment

Choose a reason for hiding this comment

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

Brilliant work on this @mhsmith!

@henryiii henryiii merged commit 0f487ee into pypa:main Jul 23, 2025
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for Android and iOS
7 participants
0