whoami
Lucas Jenß
cat /etc/motd
The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- drwxr-xr-x
- ▶ Mac OS X
- ▶ Unix
Cross-Compile Android NDK Libraries for ARM64 and x86_64
Android applications increasingly need native code for performance-sensitive work such as image processing, audio, encryption, machine learning, and hardware integration. When that code is written in C or C++, the Android NDK can produce libraries for several CPU architectures, but each ABI needs its own compiler target and build output.
For current Android releases, the two important 64-bit targets are arm64-v8a for ARM64 devices and x86_64 for Intel and AMD-based devices or emulators. A reliable build keeps these outputs separate, uses the Android NDK toolchain consistently, and packages each library under the directory expected by Gradle.
| Android ABI | CMake architecture value | Common use | Package directory |
|---|---|---|---|
arm64-v8a |
aarch64 |
Modern Android phones and tablets | jniLibs/arm64-v8a |
x86_64 |
x86_64 |
Desktop emulators, some ChromeOS devices | jniLibs/x86_64 |
armeabi-v7a |
arm |
Older 32-bit ARM devices | jniLibs/armeabi-v7a |
x86 |
x86 |
Older Intel emulators and devices | jniLibs/x86 |
Choose The ABI And NDK Toolchain
The Android NDK uses Clang rather than a traditional GCC-based cross-compiler. CMake selects the correct compiler, linker, sysroot, and Android platform libraries through android.toolchain.cmake. The ABI is supplied with ANDROID_ABI, while ANDROID_PLATFORM controls the minimum Android API level.
A practical baseline for a modern project is an API level such as 21 or higher for arm64-v8a, with the precise value determined by application requirements. x86_64 also supports current API levels, although emulator images may behave differently from physical hardware. Check the NDK version used by the Gradle project and keep local development machines, CI runners, and release builders aligned.
For example, a Linux or macOS build can configure an ARM64 directory with:
cmake -S . -B build/arm64-v8a \
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=Release
The corresponding x86_64 configuration is:
cmake -S . -B build/x86_64 \
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
-DANDROID_ABI=x86_64 \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=Release
Do not reuse the same CMake build directory for both architectures. CMake caches compiler paths and target properties, so switching ANDROID_ABI in place can leave a misleading mixture of object files and configuration values.
Configure CMake For Reproducible Outputs
Your CMakeLists.txt should describe the native target without hard-coding a host compiler. A shared library might be declared as follows:
cmake_minimum_required(VERSION 3.22)
project(native_processor LANGUAGES C CXX)
add_library(native_processor SHARED
src/native_processor.cpp
src/codec.cpp
)
target_compile_features(native_processor PRIVATE cxx_std_17)
target_link_libraries(native_processor
android
log
)
CMake will produce libnative_processor.so for each selected ABI. If Java or Kotlin calls the library through JNI, export the expected JNI symbols or register native methods explicitly. Avoid assuming that an ARM64 pointer has the same size or alignment as values used during x86_64 testing. Use fixed-width types such as std::uint32_t, and inspect serialisation code carefully.
For a practical walkthrough of the Android-specific CMake setup, CMake shared libraries provides useful background on generating NDK shared objects without relying on ndk-build. That approach fits well with Gradle projects and makes architecture-specific build directories straightforward to automate.
Build each target independently:
cmake --build build/arm64-v8a --parallel
cmake --build build/x86_64 --parallel
If the project has third-party dependencies, confirm that they are also available for both ABIs. A native library compiled only for ARM64 cannot be linked into an x86_64 application package, even if its C++ API is identical.
Package Native Libraries In The APK
The Android Gradle Plugin recognises native libraries when they are placed in ABI-specific jniLibs directories. A common arrangement is:
app/src/main/jniLibs/
├── arm64-v8a/
│ └── libnative_processor.so
└── x86_64/
└── libnative_processor.so
The files can be copied after CMake finishes, or CMake can write directly into an intermediate packaging directory. For larger projects, prefer Gradle external native builds so configuration and packaging are tied to one build process. A manually copied .so file is easy to forget when someone changes the native source late on a Friday afternoon in Melbourne.
Gradle may create APK splits or an Android App Bundle containing only the relevant ABI for each device. Test the generated artefact rather than assuming that a successful native compilation means packaging is correct. Inspect an APK with unzip -l and confirm that the expected path contains lib/arm64-v8a/ or lib/x86_64/.
On a physical Pixel or Samsung handset commonly used in Sydney or Brisbane testing, ARM64 is the normal path. The Android Studio emulator running on a developer workstation may use x86_64, depending on the selected system image and host machine. Keeping both paths in the package prevents a “works on my phone” result from hiding an emulator failure.
Test ABI-Specific Behaviour And Dependencies
A cross-compiled library can fail at several layers: compilation, linking, dynamic loading, JNI registration, or runtime behaviour. Start by checking the file type:
file app/src/main/jniLibs/arm64-v8a/libnative_processor.so
file app/src/main/jniLibs/x86_64/libnative_processor.so
Use Android tools to inspect dependencies and symbols. readelf -h shows the ELF machine type, while adb logcat often reports missing shared objects, unresolved symbols, or an incorrect JNI name. If a library loads on ARM64 but fails on x86_64, investigate assumptions about assembly instructions, compiler intrinsics, alignment, endianness, and prebuilt vendor binaries.
Do not link an ARM-only prebuilt dependency into the x86_64 target. Maintain an ABI matrix for every native dependency, including OpenSSL, FFmpeg, SQLite extensions, computer vision packages, and proprietary SDKs. If a vendor supplies only ARM64 binaries, the x86_64 build needs a supported alternative or the emulator must run an ARM-compatible image where appropriate.
Australian testing also benefits from realistic release conditions. A user in Perth may download over a slower connection than a Sydney office, and an Android App Bundle can reduce unnecessary native payloads by delivering the matching ABI. Test installation and upgrades on both Wi-Fi and mobile data, then verify that Play Console device filtering has not excluded an intended handset model.
Automate Clean Multi-ABI Builds
A small build script avoids accidental compiler reuse and makes local and CI results comparable:
#!/usr/bin/env bash
set -euo pipefail
for abi in arm64-v8a x86_64; do
build_dir="build/${abi}"
cmake -S . -B "$build_dir" \
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
-DANDROID_ABI="$abi" \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=Release
cmake --build "$build_dir" --parallel
done
Pin the NDK and CMake versions in the project rather than relying on whichever SDK installation happens to be active. Save compiler warnings, archive the generated libraries, and run smoke tests against both emulator and physical-device targets. CI workers should start from clean directories so an old ARM64 object cannot contaminate an x86_64 package.
Native build pipelines often sit beside broader infrastructure automation. If your release environment also manages network controls or virtualised lab systems, NSX-T policy automation is a useful example of treating operational configuration as repeatable code rather than manual console work.
Useful release checks include:
- Build
arm64-v8aandx86_64from separate directories. - Inspect every shared object with
fileorreadelf. - Install the APK on a physical ARM64 device and an x86_64 emulator.
- Verify all transitive native dependencies exist for both ABIs.
- Test the final App Bundle or APK, not just the CMake build folders.
Make Cross-Compilation Part Of Release Discipline
Once both architectures build cleanly, keep the process boring: fixed tool versions, isolated output directories, explicit ABI packaging, and repeatable tests. That discipline matters when a project moves between a developer laptop in Adelaide, a CI runner in Melbourne, and a release window scheduled around Australian business hours.
Add the ABI matrix to code review and document the minimum Android API level, NDK revision, CMake version, and expected output paths. With those details recorded, future changes to native dependencies become easier to diagnose, and an x86_64 emulator failure is less likely to be dismissed as an environment quirk.
Run the two-ABI script as part of every release candidate, inspect the packaged libraries, and retain the build logs with the artefact. That simple workflow turns ARM64 and x86_64 support from a last-minute packaging task into a dependable part of Android development.
cat ~/interests.json
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
Solving SVG import issues in Adobe Illustrator CS6 and CC
When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
Validating IP-Addresses in PHP
Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt