Skip to content

BLD: cross-compilation for android using cibuildwheel#29770

Open
kfchou wants to merge 3 commits into
numpy:mainfrom
kfchou:cibw-android
Open

BLD: cross-compilation for android using cibuildwheel#29770
kfchou wants to merge 3 commits into
numpy:mainfrom
kfchou:cibw-android

Conversation

@kfchou

@kfchou kfchou commented Sep 19, 2025

Copy link
Copy Markdown

Related to: #27698.

These configs enable you to run cibuildwheel --platform android to build android-compatible .whl files.

You can use briefcase to specify these wheels when you want to build a python-based android app that uses numpy. See discussion for details.

Testing
I tested these wheels using a demo briefcase app, importing numpy and get it to print out mathematical operations.

Questions for reviewers:

  • Should I include the demo briefcase app for testing? Not sure how I should do that
  • This build requires python 3.13. I'm not sure how to specify this requirement.

- pyproject: remove unused android test package
- cibw_before_build_android.sh: remove redundant code
- remove android.meson.cross: cross files are dynamically generated via cibw_before_build_android.sh
@github-actions github-actions Bot added the 36 - Build Build related PR label Sep 19, 2025
@andyfaff

Copy link
Copy Markdown
Member

A discussion is needed before we add wheel support for the Android platform. These discussions take place on numpy-discussion@scipy.org, as well as the regular online meetings (advertised on the mailing list).

Whilst Android is obviously quite popular we need to know that finite resources (developer time, CI time) is going spent effectively. i.e. we need to know that demand will be there.

Probably the first step would be to add a CI entry, rather than distributing a wheel.

@andyfaff

Copy link
Copy Markdown
Member

Ok, so the original issue said all of that and more. We'd probably be looking for a CI entry for regular test runs, not building a wheel.

# Override system library detection for Android - math functions are in libc
c_args = ['-DHAVE_SIN=1', '-DHAVE_COS=1', '-DHAVE_TAN=1', '-DHAVE_SINH=1', '-DHAVE_COSH=1', '-DHAVE_TANH=1', '-DHAVE_ASIN=1', '-DHAVE_ACOS=1', '-DHAVE_ATAN=1', '-DHAVE_ATAN2=1', '-DHAVE_EXP=1', '-DHAVE_LOG=1', '-DHAVE_LOG10=1', '-DHAVE_SQRT=1', '-DHAVE_FLOOR=1', '-DHAVE_CEIL=1', '-DHAVE_FABS=1', '-DHAVE_POW=1', '-DHAVE_FMOD=1', '-DHAVE_LDEXP=1', '-DHAVE_FREXP=1', '-DHAVE_MODF=1', '-DHAVE_COPYSIGN=1', '-DHAVE_FINITE=1', '-DHAVE_ISINF=1', '-DHAVE_ISNAN=1', '-DHAVE_ISFINITE=1', '-DHAVE_EXPM1=1', '-DHAVE_LOG1P=1', '-DHAVE_ASINH=1', '-DHAVE_ACOSH=1', '-DHAVE_ATANH=1', '-DHAVE_RINT=1', '-DHAVE_TRUNC=1', '-DHAVE_EXP2=1', '-DHAVE_LOG2=1', '-DHAVE_HYPOT=1', '-DHAVE_CBRT=1', '-DHAVE_NEXTAFTER=1', '-DHAVE_CSIN=1', '-DHAVE_CCOS=1', '-DHAVE_CTAN=1', '-DHAVE_CSINH=1', '-DHAVE_CCOSH=1', '-DHAVE_CTANH=1', '-DHAVE_CASIN=1', '-DHAVE_CACOS=1', '-DHAVE_CATAN=1', '-DHAVE_CASINH=1', '-DHAVE_CACOSH=1', '-DHAVE_CATANH=1', '-DHAVE_CEXP=1', '-DHAVE_CLOG=1', '-DHAVE_CPOW=1', '-DHAVE_CSQRT=1', '-DHAVE_CABS=1', '-DHAVE_CARG=1', '-DHAVE_CIMAG=1', '-DHAVE_CREAL=1', '-DHAVE_CONJ=1', '-DHAVE_STRTOLL=1', '-DHAVE_STRTOULL=1', '-DHAVE_STRTOLD=1']
cpp_args = ['-DHAVE_SIN=1', '-DHAVE_COS=1', '-DHAVE_TAN=1', '-DHAVE_SINH=1', '-DHAVE_COSH=1', '-DHAVE_TANH=1', '-DHAVE_ASIN=1', '-DHAVE_ACOS=1', '-DHAVE_ATAN=1', '-DHAVE_ATAN2=1', '-DHAVE_EXP=1', '-DHAVE_LOG=1', '-DHAVE_LOG10=1', '-DHAVE_SQRT=1', '-DHAVE_FLOOR=1', '-DHAVE_CEIL=1', '-DHAVE_FABS=1', '-DHAVE_POW=1', '-DHAVE_FMOD=1', '-DHAVE_LDEXP=1', '-DHAVE_FREXP=1', '-DHAVE_MODF=1', '-DHAVE_COPYSIGN=1', '-DHAVE_FINITE=1', '-DHAVE_ISINF=1', '-DHAVE_ISNAN=1', '-DHAVE_ISFINITE=1', '-DHAVE_EXPM1=1', '-DHAVE_LOG1P=1', '-DHAVE_ASINH=1', '-DHAVE_ACOSH=1', '-DHAVE_ATANH=1', '-DHAVE_RINT=1', '-DHAVE_TRUNC=1', '-DHAVE_EXP2=1', '-DHAVE_LOG2=1', '-DHAVE_HYPOT=1', '-DHAVE_CBRT=1', '-DHAVE_NEXTAFTER=1', '-DHAVE_CSIN=1', '-DHAVE_CCOS=1', '-DHAVE_CTAN=1', '-DHAVE_CSINH=1', '-DHAVE_CCOSH=1', '-DHAVE_CTANH=1', '-DHAVE_CASIN=1', '-DHAVE_CACOS=1', '-DHAVE_CATAN=1', '-DHAVE_CASINH=1', '-DHAVE_CACOSH=1', '-DHAVE_CATANH=1', '-DHAVE_CEXP=1', '-DHAVE_CLOG=1', '-DHAVE_CPOW=1', '-DHAVE_CSQRT=1', '-DHAVE_CABS=1', '-DHAVE_CARG=1', '-DHAVE_CIMAG=1', '-DHAVE_CREAL=1', '-DHAVE_CONJ=1', '-DHAVE_STRTOLL=1', '-DHAVE_STRTOULL=1', '-DHAVE_STRTOLD=1']
EOF

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should all end up in one of the config.h files, not as command line flags

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not even that, if these checks actually fail, they can either be fixed in numpy/_core/meson.build or a simple skip for the checks can be added there. That goes for has_function_sin et al. higher up too.

@mattip

mattip commented Sep 22, 2025

Copy link
Copy Markdown
Member

Should I include the demo briefcase app for testing? Not sure how I should do that

We do have aarch64 testing base on github ubuntu-22.04-arm runners. Is there a way to leverage those to test android? I think testing would be a better first step than just wheel building.

@rgommers rgommers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on this @kfchou! I left some more detailed comments; I'd say the overall goal is to make the custom code we need to carry for this as small and numpy-specific as possible, and having one CI job that tests that building an Android wheel actually works as intended.

Should I include the demo briefcase app for testing? Not sure how I should do that

No, that is undesirable. The right approach is running the test suite (or a relevant subset of it, depending on how show things run) under an emulator. On gh-27698 it was pointed out that this is possible.

This build requires python 3.13. I'm not sure how to specify this requirement.

The general config files (pyproject.toml, cibw_before*, repair_*) should not make such an assumption, since they should work for any Python version >=3.13. In the CI job to test this cross build, 3.13 can be picked.

https://github.com/beeware/toga has examples of CI jobs that may be helpful here.

set -e
set -x

# This script is designed to be called by cibuildwheecho "--- END WORKAROUND ---"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Accidental edit at the end of this sentence

echo "CIBW_ARCHS: ${CIBW_ARCHS:-'(not set)'}"
echo "CIBW_ARCHS_ANDROID: ${CIBW_ARCHS_ANDROID:-'(not set)'}"

# Check for architecture in standard cibuildwheel environment variablesbuilding for Android.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

typo in "variablesbuilding"

# WORKAROUND FOR CIBUILDWHEEL ISSUE:
# cibuildwheel documentation states that CIBW_ARCHS should be available for Android builds,
# but cibuildwheel 3.1.4 does NOT set these environment variables for Android.
# This appears to be a bug or incomplete implementation in cibuildwheel.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It'd be good to fix that upstream in cibuildwheel and add a link here to that issue or PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a misunderstanding: cibuildwheel reads the CIBW variables as one possible means of configuration, it doesn't set them itself.

# As a workaround, we detect the architecture from various sources:
# 1. Check if CIBW_ARCHS is already set (future-proofing)
# 2. Look for architecture hints in environment variables
# 3. Check process command line for architecture indicators

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You already set this in pyproject.toml, so three different ways of handling it here seems like overkill?


echo "--- Building for architecture: $TARGET_ARCH ---"

# Set the target API level from cibuildwheel's environment variable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also already handled in pyproject.toml

has_function_strtold = true

# Skip BLAS/LAPACK for Android to avoid complex math library dependencies
allow-noblas = true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This goes in [tool.cibuildwheel.android] in pyproject.toml normally. There's already an example for 32-bit Windows there.

# This ensures symbols like PyExc_ValueError and math functions are available
# Note: On Android, math functions are part of libc, not a separate libm
c_link_args = ['-lpython3.13']
cpp_link_args = ['-lpython3.13']

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this isn't handled correctly in Meson, that's where this should be fixed. That also avoids hardcoding the interpreter version here.

# Override system library detection for Android - math functions are in libc
c_args = ['-DHAVE_SIN=1', '-DHAVE_COS=1', '-DHAVE_TAN=1', '-DHAVE_SINH=1', '-DHAVE_COSH=1', '-DHAVE_TANH=1', '-DHAVE_ASIN=1', '-DHAVE_ACOS=1', '-DHAVE_ATAN=1', '-DHAVE_ATAN2=1', '-DHAVE_EXP=1', '-DHAVE_LOG=1', '-DHAVE_LOG10=1', '-DHAVE_SQRT=1', '-DHAVE_FLOOR=1', '-DHAVE_CEIL=1', '-DHAVE_FABS=1', '-DHAVE_POW=1', '-DHAVE_FMOD=1', '-DHAVE_LDEXP=1', '-DHAVE_FREXP=1', '-DHAVE_MODF=1', '-DHAVE_COPYSIGN=1', '-DHAVE_FINITE=1', '-DHAVE_ISINF=1', '-DHAVE_ISNAN=1', '-DHAVE_ISFINITE=1', '-DHAVE_EXPM1=1', '-DHAVE_LOG1P=1', '-DHAVE_ASINH=1', '-DHAVE_ACOSH=1', '-DHAVE_ATANH=1', '-DHAVE_RINT=1', '-DHAVE_TRUNC=1', '-DHAVE_EXP2=1', '-DHAVE_LOG2=1', '-DHAVE_HYPOT=1', '-DHAVE_CBRT=1', '-DHAVE_NEXTAFTER=1', '-DHAVE_CSIN=1', '-DHAVE_CCOS=1', '-DHAVE_CTAN=1', '-DHAVE_CSINH=1', '-DHAVE_CCOSH=1', '-DHAVE_CTANH=1', '-DHAVE_CASIN=1', '-DHAVE_CACOS=1', '-DHAVE_CATAN=1', '-DHAVE_CASINH=1', '-DHAVE_CACOSH=1', '-DHAVE_CATANH=1', '-DHAVE_CEXP=1', '-DHAVE_CLOG=1', '-DHAVE_CPOW=1', '-DHAVE_CSQRT=1', '-DHAVE_CABS=1', '-DHAVE_CARG=1', '-DHAVE_CIMAG=1', '-DHAVE_CREAL=1', '-DHAVE_CONJ=1', '-DHAVE_STRTOLL=1', '-DHAVE_STRTOULL=1', '-DHAVE_STRTOLD=1']
cpp_args = ['-DHAVE_SIN=1', '-DHAVE_COS=1', '-DHAVE_TAN=1', '-DHAVE_SINH=1', '-DHAVE_COSH=1', '-DHAVE_TANH=1', '-DHAVE_ASIN=1', '-DHAVE_ACOS=1', '-DHAVE_ATAN=1', '-DHAVE_ATAN2=1', '-DHAVE_EXP=1', '-DHAVE_LOG=1', '-DHAVE_LOG10=1', '-DHAVE_SQRT=1', '-DHAVE_FLOOR=1', '-DHAVE_CEIL=1', '-DHAVE_FABS=1', '-DHAVE_POW=1', '-DHAVE_FMOD=1', '-DHAVE_LDEXP=1', '-DHAVE_FREXP=1', '-DHAVE_MODF=1', '-DHAVE_COPYSIGN=1', '-DHAVE_FINITE=1', '-DHAVE_ISINF=1', '-DHAVE_ISNAN=1', '-DHAVE_ISFINITE=1', '-DHAVE_EXPM1=1', '-DHAVE_LOG1P=1', '-DHAVE_ASINH=1', '-DHAVE_ACOSH=1', '-DHAVE_ATANH=1', '-DHAVE_RINT=1', '-DHAVE_TRUNC=1', '-DHAVE_EXP2=1', '-DHAVE_LOG2=1', '-DHAVE_HYPOT=1', '-DHAVE_CBRT=1', '-DHAVE_NEXTAFTER=1', '-DHAVE_CSIN=1', '-DHAVE_CCOS=1', '-DHAVE_CTAN=1', '-DHAVE_CSINH=1', '-DHAVE_CCOSH=1', '-DHAVE_CTANH=1', '-DHAVE_CASIN=1', '-DHAVE_CACOS=1', '-DHAVE_CATAN=1', '-DHAVE_CASINH=1', '-DHAVE_CACOSH=1', '-DHAVE_CATANH=1', '-DHAVE_CEXP=1', '-DHAVE_CLOG=1', '-DHAVE_CPOW=1', '-DHAVE_CSQRT=1', '-DHAVE_CABS=1', '-DHAVE_CARG=1', '-DHAVE_CIMAG=1', '-DHAVE_CREAL=1', '-DHAVE_CONJ=1', '-DHAVE_STRTOLL=1', '-DHAVE_STRTOULL=1', '-DHAVE_STRTOLD=1']
EOF

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not even that, if these checks actually fail, they can either be fixed in numpy/_core/meson.build or a simple skip for the checks can be added there. That goes for has_function_sin et al. higher up too.

import sysconfig
import os

# Check BLDLIBRARY configuration

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left-over debug code from here onwards?

@@ -0,0 +1,142 @@
#!/bin/bash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it should be either a separate tool or part of Briefcase. It's not specific to numpy, just a generic wheel repair utility.

@kfchou

kfchou commented Oct 7, 2025

Copy link
Copy Markdown
Author

Thanks for your feedback everyone. @rgommers, I'm slowly working through your comments.

Admittedly, most of the this work is done by directing copilot/sonnet since I'm not experience with Meson or C.

@mhsmith

mhsmith commented Oct 12, 2025

Copy link
Copy Markdown
Contributor

We do have aarch64 testing base on github ubuntu-22.04-arm runners. Is there a way to leverage those to test android?

Unfortunately I found that none of GitHub's ARM runners can run the Android emulator (1, 2). They can only run it on Linux x86_64.

I think testing would be a better first step than just wheel building.

I would strongly recommend doing this through cibuildwheel, as it automaticaly handles all the details of setting up Android build and test environments, starting an emulator, etc. However, on Android it does require the test command to be a python -c or python -m command, so the shell script currently in the cibuildwheel section of pyproject.toml wouldn't work here.


# Try different approaches to fix BLDLIBRARY issue
# The issue is meson-python is inserting literal '$(BLDLIBRARY)' in link commands
# For Android, we don't want to link Python extensions to libpython at all

@mhsmith mhsmith Dec 9, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We absolutely do want to link them to libpython, but if it's using the Python .pc file to achieve this, then this may be affected by python/cpython#138800.

@mhsmith

mhsmith commented Dec 17, 2025

Copy link
Copy Markdown
Contributor

This PR has now been overtaken by #30412, so I suggest closing it.

torlando-tech pushed a commit to torlando-tech/android-python-wheels that referenced this pull request Jan 3, 2026
NumPy on Android requires complex.h for creal/cimag functions.
See: numpy/numpy#29770
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

36 - Build Build related PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants