Difference between revisions of "Build D for Android"

From D Wiki
Jump to: navigation, search
(Use ldc package from Termux repo instead)
 
(47 intermediate revisions by 6 users not shown)
Line 1: Line 1:
These instructions show you how to build D command-line executables and OpenGL ES GUI apps for Android, by using [https://github.com/joakim-noah/android/releases the cross-compiler available here] or a native Android compiler.  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.
+
These instructions show you how to build D command-line executables and OpenGL ES GUI apps for Android, either by using [https://github.com/ldc-developers/ldc/releases the desktop D compilers for Windows, Mac, or Linux available here] or a native Android compiler.  There are separate steps for cross-compilation, ie building apps on a Windows/Linux PC or Mac and running the app on Android, versus native compilation, both building and running on your Android 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.
+
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 open-source package manager/repository for Android devices.
  
 
==Prerequisites==
 
==Prerequisites==
 
===Cross-compilation===
 
===Cross-compilation===
* linux/x64 shell, where you'll run the ldc cross-compiler, called the linux host
+
* A command shell on your host PC/Mac, where you'll run the LDC D compiler
** You can use a virtual machine like VirtualBox/VMware on Windows or Mac, with your favorite linux distro installed.
+
** Either a DOS command prompt or Powershell should work on Windows.
** Windows 10: You can alternately use Bash on Ubuntu on Windows (the Windows Subsystem for Linux).
+
** Any shell should work on Mac and Linux, typical commands for the <tt>bash</tt> shell are shown.
* Android native toolchain, [https://developer.android.com/ndk/index.html the NDK] and optionally [https://developer.android.com/studio/index.html the SDK]
+
* Version r20 or r21 of the Android [https://developer.android.com/ndk/ NDK] and optionally the [https://developer.android.com/studio SDK]. Other versions might work but reports of version 22+ say the linker changed and it won't just work anymore.
** 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.
+
** The SDK is necessary 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
+
* A recent LDC compiler for your host platform
** 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.
+
** It's best to use an [https://github.com/ldc-developers/ldc/releases official release from GitHub], as it's built against a [https://github.com/ldc-developers/llvm-project/releases slightly tweaked LLVM] with custom TLS emulation for Android targets.  If using LDC from your distro or elsewhere, make sure it was built against our tweaked LLVM, otherwise it will not compile properly for Android.
** 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:
+
* Android, whether a device or emulator, to run your D code
# 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.  
+
** The SDK comes with an emulator.  I use actual hardware, so that's what I'll discuss.
 +
** When 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 Android device and scp the app over.  Alternately, set up an ssh server on your host PC/Mac, and use an ssh/scp client on Android to get the app.  This is what I do, by using the OpenSSH package in Termux.  
 
# Host the app in a web server and get it by using your Android browser or a downloader app.
 
# Host the app in a web server and get it by using your Android browser or a downloader app.
 
# [https://developer.android.com/studio/command-line/adb.html Setup the Android Debug Bridge (adb) on your device] and use the SDK tools to push your files over.
 
# [https://developer.android.com/studio/command-line/adb.html Setup the Android Debug Bridge (adb) on your device] and use the SDK tools to push your files over.
  
 
===Native compilation===
 
===Native compilation===
 +
* Android Version 10 ("Q"), as LDC compiler support does not exist for Android Version 9 ("Pie") and earlier.
 
* Termux for Android, available in [https://play.google.com/store/apps/details?id=com.termux&hl=en the official Play Store], [https://www.apkmirror.com/apk/fredrik-fornwall/termux/ APKMirror], or [https://f-droid.org/packages/com.termux/ F-Droid]
 
* Termux for Android, available in [https://play.google.com/store/apps/details?id=com.termux&hl=en the official Play Store], [https://www.apkmirror.com/apk/fredrik-fornwall/termux/ APKMirror], or [https://f-droid.org/packages/com.termux/ 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.
+
* LDC for Termux: <code>apt install ldc</code> With Android version 9 and earlier, the install will fail.
  
==Setup==
+
==Cross-compilation 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.
+
Once you have LDC and have unzipped the Android NDK, it's time to set up LDC for the desired Android target(s). See [[Cross-compiling with LDC]] for the general guide; I present two examples for a quick summary:
  
===Cross-compilation===
+
* Targeting 32-bit Android/ARMv7-A on a Win64 host:
 +
*# Download the prebuilt <tt>android-armv7a</tt> [https://github.com/ldc-developers/ldc/releases/ package from GitHub] matching the version of your LDC.
 +
*# Extract the <tt>lib</tt> directory into your LDC installation directory and rename it, e.g., to <tt>lib-android_armv7a</tt>.
 +
*# Open <tt><LDC install dir>\etc\ldc2.conf</tt> in a text editor and append a section like this, adapting lib and NDK paths as needed:
 +
<pre>
 +
"armv7a-.*-linux-android":
 +
{
 +
    switches = [
 +
        "-defaultlib=phobos2-ldc,druntime-ldc",
 +
        "-link-defaultlib-shared=false",
 +
        "-gcc=C:/LDC/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64/bin/armv7a-linux-androideabi21-clang.cmd",
 +
    ];
 +
    lib-dirs = [
 +
        "%%ldcbinarypath%%/../lib-android_armv7a",
 +
    ];
 +
    rpath = "";
 +
};
 +
</pre>
 +
* Targeting 64-bit Android/AArch64 on a Linux host:
 +
*# Download the prebuilt <tt>android-aarch64</tt> [https://github.com/ldc-developers/ldc/releases/ package from GitHub] matching the version of your LDC.
 +
*# Extract the <tt>lib</tt> directory into your LDC installation directory and rename it, e.g., to <tt>lib-android_aarch64</tt>.
 +
*# Open <tt><LDC install dir>/etc/ldc2.conf</tt> in a text editor and append a section like this, adapting lib and NDK paths as needed:
 +
<pre>
 +
"aarch64-.*-linux-android":
 +
{
 +
    switches = [
 +
        "-defaultlib=phobos2-ldc,druntime-ldc",
 +
        "-link-defaultlib-shared=false",
 +
        "-gcc=/home/me/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang",
 +
    ];
 +
    lib-dirs = [
 +
        "%%ldcbinarypath%%/../lib-android_aarch64",
 +
    ];
 +
    rpath = "";
 +
};
 +
</pre>
  
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.
+
The prebuilt Android packages also include the corresponding x86 simulator libraries, so 32/64-bit x86 Android simulator targets can be set up the same way.
<syntaxhighlight lang=bash>
 
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
 
</syntaxhighlight>
 
 
 
Make sure the NDK and LDC variables are set to the full path where they are located.
 
 
 
===Native compilation===
 
 
 
Just install ldc from the Termux app, which will automatically pull in the clang compiler and a linker, as ldc tries to use the local C compiler for linking.
 
<syntaxhighlight lang=bash>
 
apt install ldc
 
</syntaxhighlight>
 
  
 
==Build a command-line executable==
 
==Build a command-line executable==
  
Now that we have a D compiler for Android, let's try building a small program, [https://github.com/dlang/dmd/blob/master/samples/sieve.d the classic Sieve of Eratosthenes single-core benchmark], which finds all prime numbers up to a number you choose.
+
Now that we have a D compiler setup for (one or more) Android targets, let's try building a small program, [https://github.com/dlang/dmd/blob/master/samples/sieve.d the classic Sieve of Eratosthenes single-core benchmark], which finds all prime numbers up to a number you choose. Install the curl package in Termux if you're natively compiling, <tt>apt install curl</tt>.
 
 
===Cross-compilation===
 
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
 +
# Load this link in your browser and download the file otherwise
 
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d
 
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d
  
$LDC/bin/ldc2 -c sieve.d
+
# Cross-compile & -link to ARMv7-A (on any host)
 +
ldc2 -mtriple=armv7a--linux-androideabi sieve.d
 +
 
 +
# Cross-compile & -link to AArch64 (on any host)
 +
ldc2 -mtriple=aarch64--linux-android sieve.d
  
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-z,nocopyreloc
+
# Compile & link natively in Termux
--sysroot=$NDK/platforms/android-16/arch-arm -lgcc
+
ldc2 sieve.d
-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
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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:
+
===Cross-compilation===
 +
 
 +
Copy this <tt>sieve</tt> program onto an Android device or emulator and set its permissions with the <tt>chmod</tt> command. Here's how I do it in Termux, with an ssh server running on the host PC/Mac with IP address 192.168.1.37:
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
 
apt install openssh
 
apt install openssh
 
cd
 
cd
scp jo@my.server.com:sieve .
+
scp jo@192.168.1.37:sieve .
</syntaxhighlight>
+
chmod 700 sieve
 
 
===Native compilation===
 
<syntaxhighlight lang=bash>
 
apt install curl
 
 
 
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d
 
 
 
ldc2 sieve.d
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
===Run the sieve program===
 
===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:
+
The <tt>sieve</tt> 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:
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 93: Line 103:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
If you built sieve successfully, it should return
+
If you built <tt>sieve</tt> successfully, it should return
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 101: Line 111:
 
==Build a sample OpenGL ES 1.0 GUI app ported to D==
 
==Build a sample OpenGL ES 1.0 GUI app ported to D==
  
Clone [https://github.com/joakim-noah/android 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.
+
Clone the [https://github.com/Diewi/android android repository] or download [https://github.com/Diewi/android/releases its source in a zip file], which contains several headers and sample OpenGL apps from the NDK translated to D:
 
 
===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:
 
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
sudo apt-get install git
+
sudo apt-get install git # In Termux, apt install git
 +
git clone https://github.com/Diewi/android.git
 +
cd android
  
git clone https://github.com/joakim-noah/android.git
+
# Alternatively, without git:
 +
curl -L -O https://github.com/Diewi/android/archive/build.zip
 +
unzip build.zip
 +
cd android-build
 +
</syntaxhighlight>
  
cd android/samples/native-activity/
+
Then build the Native Activity app, which is written completely in D. D code for an apk must be compiled to a shared library, which the Android runtime will call:
  
$LDC/bin/ldc2 -I../../ -c jni/main.d
+
<syntaxhighlight lang=bash>
 +
cd samples/native-activity
  
$LDC/bin/ldc2 -I../../ -c ../../android/sensor.d
+
ldc2 -I../.. jni/main.d ../../android/sensor.d ../../android_native_app_glue.d \
 +
    -shared -of=libs/arm64-v8a/libnative-activity.so \ # or `libs/armeabi-v7a/...` for 32-bit ARM
 +
    -L-soname -Llibnative-activity.so \
 +
    -mtriple=aarch64--linux-android # only for cross-compilation; use `armv7a--linux-androideabi` for 32-bit ARM
 +
    # possibly needed: -L-llog -L-landroid -L-lEGL -L-lGLESv1_CM
 +
</syntaxhighlight>
  
$LDC/bin/ldc2 -I../../ -c ../../android_native_app_glue.d
+
===Cross-compilation===
 
 
mkdir -p libs/armeabi-v7a/
 
  
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-soname,libnative-activity.so
+
====Ant approach====
-shared --sysroot=$NDK/platforms/android-16/arch-arm main.o sensor.o
+
Finally, package the app as the SDK directs: at this point, it's just like building a regular Android app.  I document the older Ant approach, which is deprecated, replace it with the Gradle command from a newer SDK.  With Ant on Mac or Linux, set the path to your SDK, then run these commands:
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
 
</syntaxhighlight>
 
 
 
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:
 
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
export SDK=/path/to/your/android-sdk-linux
+
export SDK=/path/to/your/android-sdk
 
$SDK/tools/android update project -p . -s --target 1
 
$SDK/tools/android update project -p . -s --target 1
 
ant debug
 
ant debug
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Transfer the resulting bin/NativeActivity-debug.apk to your device.
+
====Android Studio approach====
  
<syntaxhighlight lang=bash>
+
Enter on your <application name>/app/src/main and create a folder called jniLibs, this folder is <b>extremely important</b>, it is the default folder to put your shared libraries to be imported together with your .apk. If you wish to use other name for it, you will need to change your gradle file. For actually putting your libraries inside that folder, you will actually need to make directories for the target architectures, so, create inside it:
cd /sdcard/Download/
 
scp jo@my.server.com:android/samples/native-activity/bin/NativeActivity-debug.apk .
 
</syntaxhighlight>
 
  
===Native compilation===
+
* armeabi-v7a (For that, it is commonly used ldc2(version)-android-armv7a/lib
<syntaxhighlight lang=bash>
+
* arm64-v8a (This is our target right now, ldc2(version)-android-aarch64/lib)
apt install git
+
* x86 (It is the lib32 for armv7a -> ldc2(version)-android-armv7a/lib686
 +
* x86_64 (It is the lib32 for the aarch64 -> ldc2(version)-android-aarch64/lib-x86_64 For reference, check ndk abi guide from official android site: [https://developer.android.com/ndk/guides/abis Android ABI Guide]
  
git clone https://github.com/joakim-noah/android.git
+
After creating those folders, you can actually move your shared library inside one of them, just click on run and you're good to go.
  
cd android/samples/native-activity/
+
---
  
ldc2 -I../../ -c jni/main.d
+
Transfer the resulting <tt>bin/NativeActivity-debug.apk</tt> to your Android device, again shown here by using <tt>scp</tt> from the Termux app.
  
ldc2 -I../../ -c ../../android/sensor.d
+
<syntaxhighlight lang=bash>
 +
scp jo@192.168.1.37:android/samples/native-activity/bin/NativeActivity-debug.apk /sdcard/Download/
 +
</syntaxhighlight>
  
ldc2 -I../../ -c ../../android_native_app_glue.d
+
===Native compilation===
 
 
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
 
</syntaxhighlight>
 
  
 
[[#Package_an_Android_app_from_scratch_on_your_Android_device|Follow the instructions below to package this native shared library into an Android apk]].
 
[[#Package_an_Android_app_from_scratch_on_your_Android_device|Follow the instructions below to package this native shared library into an Android apk]].
  
 
===Install and run the sample GUI app===
 
===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.
+
Go to <tt>Settings->Security</tt> on your Android device and allow installation of apps from unknown sources, ie from outside the Play Store, then go to <tt>/sdcard/Download</tt> in your file manager and choose the <tt>NativeActivity-debug</tt> apk to install it.  Open the app after installing or go to your app folder and run the app named <tt>NativeActivity</tt>: 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==
 
==Build a sample OpenGL ES 2.0 GUI app mostly written in D, with some Java==
  
===Cross-compilation===
+
This D app has not been ported to 64-bit Android/ARM yet, only 32-bit ARM compilation will work for now:
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.
 
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
cd android/samples/Teapot/
+
cd samples/Teapot
./build-apk
+
 
 +
ldc2 -I../.. -Ijni -Jjni \
 +
    ../../ndk_helper/GLContext.d \
 +
    ../../ndk_helper/JNIHelper.d \
 +
    ../../ndk_helper/gestureDetector.d \
 +
    ../../ndk_helper/perfMonitor.d \
 +
    ../../ndk_helper/shader.d \
 +
    ../../ndk_helper/tapCamera.d \
 +
    jni/TeapotNativeActivity.d \
 +
    jni/TeapotRenderer.d \
 +
    ../../android/sensor.d \
 +
    ../../android_native_app_glue.d \
 +
    -shared -of=libs/armeabi-v7a/libTeapotNativeActivity.so \
 +
    -L-soname -LlibTeapotNativeActivity.so \
 +
    -mtriple=armv7a--linux-androideabi # only for cross-compilation
 +
    # possibly needed: -L-llog -L-landroid -L-lEGL -L-lGLESv2
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Here are the contents of that script, so you can see what it's doing.
+
===Cross-compilation===
 +
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===
 +
 
 +
Install the right Eclipse Java compiler package for your device (the ecj4.6 package if you're running Android 5 or 6), the Android dex tool, and other packages needed to build an Android apk. Generate any Java files needed, compile and dex them, then package everything up into an apk and sign it.
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/GLContext.d
+
apt install ecj dx aapt apksigner
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/JNIHelper.d
+
 
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/gestureDetector.d
+
aapt package  -M ./AndroidManifest.xml -I $PREFIX/share/java/android-21.jar -J src/ -S res -m
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/perfMonitor.d
+
 
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/shader.d
+
ecj-21 -d ./obj -sourcepath src $(find src -type f -name "*.java")
$LDC/bin/ldc2 -I../../ -c ../../ndk_helper/tapCamera.d
+
 
 +
dx --dex --output=./classes.dex ./obj/
  
$LDC/bin/ldc2 -I../../ -Ijni/ -Jjni/ -c jni/TeapotNativeActivity.d
+
aapt package  -M ./AndroidManifest.xml -S res -A assets -F teapot.apk
$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
+
aapt add teapot.apk classes.dex lib/armeabi-v7a/libTeapotNativeActivity.so
  
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -Wl,-soname,libTeapotNativeActivity.so
+
apksigner debug.ks teapot.apk teapot-signed.apk
-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
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Package this shared library into an apk by using the SDK, as you would normally, and try installing and running it on your device.
+
Finally, move <tt>teapot-signed.apk</tt> into a public directory, from which you can install and run it.
 +
 
 +
==Examples and useful repositories==
 +
* [https://github.com/adamdruppe/d_android D Android from adamdruppe] - You can find android-setup.d which will download the pre-built Android runtimes for you and set up ldc2.conf;
 +
 
 +
* [https://github.com/MrcSnm/D-SDL-Android-Project D SDL Android from MrcSnm|Hipreme] - Provides an Android template project for working with [http://libsdl.org SDL2];
 +
 
 +
* [https://github.com/MrcSnm/D-Lang-on-Android D Lang on Android from MrcSnm|Hipreme] - Additional documentation about setting up a D project for [https://developer.android.com/studio Android Studio].
 +
 
 +
==Changes for Android==
 +
 
 +
Now that you've seen some examples, here's a description of changes to D that have been made for Android.
 +
 
 +
The Android environment doesn't support native Thread-Local Storage (TLS), which is integral to D, since [https://dlang.org/migrate-to-shared.html all static and global variables not explicitly marked shared/__gshared/immutable are thread-local by default in D].  The Android D runtime supports emulated TLS instead, but this currently requires the <tt>ld.bfd</tt> linker - <tt>ld.gold</tt> or <tt>lld</tt> won't do.
  
===Native compilation===
+
If building a shared library and not a D command-line executable, you must also initialize and exit the D runtime by calling <tt>rt_init()</tt> and <tt>rt_term()</tt> before and after all D code is run, [https://github.com/Diewi/android/blob/4fbdbb1344725a593d8df1e008ba371c0e694a11/android_native_app_glue.d#L554 as has been done in the default Android wrapper] ([[Runtime internals|<tt>rt_init</tt>/<tt>rt_term</tt> are automatically inserted and run for a D executable]])Running multiple D shared libraries is currently unsupported on Android, only a single D shared library that statically links against the D runtime will work.
The steps are the same as above, except for a marginally different linker command, which is included in the build script but commented outHowever, 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==
 
==Package an Android app from scratch on your Android device==
  
Install aapt, the Android Asset Packaging Tool, and apksigner, a tool to create a hashed manifest and sign your apps.
+
Install <tt>aapt</tt>, the Android Asset Packaging Tool, and <tt>apksigner</tt>, a tool to create a hashed manifest and sign your apps.
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 223: Line 245:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
I'll demonstrate with the NativeActivity app built above.
+
I'll demonstrate with the <tt>NativeActivity</tt> app built above.
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
cd android/samples/native-activity/
+
cd samples/native-activity
 
aapt package -M AndroidManifest.xml -S res -F NativeActivity-debug-unsigned.apk
 
aapt package -M AndroidManifest.xml -S res -F NativeActivity-debug-unsigned.apk
aapt add NativeActivity-debug-unsigned.apk lib/armeabi-v7a/libnative-activity.so
+
APK_DIR=armeabi-v7a # or `arm64-v8a` for 64-bit ARM
 +
aapt add NativeActivity-debug-unsigned.apk libs/$APK_DIR/libnative-activity.so
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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.
+
This simple app only requires three files, <tt>AndroidManifest.xml</tt>, <tt>resources.arsc</tt>, and <tt>libs/$APK_DIR/libnative-activity.so</tt>, which you can check with the following <tt>aapt</tt> command.
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 237: Line 260:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Now let's generate a hashed manifest, just like a Java jar file, and sign the app.  If you have your own Java Keystore already, just supply it to apksigner.  If not, apksigner will generate a self-signed Keystore file, which we name debug.ks below, which is good enough to sign and install debug apps on your own Android device.
+
Now let's generate a hashed manifest, just like a Java jar file, and sign the app.  If you have your own Java Keystore already, just supply it to <tt>apksigner</tt>.  If not, <tt>apksigner</tt> will generate a self-signed Keystore file, which we name <tt>debug.ks</tt> below, which is good enough to sign and install debug apps on your own Android device.
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 243: Line 266:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
You should see three additional files in the apk, if you list its contents using the command above.  At this point, [[#Install_and_run_the_sample_GUI_app|you can install and run the signed app on your own device]].
+
You should see three additional files in the apk, if you list its contents using the command above.  At this point, [[#Install_and_run_the_sample_GUI_app|you can install and run the signed app on your own device]].  If you modify the app, you'll need to build the manifest and sign it again: make sure you use the <tt>debug.ks</tt> you created before or Android won't allow you to reinstall the same app with a newly generated key, unless you first uninstall the app.
  
 
===Sign your app using a certificate and OpenSSL===
 
===Sign your app using a certificate and OpenSSL===
Line 251: Line 274:
 
For a valid certificate for the final release, [https://developer.android.com/studio/publish/app-signing.html there's plenty of information online on how to generate one].  I'll just show how to create a self-signed certificate for debugging purposes.
 
For a valid certificate for the final release, [https://developer.android.com/studio/publish/app-signing.html there's plenty of information online on how to generate one].  I'll just show how to create a self-signed certificate for debugging purposes.
  
First, install the OpenSSL package in Termux.  Then, this OpenSSL command will generate a self-signed debug certificate, apk.cert, and a 2048-bit RSA private key, key.pem, which isn't encrypted with a password.  It will ask you for some signing info, for which I've shown what's used by the debug certificate in the Android SDK, but it doesn't matter what you enter, as it's ignored:
+
First, install the OpenSSL package in Termux.  Then, this OpenSSL command will generate a self-signed debug certificate, <tt>apk.cert</tt>, and a 2048-bit RSA private key, <tt>key.pem</tt>, which isn't encrypted with a password.  It will ask you for some signing info, for which I've shown what's used by the debug certificate in the Android SDK, but it doesn't matter what you enter, as it's ignored:
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 259: Line 282:
  
 
....................................+++
 
....................................+++
...............................................................................................................................................................+++
 
 
writing new private key to 'key.pem'
 
writing new private key to 'key.pem'
 
-----
 
-----
Line 278: Line 300:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Now that we have a certificate- self-signed in this case, use your actual release certificate if you want to release the app- and private key, we use them to sign the app.  Since the apk is just a zip file, unzip it into a directory and use OpenSSL to generate a new signature file, CERT.RSA, then update the apk with the new signature, and copy the apk to a public user directory from which you can install it:
+
Now that we have a certificate- self-signed in this case, use your actual release certificate if you want to release the app- and private key, we use them to sign the app.  Since the apk is just a zip file, unzip it into a directory and use OpenSSL to generate a new signature file, <tt>CERT.RSA</tt>, then update the apk with the new signature, and copy the apk to a public user directory from which you can install it:
  
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
Line 296: Line 318:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The OpenSSL commands to generate a certificate and sign the apk were taken [http://qistoph.blogspot.com/2012/01/manual-verify-pkcs7-signed-data-with.html from this 2012 blog post], you can follow it further to see what the signature consists of and verify it for yourself.  [https://nelenkov.blogspot.com/2013/04/android-code-signing.html This 2013 blog post was critical for me to understand how apk signing works], I used to run all those commands by hand until the apksigner package was added to the Termux package repo late last year.
+
The OpenSSL commands to generate a certificate and sign the apk were taken [http://qistoph.blogspot.com/2012/01/manual-verify-pkcs7-signed-data-with.html from this 2012 blog post], you can follow it further to see what the signature consists of and verify it for yourself.  [https://nelenkov.blogspot.com/2013/04/android-code-signing.html This 2013 blog post was critical for me to understand how apk signing works], I used to run all those commands by hand until the <tt>apksigner</tt> package was added to the Termux package repo.
 +
 
 +
==Directions for future work==
 +
 
 +
* Revise the way TLS data is initialized on Android. <tt>ld.bfd</tt> is required because it's the only linker putting the <tt>.tdata</tt> and <tt>.tbss</tt> sections adjacent to each other (by default).
 +
 
 +
* Integrate the linux shared library support in druntime's <tt>rt.sections_elf_shared</tt>, so that multiple D shared libraries can be used. Things may likely get complicated because of LDC's custom TLS emulation; using LLVM's default EmuTLS for Android instead might be an option.
 +
 
 +
* Fix [https://github.com/ldc-developers/ldc/issues/2153 the remaining stdlib incompatibilities on 64-bit ARM].
  
[[Category: Android]]
+
[[Category: Android]] [[Category: LDC]]

Latest revision as of 19:16, 13 December 2022

These instructions show you how to build D command-line executables and OpenGL ES GUI apps for Android, either by using the desktop D compilers for Windows, Mac, or Linux available here or a native Android compiler. There are separate steps for cross-compilation, ie building apps on a Windows/Linux PC or Mac and running the app on Android, versus native compilation, both building and running on your Android 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 open-source package manager/repository for Android devices.

Prerequisites

Cross-compilation

  • A command shell on your host PC/Mac, where you'll run the LDC D compiler
    • Either a DOS command prompt or Powershell should work on Windows.
    • Any shell should work on Mac and Linux, typical commands for the bash shell are shown.
  • Version r20 or r21 of the Android NDK and optionally the SDK. Other versions might work but reports of version 22+ say the linker changed and it won't just work anymore.
    • The SDK is necessary to package a GUI app; the NDK is enough if you just want to build a command-line binary.
  • A recent LDC compiler for your host platform
    • It's best to use an official release from GitHub, as it's built against a slightly tweaked LLVM with custom TLS emulation for Android targets. If using LDC from your distro or elsewhere, make sure it was built against our tweaked LLVM, otherwise it will not compile properly for Android.
  • Android, 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.
    • When 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:
  1. Install an ssh server app on your Android device and scp the app over. Alternately, set up an ssh server on your host PC/Mac, and use an ssh/scp client on Android to get the app. This is what I do, by using the OpenSSH package in Termux.
  2. Host the app in a web server and get it by using your Android browser or a downloader app.
  3. Setup the Android Debug Bridge (adb) on your device and use the SDK tools to push your files over.

Native compilation

  • Android Version 10 ("Q"), as LDC compiler support does not exist for Android Version 9 ("Pie") and earlier.
  • Termux for Android, available in the official Play Store, APKMirror, or F-Droid
  • LDC for Termux: apt install ldc With Android version 9 and earlier, the install will fail.

Cross-compilation setup

Once you have LDC and have unzipped the Android NDK, it's time to set up LDC for the desired Android target(s). See Cross-compiling with LDC for the general guide; I present two examples for a quick summary:

  • Targeting 32-bit Android/ARMv7-A on a Win64 host:
    1. Download the prebuilt android-armv7a package from GitHub matching the version of your LDC.
    2. Extract the lib directory into your LDC installation directory and rename it, e.g., to lib-android_armv7a.
    3. Open <LDC install dir>\etc\ldc2.conf in a text editor and append a section like this, adapting lib and NDK paths as needed:
"armv7a-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=C:/LDC/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64/bin/armv7a-linux-androideabi21-clang.cmd",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_armv7a",
    ];
    rpath = "";
};
  • Targeting 64-bit Android/AArch64 on a Linux host:
    1. Download the prebuilt android-aarch64 package from GitHub matching the version of your LDC.
    2. Extract the lib directory into your LDC installation directory and rename it, e.g., to lib-android_aarch64.
    3. Open <LDC install dir>/etc/ldc2.conf in a text editor and append a section like this, adapting lib and NDK paths as needed:
"aarch64-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=/home/me/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_aarch64",
    ];
    rpath = "";
};

The prebuilt Android packages also include the corresponding x86 simulator libraries, so 32/64-bit x86 Android simulator targets can be set up the same way.

Build a command-line executable

Now that we have a D compiler setup for (one or more) Android targets, 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. Install the curl package in Termux if you're natively compiling, apt install curl.

# Load this link in your browser and download the file otherwise 
curl -L -O https://raw.githubusercontent.com/dlang/dmd/master/samples/sieve.d

# Cross-compile & -link to ARMv7-A (on any host)
ldc2 -mtriple=armv7a--linux-androideabi sieve.d

# Cross-compile & -link to AArch64 (on any host)
ldc2 -mtriple=aarch64--linux-android sieve.d

# Compile & link natively in Termux
ldc2 sieve.d

Cross-compilation

Copy this sieve program onto an Android device or emulator and set its permissions with the chmod command. Here's how I do it in Termux, with an ssh server running on the host PC/Mac with IP address 192.168.1.37:

apt install openssh
cd
scp jo@192.168.1.37:sieve .
chmod 700 sieve

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 the android repository or download its source in a zip file, which contains several headers and sample OpenGL apps from the NDK translated to D:

sudo apt-get install git # In Termux, apt install git
git clone https://github.com/Diewi/android.git
cd android

# Alternatively, without git:
curl -L -O https://github.com/Diewi/android/archive/build.zip
unzip build.zip
cd android-build

Then build the Native Activity app, which is written completely in D. D code for an apk must be compiled to a shared library, which the Android runtime will call:

cd samples/native-activity

ldc2 -I../.. jni/main.d ../../android/sensor.d ../../android_native_app_glue.d \
     -shared -of=libs/arm64-v8a/libnative-activity.so \ # or `libs/armeabi-v7a/...` for 32-bit ARM
     -L-soname -Llibnative-activity.so \
     -mtriple=aarch64--linux-android # only for cross-compilation; use `armv7a--linux-androideabi` for 32-bit ARM
     # possibly needed: -L-llog -L-landroid -L-lEGL -L-lGLESv1_CM

Cross-compilation

Ant approach

Finally, package the app as the SDK directs: at this point, it's just like building a regular Android app. I document the older Ant approach, which is deprecated, replace it with the Gradle command from a newer SDK. With Ant on Mac or Linux, set the path to your SDK, then run these commands:

export SDK=/path/to/your/android-sdk
$SDK/tools/android update project -p . -s --target 1
ant debug

Android Studio approach

Enter on your <application name>/app/src/main and create a folder called jniLibs, this folder is extremely important, it is the default folder to put your shared libraries to be imported together with your .apk. If you wish to use other name for it, you will need to change your gradle file. For actually putting your libraries inside that folder, you will actually need to make directories for the target architectures, so, create inside it:

  • armeabi-v7a (For that, it is commonly used ldc2(version)-android-armv7a/lib
  • arm64-v8a (This is our target right now, ldc2(version)-android-aarch64/lib)
  • x86 (It is the lib32 for armv7a -> ldc2(version)-android-armv7a/lib686
  • x86_64 (It is the lib32 for the aarch64 -> ldc2(version)-android-aarch64/lib-x86_64 For reference, check ndk abi guide from official android site: Android ABI Guide

After creating those folders, you can actually move your shared library inside one of them, just click on run and you're good to go.

---

Transfer the resulting bin/NativeActivity-debug.apk to your Android device, again shown here by using scp from the Termux app.

scp jo@192.168.1.37:android/samples/native-activity/bin/NativeActivity-debug.apk /sdcard/Download/

Native compilation

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

This D app has not been ported to 64-bit Android/ARM yet, only 32-bit ARM compilation will work for now:

cd samples/Teapot

ldc2 -I../.. -Ijni -Jjni \
     ../../ndk_helper/GLContext.d \
     ../../ndk_helper/JNIHelper.d \
     ../../ndk_helper/gestureDetector.d \
     ../../ndk_helper/perfMonitor.d \
     ../../ndk_helper/shader.d \
     ../../ndk_helper/tapCamera.d \
     jni/TeapotNativeActivity.d \
     jni/TeapotRenderer.d \
     ../../android/sensor.d \
     ../../android_native_app_glue.d \
     -shared -of=libs/armeabi-v7a/libTeapotNativeActivity.so \
     -L-soname -LlibTeapotNativeActivity.so \
     -mtriple=armv7a--linux-androideabi # only for cross-compilation
     # possibly needed: -L-llog -L-landroid -L-lEGL -L-lGLESv2

Cross-compilation

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

Install the right Eclipse Java compiler package for your device (the ecj4.6 package if you're running Android 5 or 6), the Android dex tool, and other packages needed to build an Android apk. Generate any Java files needed, compile and dex them, then package everything up into an apk and sign it.

apt install ecj dx aapt apksigner

aapt package  -M ./AndroidManifest.xml -I $PREFIX/share/java/android-21.jar -J src/ -S res -m

ecj-21 -d ./obj -sourcepath src $(find src -type f -name "*.java")

dx --dex --output=./classes.dex ./obj/

aapt package  -M ./AndroidManifest.xml -S res -A assets -F teapot.apk

aapt add teapot.apk classes.dex lib/armeabi-v7a/libTeapotNativeActivity.so

apksigner debug.ks teapot.apk teapot-signed.apk

Finally, move teapot-signed.apk into a public directory, from which you can install and run it.

Examples and useful repositories

  • D Android from adamdruppe - You can find android-setup.d which will download the pre-built Android runtimes for you and set up ldc2.conf;

Changes for Android

Now that you've seen some examples, here's a description of changes to D that have been made for Android.

The Android environment doesn't support native Thread-Local Storage (TLS), which is integral to D, since all static and global variables not explicitly marked shared/__gshared/immutable are thread-local by default in D. The Android D runtime supports emulated TLS instead, but this currently requires the ld.bfd linker - ld.gold or lld won't do.

If building a shared library and not a D command-line executable, you must also initialize and exit the D runtime by calling rt_init() and rt_term() before and after all D code is run, as has been done in the default Android wrapper (rt_init/rt_term are automatically inserted and run for a D executable). Running multiple D shared libraries is currently unsupported on Android, only a single D shared library that statically links against the D runtime will work.

Package an Android app from scratch on your Android device

Install aapt, the Android Asset Packaging Tool, and apksigner, a tool to create a hashed manifest and sign your apps.

apt install aapt apksigner

I'll demonstrate with the NativeActivity app built above.

cd samples/native-activity
aapt package -M AndroidManifest.xml -S res -F NativeActivity-debug-unsigned.apk
APK_DIR=armeabi-v7a # or `arm64-v8a` for 64-bit ARM
aapt add NativeActivity-debug-unsigned.apk libs/$APK_DIR/libnative-activity.so

This simple app only requires three files, AndroidManifest.xml, resources.arsc, and libs/$APK_DIR/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. If you have your own Java Keystore already, just supply it to apksigner. If not, apksigner will generate a self-signed Keystore file, which we name debug.ks below, which is good enough to sign and install debug apps on your own Android device.

apksigner debug.ks NativeActivity-debug-unsigned.apk NativeActivity-debug.apk

You should see three additional files in the apk, if you list its contents using the command above. At this point, you can install and run the signed app on your own device. If you modify the app, you'll need to build the manifest and sign it again: make sure you use the debug.ks you created before or Android won't allow you to reinstall the same app with a newly generated key, unless you first uninstall the app.

Sign your app using a certificate and OpenSSL

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 and want to release your app to an app store, you'll have to use OpenSSL to sign the app.

For a valid certificate for the final release, there's plenty of information online on how to generate one. I'll just show how to create a self-signed certificate for debugging purposes.

First, install the OpenSSL package in Termux. Then, this OpenSSL command will generate a self-signed debug certificate, apk.cert, and a 2048-bit RSA private key, key.pem, which isn't encrypted with a password. It will ask you for some signing info, for which I've shown what's used by the debug certificate in the Android SDK, but it doesn't matter what you enter, as it's ignored:

apt install openssl-tool

openssl req -x509 -nodes -newkey rsa:2048 -keyout key.pem -out apk.cert

....................................+++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Android
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Android Debug
Email Address []:

Now that we have a certificate- self-signed in this case, use your actual release certificate if you want to release the app- and private key, we use them to sign the app. Since the apk is just a zip file, unzip it into a directory and use OpenSSL to generate a new signature file, CERT.RSA, then update the apk with the new signature, and copy the apk to a public user directory from which you can install it:

mkdir unpack
cd unpack/
unzip ../NativeActivity-debug.apk

cd META-INF/
openssl smime -sign -md sha1 -binary -noattr -in CERT.SF -out CERT.RSA -outform der -inkey ../../key.pem -signer ../../apk.cert

cd ..
aapt remove ../NativeActivity-debug.apk META-INF/CERT.RSA
aapt add ../NativeActivity-debug.apk META-INF/CERT.RSA

cd ..
cp NativeActivity-debug.apk /sdcard/Download/

The OpenSSL commands to generate a certificate and sign the apk were taken from this 2012 blog post, you can follow it further to see what the signature consists of and verify it for yourself. This 2013 blog post was critical for me to understand how apk signing works, I used to run all those commands by hand until the apksigner package was added to the Termux package repo.

Directions for future work

  • Revise the way TLS data is initialized on Android. ld.bfd is required because it's the only linker putting the .tdata and .tbss sections adjacent to each other (by default).
  • Integrate the linux shared library support in druntime's rt.sections_elf_shared, so that multiple D shared libraries can be used. Things may likely get complicated because of LDC's custom TLS emulation; using LLVM's default EmuTLS for Android instead might be an option.