diff --git a/.github/actions/preview-emulator/action.yml b/.github/actions/preview-emulator/action.yml new file mode 100644 index 0000000..23d3ce5 --- /dev/null +++ b/.github/actions/preview-emulator/action.yml @@ -0,0 +1,160 @@ +name: Set up Android Emulator Preview +description: > + Installs the Android Emulator Preview package (emulators;latest), a + minor-versioned system image, and creates an AVD for it — plus the host + dependencies and console auth token the preview emulator needs on a bare + CI runner. Shared by the emulator-preview-* jobs so their setup cannot drift. + +inputs: + api-level: + description: 'Minor-versioned platform, e.g. 37.0' + required: false + default: '37.0' + target: + description: 'System image target, e.g. google_apis_ps16k' + required: false + default: 'google_apis_ps16k' + abi: + description: 'System image ABI' + required: false + default: 'x86_64' + avd-name: + description: 'Name of the AVD to create' + required: false + default: 'test' + device: + description: 'Device profile passed to avdmanager' + required: false + default: 'pixel_6' + ram-size: + description: 'hw.ramSize in MB' + required: false + default: '4096' + cores: + description: 'hw.cpu.ncore' + required: false + default: '4' + disk-size: + description: 'disk.dataPartition.size' + required: false + default: '8192M' + +runs: + using: composite + steps: + - name: Enable KVM + shell: bash + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # The runner image ships an outdated avdmanager that writes target=android-0 + # for minor-versioned packages (android-37.x); the emulator clamps that to + # API 3 and skips the Vulkan/GLDirectMem auto-enable the ps16k images need. + - name: Update SDK cmdline-tools + shell: bash + run: | + # Composite steps run with `bash -e -o pipefail`, unlike workflow `run:` + # steps (plain `bash -e`). Under pipefail `yes | sdkmanager` fails: yes + # takes SIGPIPE when sdkmanager exits. Turning pipefail off makes the + # pipeline report sdkmanager's status, so real failures still surface. + set +o pipefail + SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" + yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --install "cmdline-tools;latest" > /dev/null + # sdkmanager won't overwrite the preinstalled dir; it installs to latest-2. + if [ -d "$SDK/cmdline-tools/latest-2" ]; then + rm -rf "$SDK/cmdline-tools/latest" + mv "$SDK/cmdline-tools/latest-2" "$SDK/cmdline-tools/latest" + fi + "$SDK/cmdline-tools/latest/bin/avdmanager" list device > /dev/null + + # The AVD must be created BEFORE emulators;latest is installed: with the + # preview package present, avdmanager create silently no-ops. + - name: Install system image and create AVD + shell: bash + run: | + set +o pipefail # see "Update SDK cmdline-tools" -- `yes |` vs pipefail + SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" + AVD='${{ inputs.avd-name }}' + PKG='system-images;android-${{ inputs.api-level }};${{ inputs.target }};${{ inputs.abi }}' + yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install "$PKG" platform-tools > /dev/null + # Mimic the emulator-runner action's invocation (--abi and --device + # skip the auto-select/prompt path where bare creates silently no-op). + echo no | "$SDK/cmdline-tools/latest/bin/avdmanager" create avd --force -n "$AVD" \ + --abi '${{ inputs.target }}/${{ inputs.abi }}' --device '${{ inputs.device }}' \ + --package "$PKG" 2>&1 || echo "avdmanager exit code: $?" + if [ ! -d "$HOME/.android/avd/$AVD.avd" ]; then + echo "avdmanager produced nothing; writing the AVD ini files by hand" + mkdir -p "$HOME/.android/avd/$AVD.avd" + printf 'avd.ini.encoding=UTF-8\npath=%s/.android/avd/%s.avd\npath.rel=avd/%s.avd\ntarget=android-%s\n' \ + "$HOME" "$AVD" "$AVD" '${{ inputs.api-level }}' > "$HOME/.android/avd/$AVD.ini" + cat > "$HOME/.android/avd/$AVD.avd/config.ini" << CFG + AvdId=$AVD + avd.ini.displayname=$AVD + avd.ini.encoding=UTF-8 + abi.type=${{ inputs.abi }} + hw.cpu.arch=${{ inputs.abi }} + image.sysdir.1=system-images/android-${{ inputs.api-level }}/${{ inputs.target }}/${{ inputs.abi }}/ + tag.id=google_apis + tag.display=Google APIs + PlayStore.enabled=no + hw.lcd.density=420 + hw.lcd.width=1080 + hw.lcd.height=2400 + hw.keyboard=yes + hw.gpu.enabled=yes + hw.gpu.mode=auto + disk.dataPartition.size=${{ inputs.disk-size }} + hw.ramSize=${{ inputs.ram-size }} + hw.cpu.ncore=${{ inputs.cores }} + CFG + fi + test -d "$HOME/.android/avd/$AVD.avd" || { echo "ERROR: AVD not created"; exit 1; } + # avdmanager defaults are too small for the ps16k image (the manual + # fallback already writes these keys). + grep -q '^hw.ramSize=' "$HOME/.android/avd/$AVD.avd/config.ini" || \ + printf 'hw.ramSize=%s\nhw.cpu.ncore=%s\ndisk.dataPartition.size=%s\n' \ + '${{ inputs.ram-size }}' '${{ inputs.cores }}' '${{ inputs.disk-size }}' \ + >> "$HOME/.android/avd/$AVD.avd/config.ini" + echo "===== AVD root ini ====="; cat "$HOME/.android/avd/$AVD.ini" + + - name: Install preview emulator + shell: bash + run: | + set +o pipefail # see "Update SDK cmdline-tools" -- `yes |` vs pipefail + SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" + yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install 'emulators;latest' > /dev/null + echo "===== preview emulator version =====" + "$SDK/emulators/latest/emulator" -version | head -8 || true + + # The preview package's qemu links libpulse.so.0 at load time. + - name: Install emulator host dependencies + shell: bash + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq libpulse0 + + # The emulator console starts unauthenticated and then exposes only + # help/ping/auth/quit/avd -- 'kill' is not in that set, so 'adb emu kill' + # answers "KO: unknown command" and does nothing. The preview emulator + # currently READS $HOME/.emulator_console_auth_token but never creates it, + # and a fresh runner has no such file. Creating it here (both the emulator + # and adb read this same file, so the value is arbitrary) unlocks the full + # command set and makes 'adb emu kill' a real graceful shutdown. + # + # A fix has been proposed upstream so the emulator creates this file + # itself; this step already defers to it -- once a preview build ships + # that change, an existing token is left untouched and this becomes a + # no-op that can be deleted. + - name: Ensure emulator console auth token + shell: bash + run: | + TOKEN="$HOME/.emulator_console_auth_token" + if [ -s "$TOKEN" ]; then + echo "console auth token already present; leaving it untouched" + else + printf 'ciConsoleTokenForHeadlessRuns' > "$TOKEN" + chmod 600 "$TOKEN" + echo "created console auth token (workaround: preview emulator does not create one)" + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1253c9..a880981 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -432,83 +432,10 @@ jobs: echo "y" | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager \ "ndk;29.0.14206865" 2>&1 | tail -3 - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - # The runner image ships an outdated avdmanager that writes target=android-0 - # for minor-versioned packages (android-37.x); the emulator clamps that to - # API 3 and skips the Vulkan/GLDirectMem auto-enable the ps16k images need. - - name: Update SDK cmdline-tools - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --install "cmdline-tools;latest" > /dev/null - # sdkmanager won't overwrite the preinstalled dir; it installs to latest-2. - if [ -d "$SDK/cmdline-tools/latest-2" ]; then - rm -rf "$SDK/cmdline-tools/latest" - mv "$SDK/cmdline-tools/latest-2" "$SDK/cmdline-tools/latest" - fi - "$SDK/cmdline-tools/latest/bin/avdmanager" list device > /dev/null - - # The AVD must be created BEFORE emulators;latest is installed: with the - # preview package's new-generation metadata in the SDK, avdmanager create - # silently produces nothing (exit 0) — same trap as the Android CLI - # packages. - - name: Install API 37.0 ps16k image and create AVD - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install 'system-images;android-37.0;google_apis_ps16k;x86_64' platform-tools > /dev/null - # Mimic the emulator-runner action's invocation (--abi and --device - # skip the auto-select/prompt path where bare creates silently no-op). - echo no | "$SDK/cmdline-tools/latest/bin/avdmanager" create avd --force -n test --abi 'google_apis_ps16k/x86_64' --device 'pixel_6' --package 'system-images;android-37.0;google_apis_ps16k;x86_64' 2>&1 || echo "avdmanager exit code: $?" - if [ ! -d "$HOME/.android/avd/test.avd" ]; then - echo "avdmanager produced nothing; writing the AVD ini files by hand" - mkdir -p "$HOME/.android/avd/test.avd" - printf 'avd.ini.encoding=UTF-8\npath=%s/.android/avd/test.avd\npath.rel=avd/test.avd\ntarget=android-37.0\n' "$HOME" > "$HOME/.android/avd/test.ini" - cat > "$HOME/.android/avd/test.avd/config.ini" << 'CFG' - AvdId=test - avd.ini.displayname=test - avd.ini.encoding=UTF-8 - abi.type=x86_64 - hw.cpu.arch=x86_64 - image.sysdir.1=system-images/android-37.0/google_apis_ps16k/x86_64/ - tag.id=google_apis - tag.display=Google APIs - PlayStore.enabled=no - hw.lcd.density=420 - hw.lcd.width=1080 - hw.lcd.height=2400 - hw.keyboard=yes - hw.gpu.enabled=yes - hw.gpu.mode=auto - disk.dataPartition.size=8192M - hw.ramSize=4096 - hw.cpu.ncore=4 - CFG - sed -i 's/^ //' "$HOME/.android/avd/test.avd/config.ini" - fi - test -d "$HOME/.android/avd/test.avd" || { echo "ERROR: AVD not created"; exit 1; } - # avdmanager defaults are too small for the ps16k image (the manual - # fallback already writes these keys). - grep -q '^hw.ramSize=' "$HOME/.android/avd/test.avd/config.ini" || printf 'hw.ramSize=4096\nhw.cpu.ncore=4\ndisk.dataPartition.size=8192M\n' >> "$HOME/.android/avd/test.avd/config.ini" - echo "===== AVD root ini ====="; cat "$HOME/.android/avd/test.ini" - - - name: Install preview emulator - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install 'emulators;latest' > /dev/null - echo "===== preview emulator version =====" - "$SDK/emulators/latest/emulator" -version | head -8 || true - - # The preview package's qemu links libpulse.so.0 at load time even with - # -noaudio (its command line already uses -audiodev none); the runner - # image doesn't ship it. - - name: Install emulator host dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq libpulse0 + # Shared setup: KVM, cmdline-tools, ps16k image + AVD, the preview + # emulator package, libpulse, and the console auth token. + - name: Set up Android Emulator Preview + uses: ./.github/actions/preview-emulator - name: Boot preview emulator timeout-minutes: 12 @@ -623,76 +550,16 @@ jobs: echo "y" | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager \ "ndk;29.0.14206865" 2>&1 | tail -3 - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - name: Build debug APK run: ./gradlew assembleDebug --no-daemon # The runner image ships an outdated avdmanager that writes target=android-0 # for minor-versioned packages (android-37.x); the emulator clamps that to # API 3 and skips the Vulkan/GLDirectMem auto-enable the ps16k images need. - - name: Update SDK cmdline-tools - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --install "cmdline-tools;latest" > /dev/null - # sdkmanager won't overwrite the preinstalled dir; it installs to latest-2. - if [ -d "$SDK/cmdline-tools/latest-2" ]; then - rm -rf "$SDK/cmdline-tools/latest" - mv "$SDK/cmdline-tools/latest-2" "$SDK/cmdline-tools/latest" - fi - "$SDK/cmdline-tools/latest/bin/avdmanager" list device > /dev/null - - # The AVD must be created BEFORE emulators;latest is installed (its - # metadata makes avdmanager create silently no-op). - - name: Install API 37.0 ps16k image and create AVD - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install 'system-images;android-37.0;google_apis_ps16k;x86_64' platform-tools > /dev/null - echo no | "$SDK/cmdline-tools/latest/bin/avdmanager" create avd --force -n test --abi 'google_apis_ps16k/x86_64' --device 'pixel_6' --package 'system-images;android-37.0;google_apis_ps16k;x86_64' 2>&1 || echo "avdmanager exit code: $?" - if [ ! -d "$HOME/.android/avd/test.avd" ]; then - echo "avdmanager produced nothing; writing the AVD ini files by hand" - mkdir -p "$HOME/.android/avd/test.avd" - printf 'avd.ini.encoding=UTF-8\npath=%s/.android/avd/test.avd\npath.rel=avd/test.avd\ntarget=android-37.0\n' "$HOME" > "$HOME/.android/avd/test.ini" - cat > "$HOME/.android/avd/test.avd/config.ini" << 'CFG' - AvdId=test - avd.ini.displayname=test - avd.ini.encoding=UTF-8 - abi.type=x86_64 - hw.cpu.arch=x86_64 - image.sysdir.1=system-images/android-37.0/google_apis_ps16k/x86_64/ - tag.id=google_apis - tag.display=Google APIs - PlayStore.enabled=no - hw.lcd.density=420 - hw.lcd.width=1080 - hw.lcd.height=2400 - hw.keyboard=yes - hw.gpu.enabled=yes - hw.gpu.mode=auto - disk.dataPartition.size=8192M - hw.ramSize=4096 - hw.cpu.ncore=4 - CFG - sed -i 's/^ //' "$HOME/.android/avd/test.avd/config.ini" - fi - test -d "$HOME/.android/avd/test.avd" || { echo "ERROR: AVD not created"; exit 1; } - grep -q '^hw.ramSize=' "$HOME/.android/avd/test.avd/config.ini" || printf 'hw.ramSize=4096\nhw.cpu.ncore=4\ndisk.dataPartition.size=8192M\n' >> "$HOME/.android/avd/test.avd/config.ini" - - - name: Install preview emulator - run: | - SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}}" - yes | "$SDK/cmdline-tools/latest/bin/sdkmanager" --channel=3 --install 'emulators;latest' > /dev/null - "$SDK/emulators/latest/emulator" -version | head -8 || true - - # The preview package's qemu links libpulse.so.0 at load time. - - name: Install emulator host dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq libpulse0 + # Shared setup: KVM, cmdline-tools, ps16k image + AVD, the preview + # emulator package, libpulse, and the console auth token. + - name: Set up Android Emulator Preview + uses: ./.github/actions/preview-emulator - name: Snapshot multi-run cycle timeout-minutes: 25 @@ -703,18 +570,8 @@ jobs: PKG=com.jpcottin.vulkanspaceinvaders mkdir -p "${{ github.workspace }}/screenshots" - # The emulator console starts unauthenticated and then exposes only - # help/ping/auth/quit/avd -- 'kill' is not in that set, which is why - # 'adb emu kill' used to answer "KO: unknown command" and act as a - # no-op. The preview emulator READS $HOME/.emulator_console_auth_token - # but never creates it, and a fresh runner has no such file. Creating - # it before the first boot (both the emulator and adb read this same - # file, so the value is arbitrary) unlocks the full command set and - # makes 'adb emu kill' a real graceful shutdown: the emulator exits in - # ~2s having saved its snapshot, instead of being SIGTERMed minutes - # later while the game keeps playing. - printf 'ciConsoleTokenForHeadlessRuns' > "$HOME/.emulator_console_auth_token" - chmod 600 "$HOME/.emulator_console_auth_token" + # The console auth token that makes 'adb emu kill' work is created by + # the Set up Android Emulator Preview action; see it for the details. SELF_PGID="$(ps -o pgid= -p $$ | tr -d ' ')"