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.