version: '3' includes: common: ../Taskfile.yml vars: APP_ID: '{{.APP_ID | default "com.wails.app"}}' MIN_SDK: '21' TARGET_SDK: '34' # 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"' 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 deps: - task: build vars: PRODUCTION: "true" ARCH: '{{.ARCH | default .HOST_ARCH}}' 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 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" 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 echo "No Android Virtual Devices found. Create one with:" echo " avdmanager create avd --name wails --package 'system-images;android-34;google_apis;{{.ANDROID_ABI}}' --device pixel_7" exit 1 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 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: [package] 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' 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