Build D for Android
These instructions show you how to build D command-line executables and OpenGL ES GUI apps for Android, by using the native and cross-compilers available here. There are separate steps for cross-compilation, ie building apps on a linux/x64 PC and running on Android/ARM, versus native compilation, building and running on your Android/ARM device itself.
Since you cannot install the Android SDK on Android, I end by showing how to package a GUI Android app, a zip file called an .apk, from scratch, by using the tools available in the Termux app for Android, a terminal emulator app and OSS package manager/repository for Android devices.
Contents
Prerequisites
Cross-compilation
- linux/x64 shell, where you'll run the ldc cross-compiler, called the linux host
- You can use a virtual machine like VirtualBox/VMware on Windows or Mac, with your favorite linux distro installed.
- Windows 10: You can alternately use Bash on Ubuntu on Windows (the Windows Subsystem for Linux).
- Android native toolchain, the NDK and optionally the SDK
- The SDK is necessary if you want to package a GUI app; the NDK is enough if you just want to build a command-line binary.
- Android/ARM, whether a device or emulator, to run your D code
- The SDK comes with an emulator. I use actual hardware, so that's what I'll discuss. Make sure it's a 32-bit ARM device, as 64-bit ARM is not supported yet.
- If using a device, you need some way to transfer the app over. There are several ways to do this, here are a few I've tried:
- Install an ssh server app on your device and scp the app over. Alternately, set up an ssh server on your linux host, and use an ssh/scp client on Android to get the app. This is what I do, by using the OpenSSH client in Termux.
- Host the app in a web server and get it by using your Android browser or a downloader app.
- Setup the Android Debug Bridge (adb) on your device and use the SDK tools to push your files over.
Native compilation
- Termux for Android, available in the official Play Store, APKMirror, or F-Droid
- Check if it's a 32-bit ARM device by running "uname -m". If it returns "armv7l", you're good. If it says "aarch64," your 64-bit ARM device is not supported by ldc yet.
Setup
Once you've got a linux/x64 shell setup or the Termux app installed, get the ldc compiler for Android and the NDK for cross-compilation.
Cross-compilation
Make sure curl is available, or use the equivalent wget command. You will need tar to unpack ldc and unzip for the NDK. I show the command to install unzip for Ubuntu, use the right package manager command for your distro.
sudo apt-get install unzip
curl -L -O https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip
unzip android-ndk-r15c-linux-x86_64.zip
export NDK=/path/to/your/android-ndk-r15c
curl -L -O https://github.com/joakim-noah/android/releases/download/tea/ldc2-android-arm-1.3.1-beta2-linux-x86_64.tar.xz
tar xf ldc2-android-arm-1.3.1-beta2-linux-x86_64.tar.xz
export LDC=/path/to/your/ldc2-android-arm-1.3.1-beta2-linux-x86_64
Make sure the NDK and LDC variables are set to the full path where they are located.
Native compilation
You need the clang compiler and the linker it automatically installs, as ldc tries to use the local C compiler for linking.
apt install clang curl
curl -L -O https://github.com/joakim-noah/android/releases/download/tea/ldc_1.3.0_arm.deb
dpkg -i ldc_1.3.0_arm.deb
Build a command-line executable
Now that we have a D compiler for Android, let's try building a small program, the classic Sieve of Eratosthenes single-core benchmark, which finds all prime numbers up to a number you choose.
Cross-compilation
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d
$LDC/bin/ldc2 -c sieve.d
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-z,nocopyreloc
--sysroot=$NDK/platforms/android-16/arch-arm -lgcc
-gcc-toolchain $NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
-target armv7-none-linux-androideabi -no-canonical-prefixes -fuse-ld=bfd
-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro
-Wl,-z,now -fPIE -pie -Wl,--export-dynamic -lc -lm sieve.o
$LDC/lib/libphobos2-ldc.a $LDC/lib/libdruntime-ldc.a -o sieve
Copy this sieve program onto an Android device or emulator. Here's how I do it in Termux, with an ssh server running on the linux host:
cd
scp jo@my.server.com:sieve .
Native compilation
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d
ldc2 sieve.d
Run the sieve program
The sieve program will tell you how many prime numbers there are in the first n integers, a limit you can specify. Run this command to find how many primes there are in the first million integers:
./sieve 1000000
If you built sieve successfully, it should return
78498 primes
Build a sample OpenGL ES 1.0 GUI app ported to D
Clone my android repository, which contains several headers and sample OpenGL apps from the NDK translated to D, and build the Native Activity app, which is written completely in D. As you'll see below, D code for an apk must be compiled to a shared library, which the Android runtime will call.
Cross-compilation
After cloning my Android repo, go to the sample app, compile the D source, then link the objects into a shared library and place it in the directory that the SDK expects:
sudo apt-get install git
git clone https://github.com/joakim-noah/android.git
cd android/samples/native-activity/
$LDC/bin/ldc2 -I../../ -c jni/main.d
$LDC/bin/ldc2 -I../../ -c ../../android/sensor.d
$LDC/bin/ldc2 -I../../ -c ../../android_native_app_glue.d
mkdir -p libs/armeabi-v7a/
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-soname,libnative-activity.so
-shared --sysroot=$NDK/platforms/android-16/arch-arm main.o sensor.o
android_native_app_glue.o $LDC/lib/libphobos2-ldc.a $LDC/lib/libdruntime-ldc.a
-lgcc -gcc-toolchain $NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
-no-canonical-prefixes -fuse-ld=bfd -target armv7-none-linux-androideabi
-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now
-llog -landroid -lEGL -lGLESv1_CM -lc -lm -o libs/armeabi-v7a/libnative-activity.so
Finally, package the app as the SDK directs. I document the older Ant approach, which is deprecated, replace it with the Gradle command from a newer SDK. With Ant, set the path to your SDK, then run these commands:
export SDK=/path/to/your/android-sdk-linux
$SDK/tools/android update project -p . -s --target 1
ant debug
Transfer the resulting bin/NativeActivity-debug.apk to your device.
cd /sdcard/Download/
scp jo@my.server.com:android/samples/native-activity/bin/NativeActivity-debug.apk .
Native compilation
apt install git
git clone https://github.com/joakim-noah/android.git
cd android/samples/native-activity/
ldc2 -I../../ -c jni/main.d
ldc2 -I../../ -c ../../android/sensor.d
ldc2 -I../../ -c ../../android_native_app_glue.d
mkdir -p lib/armeabi-v7a/
$PREFIX/bin/clang -Wl,-soname,libnative-activity.so -shared main.o sensor.o
android_native_app_glue.o $PREFIX/lib/libphobos2-ldc.a $PREFIX/lib/libdruntime-ldc.a
-lgcc -no-canonical-prefixes -target armv7-none-linux-androideabi -Wl,--fix-cortex-a8
-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -llog -landroid -lEGL
-lGLESv1_CM -lc -lm -o lib/armeabi-v7a/libnative-activity.so
Follow the instructions below to package this native shared library into an Android apk.
Install and run the sample GUI app
Go to Settings->Security on your Android device and allow installation of apps from unknown sources, ie from outside the Play Store, then go to /sdcard/Download in your file manager and choose the NativeActivity-debug apk to install it. Open the app after installing or go to your app folder and run the app named NativeActivity: it'll show a black screen initially, then flash a bunch of colors when the screen is touched.
Build a sample OpenGL ES 2.0 GUI app mostly written in D, with some Java
Cross-compilation
This app comes with a simple script called build-apk, which will build the D shared library for you, as long as the LDC and NDK variables are set.
cd android/samples/Teapot/
./build-apk
Here are the contents of that script, so you can see what it's doing.
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/GLContext.d
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/JNIHelper.d
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/gestureDetector.d
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/perfMonitor.d
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/shader.d
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/tapCamera.d
$LDC/bin/ldc2 -I../../ -Ijni/ -Jjni/ -c jni/TeapotNativeActivity.d
$LDC/bin/ldc2 -I../../ -Jjni/ -c jni/TeapotRenderer.d
$LDC/bin/ldc2 -I../../ -c ../../android/sensor.d
$LDC/bin/ldc2 -I../../ -c ../../android_native_app_glue.d
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-soname,libTeapotNativeActivity.so
-shared --sysroot=$NDK/platforms/android-21/arch-arm TeapotNativeActivity.o sensor.o
TeapotRenderer.o android_native_app_glue.o GLContext.o JNIHelper.o gestureDetector.o
perfMonitor.o shader.o tapCamera.o $LDC/lib/libphobos2-ldc.a $LDC/lib/libdruntime-ldc.a
-lgcc -gcc-toolchain $NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
-no-canonical-prefixes -fuse-ld=bfd -target armv7-none-linux-androideabi -Wl,--fix-cortex-a8
-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--warn-shared-textrel
-Wl,--fatal-warnings -llog -landroid -lEGL -lGLESv2 -lc -lm -o libTeapotNativeActivity.so
Package this shared library into an apk by using the SDK, as you would normally, and try installing and running it on your device.
Native compilation
The steps are the same as above, except for a marginally different linker command, which is included in the build script but commented out. However, this app requires compiling some Java code and the Java compilers in Termux aren't working at the moment, so I'll hold off on this for now.
Package an Android app from scratch on your Android device
Install aapt, the Android Asset Packaging Tool; apksigner, a tool to create a hashed manifest and sign your apps; and OpenSSL, to generate your certificate to sign your app.
apt install aapt apksigner openssl-tool
I'll demonstrate with the NativeActivity app built above.
cd android/samples/native-activity/
aapt package -M AndroidManifest.xml -S res -F NativeActivity-debug-unsigned.apk
aapt add NativeActivity-debug-unsigned.apk lib/armeabi-v7a/libnative-activity.so
This simple app only requires three files, AndroidManifest.xml, resources.arsc, and lib/armeabi-v7a/libnative-activity.so, which you can check with the following aapt command.
aapt list NativeActivity-debug-unsigned.apk
Now let's generate a hashed manifest, just like a Java jar file, and sign the app. Unfortunately, apksigner only supports Java Keystore files for signing right now and I don't know how to build one from scratch, so if you don't have a keystore, we'll have to use OpenSSL to sign the app. For now, we just sign it with a junk keystore that apksigner automatically generates.
apksigner junk.ks NativeActivity-debug-unsigned.apk NativeActivity-debug.apk
rm junk.ks
You should see three additional files in the apk, if you list its contents using the command above.
If you didn't supply a valid keystore instead of junk.ks, you need to use OpenSSL and a certificate to sign the apk.
More info on signing to come...