Why this is not supposed to work
Google ships its Linux Android SDK build tools for x86-64. On aarch64, sdkmanager installs fine, the platform zips unpack fine, and then the build dies with:
AAPT2 aapt2-9.1.1-14792394-linux Daemon #0: Daemon startup failed
That message appears repeatedly across process*DebugResources and library AAR transforms. It is not a resource bug. The aapt2 binary that Gradle fetched from Maven is an x86-64 ELF.
Trap 1 — the aapt2 barrier
Everything else in the toolchain is Java and therefore architecture-independent: d8, r8, zipalign, apksigner. aapt2 is the only native binary in the critical path, so it is the only one that needs replacing.
Termux ships an aarch64 build. Point Gradle at it in gradle.properties:
android.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2
Verify it is the right architecture before trusting it:
$ aapt2 version
2.20-android-16.0.0_r4
Trap 2 — compileSdk 37 is invisible to sdkmanager
sdkmanager "platforms;android-37" answers “Failed to find package”, which reads like the platform does not exist. It does. Google moved the repository XML to a v4 schema that older cmdline-tools cannot parse, and the package is now named with a minor version: platform-37.2_r01.zip.
curl -sL -o /tmp/platform-37.2.zip \
https://dl.google.com/android/repository/platform-37.2_r01.zip
unzip -q /tmp/platform-37.2.zip -d "$ANDROID_HOME/platforms"
The zip nests one android-37.2/ level. Move its contents up, or AGP will not see the platform. Then keep compileSdk = "37" — AGP 9.x resolves the .2 minor itself.
Do not “fix” a missing SDK by lowering compileSdk. Modern AndroidX artifacts check the AAR metadata and refuse to build: material-ripple 1.12.0-beta01 requires 37, and downgrading produces 14 AAR metadata errors instead of one missing-package error.
Trap 3 — toolchain spec is not JAVA_HOME
Setting JAVA_HOME to JDK 21 does not satisfy a Gradle toolchain request for languageVersion = 17. Gradle looks for an installed 17 and fails with a message that suggests the JDK is missing entirely. On Termux the JDKs live under:
/data/data/com.termux/files/usr/lib/jvm/java-17-openjdk
/data/data/com.termux/files/usr/lib/jvm/java-21-openjdk
org.gradle.java.installations.paths=/data/data/com.termux/files/usr/lib/jvm/java-17-openjdk
Guessing a Linux distribution path (/usr/lib64/jvm/...) is the usual failure: it does not exist in the proot, and Gradle reports the same “no matching toolchain” error.
Trap 4 — the configuration cache replays stale failures
After fixing the JDK path, the identical error comes back. The configuration cache stored the failed task graph and replays it. After any toolchain or environment change:
rm -rf .gradle/configuration-cache
./gradlew assembleDebug --no-daemon --no-configuration-cache
Also disable the daemon in a container or proot: a daemon that outlives the session holds stale environment variables.
What actually worked
- A Kotlin/Compose Android app (a CloudStream fork) configured, compiled and installed on-device with the aapt2 override.
d8/r8/apksignerrun unmodified — they are JVM tools.- APK signing and installation via ADB from the same shell.
What did not
- A full APK plus Rust build of UpgradeAll on this host has not passed. The build guide is proposed upstream as a PR; the claim is deliberately limited to the module that compiled.
- 16 KB page-size targets and NDK builds need a native toolchain (Termux clang works for JNI, the full NDK does not ship aarch64 host binaries).
Checklist
| symptom | cause | fix |
|---|---|---|
| “aapt2 Daemon startup failed” | x86-64 aapt2 from Maven | android.aapt2FromMavenOverride |
| “Failed to find package android-37” | v4 repo XML, minor-version naming | hand-install platform-37.2_r01.zip |
| “No matching toolchain” | JAVA_HOME ≠ toolchain 17 | org.gradle.java.installations.paths |
| Same error after a fix | configuration cache replay | --no-configuration-cache |