fix(hud): keep phone WiFi lock asserted across the dial loop #634
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Branch builds | |
| # On every push to a non-main branch, build the phone APK, the Wear OS APK | |
| # (when the branch has a wear/ module), and the Garmin Connect IQ artifacts | |
| # (when GARMIN_SDK_BUNDLE_URL is configured), then replace the rolling | |
| # pre-release tagged `branch-<name>` with the freshly built assets. | |
| # Testers always get the latest commit by visiting the same release URL. | |
| # | |
| # Output naming (device-first, matching the Gradle module outputs | |
| # phone-debug.apk / wearos-debug.apk, and consistent with release-apk.yml): | |
| # phone-<branch>-<short_sha>.apk | |
| # wearos-<branch>-<short_sha>.apk | |
| # hud-<branch>-<short_sha>.apk | |
| # garmin-<branch>-<short_sha>.iq (multi-device Connect IQ bundle) | |
| # garmin-<branch>-<short_sha>-prgs.zip (per-device .prg files bundled) | |
| # | |
| # Garmin CI is opt-in: set the GARMIN_SDK_BUNDLE_URL repo secret to a stable | |
| # download URL for the bundle produced by tools/bundle-ciq-sdk-for-ci.ps1. | |
| # Without that secret, the garmin job exits as `skipped` and the phone/wear | |
| # jobs keep working. | |
| # | |
| # Pull-request runs build the APK as a workflow artifact (downloadable from | |
| # the Actions tab for ~90 days) but do not touch the release list, so PRs | |
| # from forks can't push tags. | |
| on: | |
| push: | |
| branches-ignore: | |
| - main | |
| tags-ignore: | |
| - "**" | |
| pull_request: | |
| branches: | |
| - "**" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| meta: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| branch: ${{ steps.meta.outputs.branch }} | |
| short_sha: ${{ steps.meta.outputs.short_sha }} | |
| tag: ${{ steps.meta.outputs.tag }} | |
| suffix: ${{ steps.meta.outputs.suffix }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # full history so we can produce a "since main" log | |
| - name: Compute build metadata | |
| id: meta | |
| run: | | |
| BRANCH="${GITHUB_REF##*/}" | |
| SHORT_SHA="$(git rev-parse --short HEAD)" | |
| TAG="branch-${BRANCH}" | |
| SUFFIX="${BRANCH}-${SHORT_SHA}" | |
| echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" | |
| echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT" | |
| android: | |
| runs-on: ubuntu-latest | |
| needs: meta | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Make gradlew executable | |
| run: chmod +x ./gradlew | |
| - name: Build debug APKs | |
| # Build the phone APK on every branch. Build the wear + HUD APKs | |
| # too when the branch has their modules, so testers can grab the | |
| # whole stack from the same rolling release. | |
| run: | | |
| targets=":app:assembleDebug" | |
| if [ -f wear/build.gradle.kts ]; then | |
| targets="$targets :wear:assembleDebug" | |
| fi | |
| if [ -f hud/build.gradle.kts ]; then | |
| targets="$targets :hud:assembleDebug" | |
| fi | |
| ./gradlew :app:testDebugUnitTest :hud-protocol:test $targets --no-daemon --stacktrace | |
| env: | |
| GRADLE_OPTS: -Xmx4g -Dorg.gradle.jvmargs=-Xmx4g | |
| - name: Rename APKs to canonical filenames | |
| run: | | |
| mkdir -p /tmp/out | |
| cp app/build/outputs/apk/debug/phone-debug.apk \ | |
| "/tmp/out/phone-${{ needs.meta.outputs.suffix }}.apk" | |
| if [ -f wear/build/outputs/apk/debug/wearos-debug.apk ]; then | |
| cp wear/build/outputs/apk/debug/wearos-debug.apk \ | |
| "/tmp/out/wearos-${{ needs.meta.outputs.suffix }}.apk" | |
| fi | |
| if [ -f hud/build/outputs/apk/debug/hud-debug.apk ]; then | |
| cp hud/build/outputs/apk/debug/hud-debug.apk \ | |
| "/tmp/out/hud-${{ needs.meta.outputs.suffix }}.apk" | |
| fi | |
| ls -la /tmp/out | |
| - name: Upload as workflow artifacts (always) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-${{ needs.meta.outputs.short_sha }} | |
| path: /tmp/out/*.apk | |
| retention-days: 90 | |
| - name: Stage for release | |
| if: github.event_name != 'pull_request' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: stage-android-${{ needs.meta.outputs.short_sha }} | |
| path: /tmp/out/*.apk | |
| retention-days: 1 | |
| garmin: | |
| runs-on: ubuntu-latest | |
| needs: meta | |
| # Skip cleanly when the bundle URL secret isn't set, so the rest of the | |
| # workflow still works on forks / first-time setups. Note: secrets can't | |
| # be referenced directly in `if:`, so we route through an env var. | |
| env: | |
| GARMIN_SDK_BUNDLE_URL: ${{ secrets.GARMIN_SDK_BUNDLE_URL }} | |
| steps: | |
| - name: Skip if bundle URL not configured | |
| id: precheck | |
| run: | | |
| if [ -z "$GARMIN_SDK_BUNDLE_URL" ]; then | |
| echo "::notice::GARMIN_SDK_BUNDLE_URL secret not set; skipping Garmin build." | |
| echo "::notice::See tools/bundle-ciq-sdk-for-ci.ps1 to enable." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Checkout | |
| if: steps.precheck.outputs.skip == 'false' | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 (monkeyc runs on a JRE) | |
| if: steps.precheck.outputs.skip == 'false' | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Download CIQ SDK bundle | |
| if: steps.precheck.outputs.skip == 'false' | |
| run: | | |
| mkdir -p /tmp/ciq | |
| # GH release URLs are public for public repos; if you host on a | |
| # private release, prepend `Authorization: token ${{ secrets.GITHUB_TOKEN }}` | |
| # via -H. The bundle is the zip produced by bundle-ciq-sdk-for-ci.ps1. | |
| curl -fL --retry 3 -o /tmp/ciq/bundle.zip "$GARMIN_SDK_BUNDLE_URL" | |
| unzip -q /tmp/ciq/bundle.zip -d /tmp/ciq | |
| test -x /tmp/ciq/ciq-bundle/sdk/bin/monkeyc || chmod +x /tmp/ciq/ciq-bundle/sdk/bin/monkeyc /tmp/ciq/ciq-bundle/sdk/bin/monkeydo | |
| # monkeyc hard-codes ~/.Garmin/ConnectIQ/Devices as the device- | |
| # profile root — symlink the bundled devices there so monkeyc | |
| # finds every manifest entry. Otherwise it errors with "Device | |
| # ids ... not recognized" for everything in manifest.xml. | |
| mkdir -p "$HOME/.Garmin/ConnectIQ" | |
| ln -sfn /tmp/ciq/ciq-bundle/devices "$HOME/.Garmin/ConnectIQ/Devices" | |
| ls /tmp/ciq/ciq-bundle/devices | head -5 | |
| - name: Generate one-off developer key | |
| if: steps.precheck.outputs.skip == 'false' | |
| # The developer key only identifies the build to the CIQ Store; for | |
| # sideloads (which is all CI produces) a fresh per-run key works fine | |
| # and avoids checking the real key into secrets. The store-submission | |
| # build still happens locally with the user's persistent key. | |
| run: | | |
| /tmp/ciq/ciq-bundle/sdk/bin/monkeybrains.jar 2>/dev/null || true | |
| openssl genrsa -out /tmp/ciq/dev_key.pem 4096 | |
| openssl pkcs8 -topk8 -inform PEM -outform DER -in /tmp/ciq/dev_key.pem -out /tmp/ciq/dev_key.der -nocrypt | |
| - name: Build .iq (multi-device bundle) | |
| if: steps.precheck.outputs.skip == 'false' | |
| run: | | |
| mkdir -p /tmp/out | |
| export PATH="/tmp/ciq/ciq-bundle/sdk/bin:$PATH" | |
| # monkeyc resolves device profiles relative to the CIQ_DEVICES env | |
| # var; point it at the bundled directory so it doesn't go looking | |
| # for ~/.Garmin/ConnectIQ/Devices on the runner. | |
| export CIQ_HOME=/tmp/ciq/ciq-bundle/sdk | |
| export GARMIN_DEVICE_DIR=/tmp/ciq/ciq-bundle/devices | |
| OUT="/tmp/out/garmin-${{ needs.meta.outputs.suffix }}.iq" | |
| monkeyc \ | |
| -f garmin-watch-app/monkey.jungle \ | |
| -o "$OUT" \ | |
| -y /tmp/ciq/dev_key.der \ | |
| -e \ | |
| -w | |
| ls -la "$OUT" | |
| - name: Build per-device .prg files | |
| if: steps.precheck.outputs.skip == 'false' | |
| run: | | |
| export PATH="/tmp/ciq/ciq-bundle/sdk/bin:$PATH" | |
| export CIQ_HOME=/tmp/ciq/ciq-bundle/sdk | |
| export GARMIN_DEVICE_DIR=/tmp/ciq/ciq-bundle/devices | |
| # Stage per-device .prgs in /tmp/prgs first, then zip the lot | |
| # into one garmin-<suffix>-prgs.zip. The branch's rolling pre- | |
| # release used to carry 135 individual .prg files alongside the | |
| # .iq, which buried the .iq under a wall of asset rows. The .iq | |
| # is the universal artifact (multi-device, Connect IQ Store | |
| # format); .prgs are advanced-sideload-only — bundling them keeps | |
| # the release page scannable. | |
| mkdir -p /tmp/prgs | |
| for d in venu2 venu3 fenix843mm fenix847mm fenix6xpro fenix8solar47mm epix2pro47mm instinct2 instinct2s instinct2x instinct3amoled45mm instinct3amoled50mm instinct3solar45mm instinctcrossover instinctcrossoveramoled instincte40mm instincte45mm; do | |
| OUT="/tmp/prgs/garmin-${{ needs.meta.outputs.suffix }}-${d}.prg" | |
| echo "--- building $d ---" | |
| monkeyc \ | |
| -f garmin-watch-app/monkey.jungle \ | |
| -o "$OUT" \ | |
| -y /tmp/ciq/dev_key.der \ | |
| -d "$d" \ | |
| -w | |
| done | |
| (cd /tmp/prgs && zip -q "/tmp/out/garmin-${{ needs.meta.outputs.suffix }}-prgs.zip" *.prg) | |
| ls -la /tmp/out | |
| - name: Upload as workflow artifacts (always) | |
| if: steps.precheck.outputs.skip == 'false' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: garmin-${{ needs.meta.outputs.short_sha }} | |
| path: /tmp/out/* | |
| retention-days: 90 | |
| - name: Stage for release | |
| if: steps.precheck.outputs.skip == 'false' && github.event_name != 'pull_request' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: stage-garmin-${{ needs.meta.outputs.short_sha }} | |
| path: /tmp/out/* | |
| retention-days: 1 | |
| publish: | |
| runs-on: ubuntu-latest | |
| needs: [meta, android, garmin] | |
| # Run even if garmin was skipped, but not for PRs. | |
| if: always() && github.event_name != 'pull_request' && needs.android.result == 'success' | |
| steps: | |
| - name: Checkout (for release body) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download staged artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/stage | |
| pattern: stage-* | |
| merge-multiple: true | |
| - name: Compose release body | |
| id: body | |
| run: | | |
| BRANCH="${{ needs.meta.outputs.branch }}" | |
| SHORT_SHA="${{ needs.meta.outputs.short_sha }}" | |
| # Pull per-branch description if BRANCH.md exists. | |
| if [ -f BRANCH.md ]; then | |
| cp BRANCH.md /tmp/branch-notes.md | |
| else | |
| cat > /tmp/branch-notes.md <<EOF | |
| # ${BRANCH} | |
| No \`BRANCH.md\` found at repo root. Add one to describe what this | |
| branch is for and who should test it. | |
| EOF | |
| fi | |
| git log --pretty=format:'- %h %s' main..HEAD | head -50 > /tmp/commit-log.md || true | |
| { | |
| echo "**Pre-release from branch \`${BRANCH}\`**, automatic build at \`${SHORT_SHA}\`." | |
| echo | |
| echo "This is a CI-built debug APK. It can't update an existing Play Store install in place; uninstall the Play version first to install this build." | |
| echo | |
| cat /tmp/branch-notes.md | |
| echo | |
| echo "## Recent commits" | |
| echo | |
| cat /tmp/commit-log.md | |
| } > /tmp/release-body.md | |
| ls -la /tmp/stage | |
| - name: Strip prior assets from rolling release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # action-gh-release attaches new files but doesn't remove previous | |
| # builds; without this the rolling pre-release accumulates one set | |
| # of artifacts per push. | |
| tag="${{ needs.meta.outputs.tag }}" | |
| if gh release view "$tag" >/dev/null 2>&1; then | |
| gh release view "$tag" --json assets \ | |
| --jq '.assets[] | select(.name | test("\\.(apk|aab|iq|prg|zip)$")) | .name' \ | |
| | while read -r asset; do | |
| [ -n "$asset" ] && gh release delete-asset "$tag" "$asset" --yes || true | |
| done | |
| fi | |
| - name: Replace rolling pre-release with new build | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.meta.outputs.tag }} | |
| name: ${{ needs.meta.outputs.tag }} (${{ needs.meta.outputs.short_sha }}) | |
| body_path: /tmp/release-body.md | |
| prerelease: true | |
| make_latest: "false" | |
| files: /tmp/stage/* | |
| - name: Announce build on Telegram | |
| # Posts each branch build into the @EUCPlanetApp "Github" topic. Opt-in | |
| # via the TELEGRAM_* secrets; failures never break the build (|| true). | |
| # The commit message is passed via env (never inlined) to avoid | |
| # expression injection. | |
| env: | |
| BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| CHAT_ID: ${{ secrets.TELEGRAM_RELEASES_CHAT_ID }} | |
| THREAD_ID: ${{ secrets.TELEGRAM_RELEASES_THREAD_ID }} | |
| GH_REPO: ${{ github.repository }} | |
| SERVER_URL: ${{ github.server_url }} | |
| BRANCH: ${{ needs.meta.outputs.branch }} | |
| SHORT_SHA: ${{ needs.meta.outputs.short_sha }} | |
| TAG: ${{ needs.meta.outputs.tag }} | |
| COMMIT_MSG: ${{ github.event.head_commit.message }} | |
| run: | | |
| if [ -z "$BOT_TOKEN" ] || [ -z "$CHAT_ID" ] || [ -z "$THREAD_ID" ]; then | |
| echo "::notice::Telegram secrets not set; skipping announcement."; exit 0 | |
| fi | |
| url="${SERVER_URL}/${GH_REPO}/releases/tag/${TAG}" | |
| subject=$(printf '%s' "$COMMIT_MSG" | head -n1) | |
| esc() { printf '%s' "$1" | sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g'; } | |
| text="🔨 <b>Build</b> on <code>$(esc "$BRANCH")</code> @ <code>$(esc "$SHORT_SHA")</code> | |
| $(esc "$subject") | |
| ⬇️ Latest build (phone, Wear OS, HUD, Garmin): | |
| ${url}" | |
| curl -sS -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ | |
| --data-urlencode "chat_id=${CHAT_ID}" \ | |
| -d "message_thread_id=${THREAD_ID}" \ | |
| --data-urlencode "text=${text}" \ | |
| -d "parse_mode=HTML" -d "disable_web_page_preview=true" \ | |
| -o /dev/null -w "Telegram HTTP %{http_code}\n" || true |