ituoke/build/android/Taskfile.yml

465 lines
18 KiB
YAML

version: '3'
includes:
common: ../Taskfile.yml
vars:
APP_ID: '{{.APP_ID | default "com.wails.app"}}'
MIN_SDK: '21'
TARGET_SDK: '35'
# The emulator runs the host architecture; physical devices are arm64
HOST_ARCH:
sh: '[ "$(uname -m)" = "x86_64" ] && echo "amd64" || echo "arm64"'
# System-image ABI for the host, used in the "create an AVD" hint below.
ANDROID_ABI:
sh: '[ "$(uname -m)" = "arm64" ] && echo "arm64-v8a" || echo "x86_64"'
# SDK location: $ANDROID_HOME / $ANDROID_SDK_ROOT, else the per-OS default
# (macOS: ~/Library/Android/sdk, Linux/other: ~/Android/Sdk)
SDK_ROOT:
sh: 'echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}"'
ADB:
sh: 'command -v adb || echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}/platform-tools/adb"'
EMULATOR:
sh: 'command -v emulator || echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}/emulator/emulator"'
# avdmanager lives under cmdline-tools/<version>/bin; used to auto-create an
# AVD when none exists (mirrors the iOS `ensure-simulator` auto-create flow).
AVDMANAGER:
sh: 'command -v avdmanager || ls "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}"/cmdline-tools/*/bin/avdmanager 2>/dev/null | sort -V | tail -1 || true'
tasks:
install:deps:
summary: Check and install Android development dependencies
cmds:
- go run build/android/scripts/deps/install_deps.go
env:
TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}'
prompt: This will check and install Android development dependencies. Continue?
build:
summary: Creates a debug build of the application for Android
deps:
- task: common:go:mod:tidy
- task: generate:android:overlay
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
cmds:
- echo "Building Android app {{.APP_NAME}}..."
- task: compile:go:shared
vars:
ARCH: '{{.ARCH | default .HOST_ARCH}}'
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}'
env:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
compile:go:shared:
summary: Compile Go code to shared library (.so)
cmds:
- |
# Locate the NDK: $ANDROID_NDK_HOME, or the newest installed NDK
NDK_ROOT="$ANDROID_NDK_HOME"
if [ -z "$NDK_ROOT" ]; then
SDK_ROOT="{{.SDK_ROOT}}"
NDK_ROOT=$(ls -d "$SDK_ROOT"/ndk/* 2>/dev/null | sort -V | tail -1)
fi
if [ -z "$NDK_ROOT" ] || [ ! -d "$NDK_ROOT" ]; then
echo "Error: Android NDK not found"
echo "Install one with: sdkmanager 'ndk;26.3.11579264' (or set ANDROID_NDK_HOME)"
exit 1
fi
# Determine toolchain based on host OS
case "$(uname -s)" in
Darwin) HOST_TAG="darwin-x86_64" ;;
Linux) HOST_TAG="linux-x86_64" ;;
*) echo "Unsupported host OS"; exit 1 ;;
esac
TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$HOST_TAG"
# Set compiler based on architecture
case "{{.ARCH}}" in
arm64)
export CC="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang"
export CXX="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang++"
export GOARCH=arm64
JNI_DIR="arm64-v8a"
;;
amd64|x86_64)
export CC="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang"
export CXX="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang++"
export GOARCH=amd64
JNI_DIR="x86_64"
;;
*)
echo "Unsupported architecture: {{.ARCH}}"
exit 1
;;
esac
export CGO_ENABLED=1
export GOOS=android
mkdir -p {{.BIN_DIR}}
mkdir -p build/android/app/src/main/jniLibs/$JNI_DIR
go build -buildmode=c-shared -overlay build/android/overlay.json {{.BUILD_FLAGS}} \
-o build/android/app/src/main/jniLibs/$JNI_DIR/libwails.so
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}'
compile:go:all-archs:
summary: Compile Go code for all Android architectures (fat APK)
cmds:
- task: compile:go:shared
vars:
ARCH: arm64
- task: compile:go:shared
vars:
ARCH: amd64
package:
summary: Packages a production build of the application into a signed release APK
desc: |
Builds for arm64 by default (covers 99%+ of real devices). Set ARCH=amd64
for emulator-only APKs, or use package:fat for a universal APK.
deps:
- task: build
vars:
PRODUCTION: "true"
ARCH: '{{.ARCH | default "arm64"}}'
cmds:
- task: assemble:apk:release
package:fat:
summary: Packages a production build for all architectures (fat APK)
deps:
- task: build
vars:
PRODUCTION: "true"
ARCH: arm64
cmds:
- task: compile:go:shared
vars:
ARCH: amd64
PRODUCTION: "true"
- task: assemble:apk:release
bundle:
summary: Packages a production AAB (Android App Bundle) for Play Store submission
desc: |
Builds for arm64 by default. Set ARCH=amd64 for emulator builds, or use
bundle:fat for a universal AAB.
deps:
- task: build
vars:
PRODUCTION: "true"
ARCH: '{{.ARCH | default "arm64"}}'
cmds:
- task: assemble:aab:release
bundle:fat:
summary: Packages a production AAB for all architectures
deps:
- task: build
vars:
PRODUCTION: "true"
ARCH: arm64
cmds:
- task: compile:go:shared
vars:
ARCH: amd64
PRODUCTION: "true"
- task: assemble:aab:release
assemble:apk:
summary: Assembles a debug APK using Gradle
preconditions:
- sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]'
msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME"
cmds:
- |
cd build/android
# The exec bit is lost when gradlew is extracted from the embedded
# build assets, so restore it before invoking the wrapper.
chmod +x ./gradlew
./gradlew assembleDebug
cp app/build/outputs/apk/debug/app-debug.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk"
echo "APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk"
assemble:apk:release:
summary: Assembles a release APK using Gradle (signed with the debug keystore unless ANDROID_KEYSTORE_FILE is set)
preconditions:
- sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]'
msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME"
cmds:
- |
cd build/android
# The exec bit is lost when gradlew is extracted from the embedded
# build assets, so restore it before invoking the wrapper.
chmod +x ./gradlew
./gradlew assembleRelease
cp app/build/outputs/apk/release/app-release.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk"
echo "Release APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk"
assemble:aab:
summary: Assembles a debug AAB (Android App Bundle) using Gradle
preconditions:
- sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]'
msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME"
cmds:
- |
cd build/android
# The exec bit is lost when gradlew is extracted from the embedded
# build assets, so restore it before invoking the wrapper.
chmod +x ./gradlew
./gradlew bundleDebug
cp app/build/outputs/bundle/debug/app-debug.aab "../../{{.BIN_DIR}}/{{.APP_NAME}}.aab"
echo "AAB created: {{.BIN_DIR}}/{{.APP_NAME}}.aab"
assemble:aab:release:
summary: Assembles a release AAB for Play Store upload (signed with the debug keystore unless ANDROID_KEYSTORE_FILE is set)
preconditions:
- sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]'
msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME"
cmds:
- |
# With Play App Signing, the keystore configured here is your UPLOAD
# key: Google verifies the upload with it, then re-signs the app with
# the app signing key it manages for distribution.
if [ -z "$ANDROID_KEYSTORE_FILE" ]; then
echo "WARNING: ANDROID_KEYSTORE_FILE is not set, so this AAB will be"
echo "signed with the debug keystore. Google Play rejects debug-signed"
echo "bundles. Set ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD,"
echo "ANDROID_KEY_ALIAS and ANDROID_KEY_PASSWORD before uploading."
fi
cd build/android
# The exec bit is lost when gradlew is extracted from the embedded
# build assets, so restore it before invoking the wrapper.
chmod +x ./gradlew
./gradlew bundleRelease
cp app/build/outputs/bundle/release/app-release.aab "../../{{.BIN_DIR}}/{{.APP_NAME}}.aab"
echo "Release AAB created: {{.BIN_DIR}}/{{.APP_NAME}}.aab"
generate:android:overlay:
internal: true
summary: Generate Go build overlay that registers the Android main
sources:
- build/config.yml
generates:
- build/android/overlay.json
- build/android/gen/main_android.gen.go
cmds:
- wails3 android overlay:gen -out build/android/overlay.json -config build/config.yml
generate:android:bindings:
internal: true
summary: Generates bindings for Android
sources:
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
# Bindings are generated from the Go AST; CGO is disabled so the NDK
# is not required for this step
- wails3 generate bindings -f '-tags android' -clean=true
env:
GOOS: android
CGO_ENABLED: 0
ensure-emulator:
internal: true
summary: Ensure Android Emulator is running
silent: true
cmds:
- |
# Check if an emulator is already running
if "{{.ADB}}" devices | grep -q "emulator"; then
echo "Emulator already running"
exit 0
fi
# Get first available AVD
AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1)
if [ -z "$AVD_NAME" ]; then
# No AVD yet. Mirror the iOS `ensure-simulator` flow and create one
# automatically — but ONLY from a system image that is already
# installed. We never trigger an sdkmanager download from a `run`
# task (that would be a surprise multi-GB download + license prompt).
# Pick the highest-API installed image matching the host ABI.
SDK_ROOT="{{.SDK_ROOT}}"
ABI="{{.ANDROID_ABI}}"
IMG=$(ls -d "$SDK_ROOT"/system-images/android-*/*/"$ABI" 2>/dev/null | sort -V | tail -1)
AVDMANAGER="{{.AVDMANAGER}}"
if [ -n "$IMG" ] && [ -x "$AVDMANAGER" ]; then
PKG="system-images;$(echo "$IMG" | sed "s|$SDK_ROOT/system-images/||" | tr '/' ';')"
echo "No Android Virtual Devices found. Creating 'wails' from $PKG..."
echo "no" | "$AVDMANAGER" create avd --name wails --package "$PKG" --device pixel_7 --force
AVD_NAME=wails
else
echo "No Android Virtual Devices found, and no system image is installed to create one from."
echo "Install a system image and create an AVD, e.g.:"
echo " sdkmanager 'system-images;android-35;google_apis;$ABI'"
echo " avdmanager create avd --name wails --package 'system-images;android-35;google_apis;$ABI' --device pixel_7"
exit 1
fi
fi
echo "Starting emulator: $AVD_NAME"
# Start the emulator daemonized so it outlives this task step. go-task's
# shell tracks background jobs by PID and reaps them when the command's
# interpreter finishes (which nohup/setsid alone don't prevent — the kill
# is direct), so a bare `emulator &` is gone before the later
# install/launch steps run. Launch it from a short-lived child shell that
# backgrounds the emulator and exits immediately: the emulator is then
# reparented to init/launchd and go-task's shell has no handle to reap it.
nohup sh -c "'{{.EMULATOR}}' -avd '$AVD_NAME' -no-snapshot-load </dev/null >/dev/null 2>&1 &" >/dev/null 2>&1
# Wait for emulator to boot (max 120 seconds)
echo "Waiting for emulator to boot..."
"{{.ADB}}" wait-for-device
for i in $(seq 1 120); do
BOOT_COMPLETED=$("{{.ADB}}" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')
if [ "$BOOT_COMPLETED" = "1" ]; then
echo "Emulator booted successfully"
exit 0
fi
sleep 1
done
echo "Emulator boot timeout"
exit 1
preconditions:
- sh: '[ -x "{{.ADB}}" ] || command -v adb'
msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
- sh: '[ -x "{{.EMULATOR}}" ] || command -v emulator'
msg: "emulator not found. Install the Android SDK emulator package (or set ANDROID_HOME)"
deploy-emulator:
summary: Deploy the packaged release APK to the Android Emulator
deps:
- task: package
vars:
ARCH: '{{.ARCH | default .HOST_ARCH}}'
cmds:
- task: ensure-emulator
- '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true'
- '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"'
- '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity'
run:
summary: Build, install and launch a debug build in the Android Emulator
deps:
- task: ensure-emulator
- task: build
cmds:
- task: assemble:apk
- '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true'
- '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"'
- '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity'
device:list:
summary: Lists connected Android devices and emulators (serials)
cmds:
- '"{{.ADB}}" devices -l'
run:device:
summary: Build, install and launch a debug build on a connected physical Android device
deps:
- task: build
vars:
ARCH: arm64
cmds:
- task: assemble:apk
- |
DEVICE='{{.DEVICE_ID | default ""}}'
if [ -z "$DEVICE" ]; then
DEVICE="${DEVICE_ID:-}"
fi
if [ -z "$DEVICE" ]; then
DEVICE=$("{{.ADB}}" devices | awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1; exit }')
fi
if [ -z "$DEVICE" ]; then
echo "Error: no connected physical Android device found."
echo "Pass DEVICE_ID=<serial> to target a device explicitly."
echo "Find connected device serials with: {{.ADB}} devices"
exit 1
fi
echo "Deploying {{.BIN_DIR}}/{{.APP_NAME}}.apk to device $DEVICE..."
"{{.ADB}}" -s "$DEVICE" uninstall {{.APP_ID}} 2>/dev/null || true
"{{.ADB}}" -s "$DEVICE" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
"{{.ADB}}" -s "$DEVICE" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity
preconditions:
- sh: '[ -x "{{.ADB}}" ] || command -v adb'
msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
deploy-device:
summary: Deploy the packaged release APK to a connected physical Android device
deps:
- task: package
vars:
ARCH: arm64
cmds:
- |
DEVICE='{{.DEVICE_ID | default ""}}'
if [ -z "$DEVICE" ]; then
DEVICE="${DEVICE_ID:-}"
fi
if [ -z "$DEVICE" ]; then
DEVICE=$("{{.ADB}}" devices | awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1; exit }')
fi
if [ -z "$DEVICE" ]; then
echo "Error: no connected physical Android device found."
echo "Pass DEVICE_ID=<serial> to target a device explicitly."
echo "Find connected device serials with: {{.ADB}} devices"
exit 1
fi
echo "Deploying {{.BIN_DIR}}/{{.APP_NAME}}.apk to device $DEVICE..."
"{{.ADB}}" -s "$DEVICE" uninstall {{.APP_ID}} 2>/dev/null || true
"{{.ADB}}" -s "$DEVICE" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
"{{.ADB}}" -s "$DEVICE" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity
preconditions:
- sh: '[ -x "{{.ADB}}" ] || command -v adb'
msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
studio:
summary: Open the generated Android project in Android Studio
cmds:
- |
if command -v studio >/dev/null 2>&1; then
studio build/android
elif [ -d "/Applications/Android Studio.app" ]; then
open -a "Android Studio" build/android
else
echo "Android Studio not found. Install it from https://developer.android.com/studio,"
echo "then open the build/android directory."
exit 1
fi
logs:
summary: Stream Android logcat filtered to this app
cmds:
- '"{{.ADB}}" logcat -v time | grep -E "(Wails|{{.APP_NAME}})" || true'
logs:all:
summary: Stream all Android logcat (verbose)
cmds:
- '"{{.ADB}}" logcat -v time'
clean:
summary: Clean build artifacts
cmds:
- rm -rf {{.BIN_DIR}}
- rm -rf build/android/app/build
- rm -rf build/android/app/src/main/jniLibs/*/libwails.so
- rm -rf build/android/.gradle