android-arm64-devkit-skill
Thin wrapper over the scripts in this repo. One skill, two branches: pick your
host and your transport. Every machine-specific value lives in
config.sh (from config.example.sh) — never in this file.
Branch 1 — Host (pick yours)
| Host | What changes |
|---|---|
| ARM64 Linux (DGX Spark, Raspberry Pi, ARM server/cloud) | Google's aapt2 / aidl / zipalign are x86-64 only, so Gradle dies with Exec format error → MUST override with the Debian aarch64 build (see "Build branch") |
| x86-64 Linux | Google's toolchain runs natively → skip the aapt2 override; the adb scripts still apply |
| Windows / macOS | These bash scripts do not run there → use the Android Studio workflow instead (see "Cross-references") |
Branch 2 — Transport (pick yours)
| Situation | Transport |
|---|---|
| Device attached to the machine running the scripts | USB — leave ADB_HOST empty, set DEVICE_SERIAL |
| Device on a VPN (e.g. Tailscale) | TCP — set ADB_HOST to the MagicDNS name; the port is discovered |
| Device attached to another host you can ssh into | remote adb — set ADB_REMOTE_HOST + ADB_REMOTE_BIN |
Config — the only place personal values live
cp {baseDir}/config.example.sh {baseDir}/config.sh
$EDITOR {baseDir}/config.sh
config.sh is gitignored and is never committed. Host names, IPs, serials,
package ids and personal paths go here — not in this skill, not in any tracked
file. Gradle-side values (sdk.dir, android.aapt2FromMavenOverride) belong to
the project's local.properties / gradle.properties.
Create an app (scaffold)
{baseDir}/scripts/new-project.sh ~/apps/MyApp com.example.myapp "My App"
cd ~/apps/MyApp && ./gradlew clean assembleDebug
Creates a minimal Gradle + Kotlin project (one Activity, adaptive icon,
minSdk 26) that already carries the aarch64 aapt2 override. It refuses to
overwrite an existing path and validates the package id.
Build branch (ARM64 host)
sudo apt-get install -y android-sdk-build-tools android-sdk-platform-tools android-libaapt
{baseDir}/scripts/setup-aapt2-arm64.sh
{baseDir}/scripts/setup-aapt2-arm64.sh --write-gradle-property path/to/project
cd path/to/project && ./gradlew clean assembleDebug
Deploy + verify branch (any host)
Prepare the transport once — these values are personal, so they go in config.sh
(never committed):
cp {baseDir}/config.example.sh {baseDir}/config.sh
# USB: ADB_HOST="" DEVICE_SERIAL=<serial from `adb devices -l`>
# VPN (TCP): ADB_HOST=<magicdns> ADB_TCP_PORT="" (the port is discovered)
# remote adb: ADB_REMOTE_HOST=<ssh alias> ADB_REMOTE_BIN=<adb path on that host>
Then connect, install, verify, tear down:
{baseDir}/scripts/adb-connect.sh # discovers the port; writes .adb-device
{baseDir}/scripts/adb-install-verify.sh app/build/outputs/apk/debug/app-debug.apk
{baseDir}/scripts/adb-disconnect.sh # add --restart-server to unwedge adb
adb-connect.sh never guesses the port: it reads it from an existing wireless
transport or over USB, and fails loudly if wireless debugging is not exposed.
adb-install-verify.sh prints the install result, the installed version/time,
launches the activity, checks the focused window and the process pid, greps
logcat for crashes, and captures a screenshot (warns below ~30 KB).
ARM64 host gotchas (any aarch64 distro)
Distro/toolchain facts, not host-specific values — values still live in config.sh
or the project.
- Google's
adbis x86-64 too.platform-tools/adbfrom the SDK will not run on aarch64 either; use the distro'sadb(the setup step installsandroid-sdk-platform-tools, which provides one). Confirm withfile "$(command -v adb)". - Non-interactive shells skip the rc guard. Ubuntu's
~/.bashrcstarts withcase $- in *i*) ;; *) return;; esac; anything after it — including Android env exports — is skipped byssh host 'cmd'. Put theANDROID_HOME/JAVA_HOME/GRADLE_HOMEexports before that guard, then verify withssh host 'echo "$ANDROID_HOME"'. - The distro
aapt2is older than AGP's. It parsesandroid.jarfor older platforms only; if a very newcompileSdkfails with a resource-table error, lowercompileSdk/targetSdkto a supported platform and re-test.
Operating Rules (for agents)
- MUST invoke the repo scripts by their
{baseDir}path; never rewrite their logic inline. - MUST run
./gradlew clean assembleDebugand requireN actionable tasks: M executedwith M > 0. A build with every taskUP-TO-DATEmeansaapt2never ran — it proves nothing. - MUST create
config.shfromconfig.example.shbefore the adb scripts, and MUST NOT commit it or hardcode its values. - MUST NOT hardcode the wireless adb port (e.g.
5555). OEMs randomize it on every wireless-debugging restart; letadb-connect.shdiscover it. - MUST treat
adb-install-verify.shoutput as the acceptance test: install + launch + focus + pid + logcat + screenshot. A screenshot < 30 KB is a cached black framebuffer (lockscreen), not success. - MUST NOT claim success from
BUILD SUCCESSFULorinstall Successalone. - When a binary capture crosses a Windows host, redirect inside
cmd(cmd /c "adb ... > shot.png"); PowerShell's>writes UTF-16 and corrupts binaries. - On a remote adb host, set
ADB_REMOTE_HOST/ADB_REMOTE_BINinconfig.sh; do not reimplement the ssh quoting —{baseDir}/scripts/lib/adb.shhandles it. - The aapt2 override is marked experimental by AGP: re-test after any AGP major upgrade.
Key facts
- aapt2 override:
android.aapt2FromMavenOverride=/usr/lib/android-sdk/build-tools/debian/aapt2 d8/apksignerare JVM wrappers → architecture-independent.- Self-test suite:
{baseDir}/tests/run-tests.sh
Troubleshooting
See {baseDir}/docs/troubleshooting.md — every trap was hit for real: Exec format error, UP-TO-DATE false success, dynamic wireless port, unauthorized
key mismatch, device offline, VPN deep sleep, PowerShell UTF-16 corruption,
stale/empty mounts, black screenshot with focus held.
Cross-references
- Windows / macOS host → the Android Studio workflow skill.
- DGX-Spark-specific operational notes (canonical layout, shell-rc env block,
dynamic-port workflow) → the host manual under
.agent/.
Scope
Debug APKs only — not for Play Store release signing.