A mise backend plugin for Android SDK packages. It wraps sdkmanager and the modern android sdk CLI, installs packages inside the selected android-sdk dependency, and exports their environment and executable paths.
Requires Linux or macOS, a POSIX shell, and configured android-sdk and java tools. Windows is not supported.
Add this to mise.toml:
[plugins]
android-sdkmanager = "https://github.com/chbiel/mise-android-sdkmanager"
[tool_alias.android-sdk]
backend = "vfox:mise-plugins/vfox-android-sdk"
[tools]
android-sdk = "latest"
java = "temurin-21"
"android-sdkmanager:platform-tools" = { version = "latest", depends = ["android-sdk", "java"] }
"android-sdkmanager:build-tools" = { version = "36.0.0", depends = ["android-sdk", "java"] }
"android-sdkmanager:platforms" = { version = "android-36", depends = ["android-sdk", "java"] }Install everything with one command, including on a clean machine:
mise installThe depends declarations make mise install Android command-line tools and Java before resolving and installing SDK packages. No separate dependency-install step is required. The plugin also declares these prerequisites; the examples keep the ordering explicit.
Commands such as adb become available in the active mise environment, without manual _.path entries:
mise exec -- adb versionIf you register the plugin under another name, such as jds-sdkmanager, use that prefix in tool declarations and commands.
| Family | Version example | Behavior |
|---|---|---|
build-tools |
36.0.0 |
Side-by-side versions |
platforms |
android-36 |
Android API platform |
platform-tools |
latest |
One shared revision; includes adb |
emulator |
latest |
One shared revision |
ndk |
28.2.13676358 |
Side-by-side versions |
cmake |
3.22.1 |
Side-by-side versions |
system-images |
android-36 |
Play Store image with a host-derived ABI |
system-images/<flavor>/<abi> |
android-36 |
Explicit image variant |
Use latest for these two families. Mise resolves it to a numeric revision for update tracking, but Android's installer accepts only the package name, not a target revision. Numeric pins and lockfile revisions are advisory. A valid installation with a different revision, newer or older, produces a warning by default.
To fail installation on a revision mismatch, add strict_revision:
"android-sdkmanager:platform-tools" = { version = "latest", strict_revision = true, depends = ["android-sdk", "java"] }This boolean option applies only to platform-tools and emulator. It checks the observed revision; it cannot retrieve unavailable older releases or prevent another project from changing the shared package later. It also applies to latest, because mise passes its resolved number. Older mise releases that serialize booleans as "true"/"false" are supported.
The system-images shorthand selects google_apis_playstore with arm64-v8a on ARM64 hosts and x86_64 otherwise. For explicit variants:
"android-sdkmanager:system-images/google_apis/arm64-v8a" = { version = "android-36", depends = ["android-sdk", "java"] }
"android-sdkmanager:system-images/google_apis_playstore/x86_64" = { version = "android-36", depends = ["android-sdk", "java"] }Flavor and ABI are part of the tool identity, keeping mise's installation records and version caches separate. Flavors start with a lowercase letter and contain lowercase letters, digits, or single, nontrailing underscores. Supported ABIs are arm64-v8a, armeabi-v7a, x86, and x86_64; selecting one does not guarantee the host can run it.
For platforms and images, an API identifier such as android-36 does not pin an immutable package revision. The shared SDK is not a byte-for-byte reproducible environment.
The android-sdk dependency owns the versioned SDK root and command-line tools. This plugin manages packages inside that same root:
<selected android-sdk installation>/
cmdline-tools/<version>/bin/
build-tools/36.0.0/
platforms/android-36/
platform-tools/
The plugin and dependency agree on ANDROID_HOME and ANDROID_SDK_ROOT. Packages follow the dependency's lifecycle: removing its installation can remove its packages. Projects selecting the same dependency installation still share packages, including singleton revisions; this is not project isolation.
The plugin discovers the selected dependency through its injected PATH, not a scan for the highest installed version, a receipt, or inherited Android SDK variables. The physical tools directory must have the standard <sdk-root>/cmdline-tools/<version> layout. Nonstandard layouts fail with a dependency diagnostic.
The plugin prefers a capable android sdk CLI, falling back to sdkmanager from the same installation when necessary. All installers and license acceptance target that SDK root explicitly.
The plugin never copies, provisions, or links command-line tools. Keeping tools and packages together lets tools such as apkanalyzer, which derive their SDK root from their physical location, find installed build-tools.
-
Update this plugin.
-
Remove every
cmdline_toolsoption. The option now fails with a migration error; select the desiredandroid-sdkversion instead. -
Install the selected
android-sdkandjavadependencies. -
Force-install all configured package tools without appending
@version. This preserves their configured options and multiple versions:mise install android-sdk java mise install --force android-sdkmanager:build-tools android-sdkmanager:platforms android-sdkmanager:platform-tools
Include your configured emulator, NDK, CMake, and system-image identities as applicable. For a renamed registration, use commands such as
mise install --force jds-sdkmanager:build-tools. -
Remove or update stale
flutter config --android-sdksettings,local.propertiesSDK paths, and hardcoded environment or PATH overrides that still select${MISE_DATA_DIR}/android-sdk-tools.
The old shared root, packages, licenses, and tooling links remain untouched. Nothing is moved, copied, uninstalled, or deleted automatically. Decide whether to remove that root only after checking other consumers.
Repeat the explicit force-install step after future android-sdk version switches. Existing mise package records do not prove that packages exist in the newly selected SDK. Plain mise install can skip those records; activation does not repair them. Mise may cache activation results and warnings until explicit reinstall.
Activation is read-only. It does not execute Android tools, download packages, accept licenses, write receipts, or wait for the installation lock. It warns about detected missing package files without blocking activation, naming the affected SDK root and an explicit force-install command. Mise caches environment results, so these warnings are not continuous integrity checks.
Retry an affected configured tool explicitly:
mise install --force android-sdkmanager:build-toolsWithout @version, mise preserves that tool's configured requests and options. It recreates the per-tool record, not the SDK. The plugin validates packages in the selected dependency and reuses healthy side-by-side packages without downloading them again. Packages in a previously selected SDK cannot satisfy validation. Singleton packages are reused when their revision matches; otherwise an installer attempt allows upgrades.
Plain mise install can skip a damaged package if mise still records it as installed. Force-install retries missing payloads, but SDK metadata can make the installer consider an incomplete package already installed. In that case, inspect the reported files, source.properties, package.xml, and installed-package listing. The plugin does not automatically uninstall shared packages, delete metadata, or wipe the SDK.
Successful installation requires valid metadata and the expected payload; failed commands are never accepted merely because files exist. An atomic .mise-sdk-package.json receipt records the package, resolved and observed revisions, SDK root, and tooling source. Receipts never select the installation root. Legacy .installed markers remain compatible and are not migrated during activation. Removing a package tool's mise record leaves SDK packages intact; removing the android-sdk dependency can remove them.
Use the same single mise install for clean runners and intact restored installations. Cache the selected android-sdk dependency, including its packages, together with mise's package records. Key caches by Android SDK and Java versions, configured package versions, OS, and architecture. Keep restored paths consistent and exclude .mise-install-lock and its contents.
A cache containing only mise records cannot restore SDK files. If your pipeline needs explicit validation after restoration, optionally force-install its configured Android package tools:
mise install --force android-sdkmanager:build-tools android-sdkmanager:platformsThis is recovery or additional validation, not a required bootstrap step. Invalidate unrecoverable caches through your CI system; the plugin does not delete caches or retry workflows automatically.
Run diagnostics in the same mise environment as the build:
printf '%s\n' "$ANDROID_HOME" "$ANDROID_SDK_ROOT"
ls -l "$ANDROID_HOME/cmdline-tools/"
sdkmanager --sdk_root="$ANDROID_HOME" --list_installed
flutter doctor -vPass --sdk_root explicitly to avoid inspecting a stale inherited SDK. The values must match the selected android-sdk installation. If command-line tools are missing, repair the dependency rather than creating external tools links. For license problems, run sdkmanager --sdk_root="$ANDROID_HOME" --licenses in this same environment after stopping other SDK writers.
All plugin mutations of one SDK root share an atomic directory lock at <sdk-root>/.mise-install-lock, including roots reached through symlinks. Different roots can install concurrently. Android Studio, Gradle, and manually launched installers do not honor this lock; avoid concurrent external writers.
MISE_ANDROID_SDK_LOCK_TIMEOUT controls the wait: an integer from 0 to 86400 seconds, default 600. Zero fails immediately on contention. This limits lock waiting, not installation duration. Activation never acquires the lock.
Normal completion and errors release it, but abrupt termination can leave a lock behind. The plugin never steals a lock based on age or a missing parent process; an installer child might still be writing.
Before removing an abandoned lock, inspect its reported owner record and stop or wait for all SDK writers. Remove owner.<token>/info, then remove the empty owner and lock directories with rmdir. A lock without an owner record may already be empty. Inspect unexpected contents rather than deleting recursively. Never remove an active lock or delete the SDK root to clear one.
| Command | Coverage |
|---|---|
mise run test --unit |
Offline Lua regression and cross-process locking |
mise run test --runtime |
Also exercises actual mise with fake SDK installers |
mise run test --sdk-stress |
Also downloads SDK tools 20.0, Java 21, build-tools 36.0.0, platform android-36, NDK 28.2.13676358, and CMake 3.22.1 for concurrent installs, warm activation, and real apkanalyzer files list on a minimal archive |
mise run test |
Unit/runtime coverage plus real version discovery, platform-tools, and emulator installation |