Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: ['**']
branches: [main]
pull_request:

concurrency:
Expand All @@ -15,6 +15,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Test action scripts
run: |
python -m pip install PyYAML==6.0.2
python -m unittest discover -s tests -v
- name: actionlint
run: |
curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash -s -- latest /usr/local/bin
Expand Down Expand Up @@ -127,6 +134,56 @@ jobs:
[ -x "/tmp/my-android-sdk/platform-tools/adb" ]
[ "$SDK_PATH" = "/tmp/my-android-sdk" ]

selftest-windows:
name: test-windows-x64
# Only repository branches may execute code on our persistent runner.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, windows, x64]
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- name: Install SDK at a path containing spaces
id: cli
uses: ./
with:
sdk-path: ${{ runner.temp }}/android sdk
packages: |
platforms/android-34
build-tools/34.0.0
platform-tools
- name: Verify native Windows paths and tools
shell: pwsh
env:
SDK_PATH: ${{ steps.cli.outputs.sdk-path }}
CLI_VERSION: ${{ steps.cli.outputs.cli-version }}
run: |
$ErrorActionPreference = 'Stop'
if (-not $env:CLI_VERSION) { throw 'Missing CLI version' }
if ($env:ANDROID_HOME -ne $env:SDK_PATH) { throw 'ANDROID_HOME mismatch' }
if ($env:ANDROID_SDK_ROOT -ne $env:SDK_PATH) { throw 'ANDROID_SDK_ROOT mismatch' }
foreach ($path in @('platforms/android-34/android.jar', 'build-tools/34.0.0/aapt2.exe', 'platform-tools/adb.exe')) {
if (-not (Test-Path (Join-Path $env:SDK_PATH $path))) { throw "Missing $path" }
}
android --no-metrics --version
if ($LASTEXITCODE -ne 0) { throw 'android failed' }
adb --version
if ($LASTEXITCODE -ne 0) { throw 'adb failed' }
- name: Verify CLI-only setup without caching
uses: ./
with:
cache: 'false'
- name: Verify default SDK path
shell: pwsh
run: |
$expected = [IO.Path]::GetFullPath((Join-Path $env:LOCALAPPDATA 'Android/Sdk'))
if ([IO.Path]::GetFullPath($env:ANDROID_HOME) -ne $expected) { throw 'Unexpected default SDK path' }
android --no-metrics --version
if ($LASTEXITCODE -ne 0) { throw 'android failed' }

# Gate job. Name MUST be "CI" to match the org-level required_status_checks
# ruleset that protects the default branch.
CI:
Expand All @@ -138,6 +195,7 @@ jobs:
- selftest-nopackages
- selftest-nocache
- selftest-custom-sdk-path
- selftest-windows
runs-on: ubuntu-latest
steps:
- name: Check results
Expand Down
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/premex-ab/setup-android-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/premex-ab/setup-android-cli/actions/workflows/ci.yml)

A GitHub Action that installs Google's agent-first [`android` CLI](https://developer.android.com/tools/agents/android-cli), sets up the Android SDK, and caches between runs. Drop-in replacement for [`setup-android`](https://github.com/android-actions/setup-android) with a simpler, faster setup.
A GitHub Action that installs Google's agent-first [`android` CLI](https://developer.android.com/tools/agents/android-cli), sets up the Android SDK, and caches between runs. An alternative to [`setup-android`](https://github.com/android-actions/setup-android) with explicit SDK packages and caching.

## Why switch?

Expand Down Expand Up @@ -98,7 +98,7 @@ The action exports the following environment variables for subsequent steps:

## What it does

1. Downloads the `android` CLI launcher (~5 MB) into the runner's tool cache.
1. Downloads the current `android` CLI launcher (`android.exe` on Windows) into the runner's tool cache on every run, including persistent self-hosted runners.
2. Unpacks embedded resources on first run into `~/.android/bin/` (~78 MB, cached).
3. Exports `ANDROID_HOME` and `ANDROID_SDK_ROOT`, and adds `platform-tools/`, `emulator/`, and `cmdline-tools/latest/bin/` to `PATH`.
4. Runs `android sdk install <packages>` if packages are specified.
Expand All @@ -112,15 +112,42 @@ The action exports the following environment variables for subsequent steps:
| `ubuntu-latest` / `ubuntu-22.04` | Supported |
| `macos-latest` / `macos-14` (Apple Silicon) | Supported |
| `macos-13` (Intel, Rosetta) | Best-effort (warning emitted) |
| `windows-*` | Not yet supported (the CLI itself has limited Windows support) |
| Windows x64 | Supported with Git Bash on PATH; SDK/build tools only, not `android emulator` |
| Self-hosted Linux x86_64 / macOS arm64 | Supported |

## Caching

By default the action caches `~/.android/bin` (CLI resources, ~78 MB) and the full SDK directory between runs. The cache key includes OS, architecture, and the hash of `**/libs.versions.toml`, `**/build.gradle*`, and `**/settings.gradle*` so it busts when your project's SDK requirements change.
By default the action caches `~/.android/bin` (CLI resources, ~78 MB) and the full SDK directory between runs. The cache key includes OS, architecture, and the hash of `**/libs.versions.toml`, `**/build.gradle*`, and `**/settings.gradle*` plus a hash of the requested packages, resolved SDK path, and download URL. Changing workflow inputs therefore invalidates the SDK cache too. `ANDROID_USER_HOME`, when set, determines where CLI resources are cached.

Gradle caching is **not** handled by this action. Use [`gradle/actions/setup-gradle@v4`](https://github.com/gradle/actions) for that.

## Windows self-hosted runners

The action uses Git Bash, including `curl` and `cygpath`, supplied by Git for Windows.
Install Git for Windows and ensure the runner service can find `bash` on PATH.
Java must be available for Gradle builds. SDK setup supports Windows x64;
Google's `android emulator` command currently does not support Windows.

```yaml
jobs:
build:
runs-on: [self-hosted, windows, x64]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- uses: premex-ab/setup-android-cli@v1
with:
packages: platforms/android-36 build-tools/36.0.0 platform-tools
- run: ./gradlew.bat assembleDebug
```

The default SDK path is `%LOCALAPPDATA%/Android/Sdk`; custom paths containing
spaces are supported. Paths exported through `ANDROID_HOME`, `ANDROID_SDK_ROOT`,
and the action outputs work in subsequent PowerShell steps as well as Git Bash.

## Migrating from `setup-android`

```diff
Expand All @@ -134,7 +161,12 @@ Gradle caching is **not** handled by this action. Use [`gradle/actions/setup-gra
```

Key differences:
- No `cmdline-tools-version` input (always current release).
- Specify every required platform and build-tools package explicitly. The default
installs only the CLI and does not adopt the runner's preinstalled SDK path.
- No `cmdline-tools-version` input (always current release). A custom
`install-url-base` can select a versioned mirror; the launcher is refreshed each run.
- Direct `sdkmanager`/`avdmanager` calls are not provided by the CLI launcher.
Migrate them separately or explicitly install the required command-line tools.
- No `accept-android-sdk-licenses` input (auto-accepted).
- Package names use slashes instead of semicolons: `platforms/android-34` not `"platforms;android-34"`.
- `tools` package doesn't exist in the new CLI; drop it.
Expand Down
97 changes: 68 additions & 29 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: 'Setup Android CLI'
description: 'Install and cache Google''s agent-first android CLI plus SDK packages. Drop-in setup-android replacement.'
description: 'Install and cache Google''s Android CLI and explicitly selected SDK packages.'
author: 'Premex'

branding:
Expand All @@ -18,7 +18,8 @@ inputs:
sdk-path:
description: >-
Override where the Android SDK is installed. Defaults to
`~/Library/Android/sdk` on macOS and `~/Android/Sdk` on Linux. The
`~/Library/Android/sdk` on macOS, `~/Android/Sdk` on Linux, and
`%LOCALAPPDATA%/Android/Sdk` on Windows. The
chosen path is exported as `ANDROID_HOME` and `ANDROID_SDK_ROOT` for
subsequent steps.
required: false
Expand All @@ -33,7 +34,7 @@ inputs:
cache-key:
description: >-
Extra input appended to the cache key. Change to bust the cache. The
default key already includes OS, arch, and the hash of
default key already includes OS, arch, SDK path, packages, download URL, and the hash of
`**/libs.versions.toml` / `**/build.gradle*`.
required: false
default: ''
Expand All @@ -46,7 +47,7 @@ inputs:
install-url-base:
description: >-
Override the base URL for downloading the CLI binary. The action
appends `/<platform>/android` (for example `/linux_x86_64/android`).
appends `/<platform>/android` (`android.exe` on Windows).
Useful for internal mirrors or air-gapped environments. Defaults to
Google's redirector.
required: false
Expand All @@ -73,6 +74,8 @@ runs:
shell: bash
env:
INPUT_SDK_PATH: ${{ inputs.sdk-path }}
PACKAGES: ${{ inputs.packages }}
INSTALL_URL_BASE: ${{ inputs.install-url-base }}
run: |
set -euo pipefail

Expand All @@ -85,8 +88,9 @@ runs:
echo "::warning::Intel Mac runner detected. Google only ships darwin_arm64; using that binary under Rosetta. Prefer an Apple Silicon runner for faster builds."
slug=darwin_arm64
;;
MINGW*/x86_64|MSYS*/x86_64) slug=windows_x86_64 ;;
*)
echo "::error::Unsupported platform: $os/$arch. Supported: Linux x86_64, macOS arm64 (darwin_arm64)."
echo "::error::Unsupported platform: $os/$arch. Supported: Linux x86_64, macOS arm64, Windows x86_64."
exit 1
;;
esac
Expand All @@ -95,18 +99,41 @@ runs:
sdk_path="${INPUT_SDK_PATH}"
elif [[ "$os" == "Darwin" ]]; then
sdk_path="$HOME/Library/Android/sdk"
elif [[ "$RUNNER_OS" == "Windows" ]]; then
sdk_path="$LOCALAPPDATA/Android/Sdk"
else
sdk_path="$HOME/Android/Sdk"
fi

tool_dir="$RUNNER_TOOL_CACHE/android-cli/latest/$slug"

binary=android
resources="${ANDROID_USER_HOME:-$HOME/.android}/bin"
if [[ "$RUNNER_OS" == "Windows" ]]; then
binary=android.exe
sdk_path=$(cygpath -am "$sdk_path")
tool_dir=$(cygpath -am "$tool_dir")
resources=$(cygpath -am "${ANDROID_USER_HOME:-$USERPROFILE/.android}/bin")
else
mkdir -p "$sdk_path"
sdk_path=$(cd "$sdk_path" && pwd -P)
fi
mkdir -p "$tool_dir" "$sdk_path"

# Changing only workflow inputs must also invalidate the immutable SDK cache.
if command -v sha256sum >/dev/null 2>&1; then
config_hash=$(printf '%s\0' "$PACKAGES" "$sdk_path" "$INSTALL_URL_BASE" | sha256sum | cut -d ' ' -f 1)
else
config_hash=$(printf '%s\0' "$PACKAGES" "$sdk_path" "$INSTALL_URL_BASE" | shasum -a 256 | cut -d ' ' -f 1)
fi

{
echo "slug=$slug"
echo "sdk-path=$sdk_path"
echo "tool-dir=$tool_dir"
echo "binary=$binary"
echo "resources=$resources"
echo "config-hash=$config_hash"
} >> "$GITHUB_OUTPUT"

- name: Cache Android CLI and SDK
Expand All @@ -115,39 +142,46 @@ runs:
uses: actions/cache@v6
with:
path: |
~/.android/bin
${{ steps.platform.outputs.resources }}
${{ steps.platform.outputs.sdk-path }}
key: setup-android-cli-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.platform.outputs.slug }}-${{ hashFiles('**/libs.versions.toml', '**/build.gradle*', '**/settings.gradle*') }}${{ inputs.cache-key && format('-{0}', inputs.cache-key) || '' }}
key: setup-android-cli-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.platform.outputs.slug }}-${{ steps.platform.outputs.config-hash }}-${{ hashFiles('**/libs.versions.toml', '**/build.gradle*', '**/settings.gradle*') }}${{ inputs.cache-key && format('-{0}', inputs.cache-key) || '' }}
restore-keys: |
setup-android-cli-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.platform.outputs.slug }}-
setup-android-cli-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.platform.outputs.slug }}-${{ steps.platform.outputs.config-hash }}-

- name: Install CLI launcher
shell: bash
env:
SLUG: ${{ steps.platform.outputs.slug }}
TOOL_DIR: ${{ steps.platform.outputs.tool-dir }}
BINARY: ${{ steps.platform.outputs.binary }}
NO_METRICS: ${{ inputs.no-metrics }}
INSTALL_URL_BASE: ${{ inputs.install-url-base }}
run: |
set -euo pipefail

if [[ -x "$TOOL_DIR/android" ]]; then
echo "android CLI launcher already present in $TOOL_DIR (from runner tool cache)."
else
echo "::group::Downloading android CLI ($SLUG)"
url="${INSTALL_URL_BASE%/}/${SLUG}/android"
echo "Source: $url"
curl -fsSL --retry 3 --retry-delay 2 "$url" -o "$TOOL_DIR/android"
chmod +x "$TOOL_DIR/android"
echo "::endgroup::"
fi
# Refresh latest on persistent runners too; never reuse an unversioned launcher.
echo "::group::Downloading android CLI ($SLUG)"
url="${INSTALL_URL_BASE%/}/${SLUG}/${BINARY}"
temp_launcher=$(mktemp "$TOOL_DIR/android-download.XXXXXX")
trap 'rm -f "$temp_launcher"' EXIT
curl -fsSL --retry 3 --retry-delay 2 "$url" -o "$temp_launcher"
chmod +x "$temp_launcher"
mv -f "$temp_launcher" "$TOOL_DIR/$BINARY"
echo "::endgroup::"

echo "$TOOL_DIR" >> "$GITHUB_PATH"
export PATH="$TOOL_DIR:$PATH"
shell_tool_dir="$TOOL_DIR"
if [[ "$RUNNER_OS" == "Windows" ]]; then
shell_tool_dir=$(cygpath -u "$TOOL_DIR")
fi
export PATH="$shell_tool_dir:$PATH"

# First run unpacks the embedded resources into ~/.android/bin. If
# that directory was cache-restored, this is effectively a no-op.
echo "::group::Bootstrapping CLI (one-time unpack if needed)"
ANDROID_CLI_FRESH_INSTALL=1 android --version
cli_args=(--version)
if [[ "$NO_METRICS" == "true" ]]; then
cli_args=(--no-metrics "${cli_args[@]}")
fi
echo "::group::Bootstrapping CLI"
ANDROID_CLI_FRESH_INSTALL=1 android "${cli_args[@]}"
echo "::endgroup::"

- name: Configure ANDROID_HOME and ANDROID_SDK_ROOT
Expand Down Expand Up @@ -177,14 +211,13 @@ runs:
run: |
set -euo pipefail

metrics_flag=""
read -r -a package_args <<< "$(printf '%s' "$PACKAGES" | tr '\r\n' ' ')"
cli_args=(--sdk="$SDK_PATH" sdk install "${package_args[@]}")
if [[ "$NO_METRICS" == "true" ]]; then
metrics_flag="--no-metrics"
cli_args=(--no-metrics "${cli_args[@]}")
fi

echo "::group::Installing SDK packages"
# shellcheck disable=SC2086
android $metrics_flag --sdk="$SDK_PATH" sdk install $PACKAGES
android "${cli_args[@]}"
echo "::endgroup::"

- name: Register Android/Gradle problem matchers
Expand All @@ -201,10 +234,16 @@ runs:
SLUG: ${{ steps.platform.outputs.slug }}
PACKAGES: ${{ inputs.packages }}
CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
NO_METRICS: ${{ inputs.no-metrics }}
run: |
set -euo pipefail

cli_version=$(android --version 2>/dev/null || echo "unknown")
cli_args=(--version)
if [[ "$NO_METRICS" == "true" ]]; then
cli_args=(--no-metrics "${cli_args[@]}")
fi
cli_version=$(android "${cli_args[@]}")
cli_version=$(printf '%s' "$cli_version" | tr -d '\r')

{
echo "sdk-path=$SDK_PATH"
Expand Down
Loading
Loading