[ANN] Simplified Android cross-compiler with DkML

DkML has had a cross-compiler for years, but I have cleaned it up so that it is much easier to use for Android developers. It now works with a regular opam installation with a custom repository. Also included are patches to the OCaml compiler to work with Android NDK 21+ (currently Google is at NDK 27).

Try it out if you do Android development … just copy-and-paste the instructions below … but please read the notes and cautions below. And if you are still interested in Android development, tell me so I can decide if I’ll merge the packages into the regular opam repository.


Trimmed slightly from the dkml-compiler Quick Start:

  • Docker container is used below for Windows and macOS users, and because it is easy to get the Android NDK from CircleCI.
  • Apple Silicon does not support 32-bit. The net effect is that Apple Silicon users cannot cross-compile android_arm32v7a.
$ docker run -it --rm cimg/android:2024.10.1-ndk

# Install opam if you don't have it
~/project$ sudo apt-get update && sudo apt-get install build-essential curl git patch rsync unzip -y
~/project$ echo /usr/local/bin | sudo bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --version 2.2.1"

# Initialize opam if you haven't already. No sandboxing is needed in containers.
~/project$ opam init --cli=2.1 --no-setup --bare --disable-sandboxing

# Two Android options to set. ANDROID_PLATFORM is the minimum API level ("targetSdkVersion" in the Android manifest)
~/project$ opam var --cli=2.1 --global ANDROID_NDK=/home/circleci/android-sdk/ndk/27.1.12297006
~/project$ opam var --cli=2.1 --global ANDROID_PLATFORM=android-34

# PICK ONE: Android arm64-v8a switch
~/project$ opam switch create android34-ndk27-arm64-v8a --cli=2.1 \
  --packages dkml-base-compiler,dkml-host-abi-linux_x86_64,dkml-target-abi-android_arm64v8a,ocamlfind,conf-dkml-cross-toolchain \
  --repos default,diskuv-4d79e732=git+https://github.com/diskuv/diskuv-opam-repository.git#4d79e732

# PICK ONE: Android armeabi-v7a switch. You will need a 32-bit C/C++ compiler.
~/project$ sudo apt-get install gcc-multilib g++-multilib -y
~/project$ opam switch create android34-ndk27-armeabi-v7a --cli=2.1 \
  --packages dkml-base-compiler,dkml-host-abi-linux_x86,dkml-target-abi-android_arm32v7a,ocamlfind,conf-dkml-cross-toolchain \
  --repos default,diskuv-4d79e732=git+https://github.com/diskuv/diskuv-opam-repository.git#4d79e732

# PICK ONE: Android x86_64 switch
~/project$ opam switch create android34-ndk27-x86_64 --cli=2.1 \
  --packages dkml-base-compiler,dkml-host-abi-linux_x86_64,dkml-target-abi-android_x86_64,ocamlfind,conf-dkml-cross-toolchain \
  --repos default,diskuv-4d79e732=git+https://github.com/diskuv/diskuv-opam-repository.git#4d79e732

# THEN: Get and cross-compile your source code. Here we use Dune and assume 'android34-ndk27-arm64-v8a'
~/project$ opam install --cli=2.1 --switch android34-ndk27-arm64-v8a dune
~/project$ git clone https://github.com/avsm/hello-world-action-ocaml hello
~/project$ cd hello
~/project/hello$ opam exec --cli=2.1 --switch android34-ndk27-arm64-v8a -- \
  dune build -x android_arm64v8a world.exe

~/project/hello$ file _build/default*/world.exe
_build/default.android_arm64v8a/world.exe: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, with debug_info, not stripped
_build/default/world.exe:                  ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1731ad9ce0fdeff69df28af0b1217e843eabe26e, for GNU/Linux 3.2.0, with debug_info, not stripped

# You can also directly use the ocamlfind -toolchain

~/project$ opam exec --cli=2.1 --switch android34-ndk27-arm64-v8a -- \
  ocamlfind ocamlc -config-var native_c_compiler
gcc -O2 -fno-strict-aliasing -fwrapv -pthread -fPIC  -D_FILE_OFFSET_BITS=64

~/project$ opam exec --cli=2.1 --switch android34-ndk27-arm64-v8a -- \
  ocamlfind -toolchain android_arm64v8a ocamlc -config-var native_c_compiler
/home/circleci/android-sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang -O2 -fno-strict-aliasing -fwrapv -pthread -fPIC  -D_FILE_OFFSET_BITS=64

DkML supports three out of the four supported Android ABIs. The three ABIs (all but x86) were chosen based on statistics for a large game on Aug 29, 2023:

Arch Percent
arm64-v8a 68.66
armeabi-v7a 30.38
x86_64 0.71
x86 0.26

and also Google’s recommendation:

Note: While 64-bit-only devices will grow in popularity with phones joining Android Auto in this group, 32-bit-only devices will continue to be important for Android Go, Android TV, and Android Wear. Please continue supporting 32-bit ABIs; Google Play will continue serving 32-bit apps to 32-bit-only devices.


Finally, a word of CAUTION. The Android cross-compiler can never use OCaml 5+ because OCaml 5 will never bring back the 32-bit instruction set. That means if you don’t want to drop a large percent of your users or drop new Android categories over the next five (?) years, you will have a critical dependency on DkML.

4 Likes

Hi there @jbeckford,
This is very interesting!
Do you think this setup for a cross-compiler for Android ARMv7 could also be used to compile OCaml programs to the Numworks graphical calculator?
It’s also based on ARMv7 32 bits.
I opened this question in 2021, and I would like to finish the job this time.
Thanks a lot, if you have any valuable inputs!

@Naereen The creator of OCaml has already said 256KB RAM is completely unusable for OCaml. That hasn’t changed. Could we add a tiny OCaml interpreter to Numworks graphical calculators? - #9 by xavierleroy