Managing the Android SDK as a mise dependency
Moving a Tauri Android setup from an Android Studio Mac to a headless Debian VM by pinning the SDK as a mise tool.
Moving Tauri Android development from an Android Studio Mac to a headless Debian VM exposed how much of the setup lived outside the repository. The SDK came from Studio’s GUI, ANDROID_HOME came from dotfiles, and the first cargo build failed on missing system libraries. I replaced all of that with two mise tasks.
Note
Tested with mise 2026.9.11, android-sdk 19.0, and NDK 28.2.13676358 on Debian 13, 2026-09-13.
The Problem
The repository (CorrePlatano, a Tauri v2 + Rust + React run-tracking app) already had host-agnostic Android tooling: a run-android.ts wrapper that reads ANDROID_HOME, auto-discovers the NDK, and prepends platform-tools to PATH. But onboarding still assumed Android Studio. The docs said “Install Android Studio, then use its SDK Manager”, and dotfiles exported ANDROID_HOME per OS, pointing at a directory that didn’t exist on the new VM.
The standard headless alternative is an imperative dance: download Google’s command-line tools zip, unzip it into cmdline-tools/latest, run sdkmanager for each component. It works, but it’s per-machine knowledge that drifts, and nothing pins which NDK or build-tools a machine has.
What Changed
mise can own the command-line tools as a regular tool. The mise registry maps android-sdk to the vfox-android-sdk plugin, which downloads the cmdline-tools zip for the host platform (Linux, macOS, or Windows), rearranges it into the layout sdkmanager expects, and exports ANDROID_HOME/ANDROID_SDK_ROOT pointing at the install:
# mise.toml
[tools]
java = "temurin-17.0.18+8"
android-sdk = "19.0"
That’s now a lockfile-pinned dependency like node or rust. The components only exist inside sdkmanager’s repository metadata, so they live in an idempotent task with the versions pinned as strings:
# mise.toml
[tasks."android:sdk"]
description = "Install the pinned Android SDK components with sdkmanager (idempotent)"
run = """
set -eu
yes | sdkmanager --licenses >/dev/null
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"
"""
[tasks."android:init"]
depends = ["android:sdk"]
android:doctor gained a sdkmanager --version check, and on this VM it reports the whole chain resolved from the mise-managed SDK:
openjdk version "17.0.18" # mise tool
19.0 # sdkmanager, mise tool
Android Debug Bridge version 1.0.41
Installed as /home/fran/.local/share/mise/installs/android-sdk/19.0/platform-tools/adb
tauri-cli 2.11.4
One precedence detail I tested: inside mise run/mise exec, mise’s plugin env overrides an ANDROID_HOME already exported by the shell, and the mise-managed SDK wins. The dotfiles exports still deserved deletion because they mislead any command run outside mise.
The desktop build needed a second task. Tauri on Linux links against the system GUI stack (WebKitGTK, GTK3, libdbus), so a fresh VM fails compiling libdbus-sys with a pkg-config error. Those are OS packages, so they now live in a guarded setup:linux task: Linux-only, apt-only, checks dpkg-query first, and only invokes sudo apt-get install for the missing set. It’s explicit opt-in, never wired into a build or test gate.
The docs also got a two-tier development loop, because most days need none of this: mise run dev:web plus mise run test covers shared React and Rust work. The Android loop only runs when a change touches WebView, plugins, permissions, or secure storage; then the phone connects over wireless debugging instead of a cable:
adb pair <phone-ip>:<pair-port> # once, from Developer options
adb connect <phone-ip>:<port>
mise run test:android # builds, installs, runs the Maestro smoke
Results
Onboarding on a fresh Debian 13 VM is now mise install && mise run android:init, and every quality gate that doesn’t need a device passes: mise run verify exits 0, covering format, lint, typecheck, tests, and builds. The Mac keeps the one job it’s uniquely required for: iOS and macOS desktop builds, whenever those start.
What I’d Do Differently
Three caveats I accepted knowingly. The version strings in the task are the pin, and sdkmanager has no lock concept. Uninstalling the android-sdk tool wipes everything sdkmanager installed into it, recoverable by rerunning the task. And the local emulator path would need an x86_64-linux-android Rust target on this host, since the repo builds ARM64 only; I skipped the emulator entirely because verifying on the phone is faster anyway.
References
- mise: the tool manager this setup relies on
- mise registry: where the
android-sdkalias resolves from - mise-plugins/vfox-android-sdk: the plugin that installs cmdline-tools and exports
ANDROID_HOME - Tauri: the framework the app is built with
- Tauri prerequisites: required Android SDK packages and
NDK >= 28guidance - Android command-line tools: the sdkmanager/avdmanager packages this setup automates
- Android Studio: the GUI-based setup this replaces
- Maestro: the mobile UI test framework behind the smoke test
- CorrePlatano: the changeset described here
This post was written with AI assistance.