Skip to content

fix(history-viewer): bigger prediction markers with halo #33

fix(history-viewer): bigger prediction markers with halo

fix(history-viewer): bigger prediction markers with halo #33

Workflow file for this run

name: Release builds
# On every push of a `v*` tag (e.g. v0.9.0), build every surface with
# consistent filenames and attach them to the matching GitHub release:
#
# phone-<version>-slim.apk (slim release-signed phone APK)
# wearos-<version>.apk (release-signed wear APK)
# hud-<version>.apk (release-signed HUD APK)
# garmin-<version>.iq (multi-device Connect IQ bundle —
# this is the file you upload to the
# Connect IQ Store; one .iq covers
# every supported device)
# garmin-<version>-prgs.zip (per-device .prg files bundled —
# for advanced sideload via
# Connect IQ Mobile on Garmins
# without store access)
#
# `<version>` is the tag with the leading `v` stripped (v0.9.0 -> 0.9.0).
# Naming matches the branch-builds workflow's `<device>-...` convention.
#
# Why slim? The Play AAB embeds the wear APK so Google Play auto-delivers
# it to a paired Wear OS watch. Sideload users who download the APK from
# GitHub don't need that path (many of them don't even own a watch), so
# we save them ~40 MB by stripping the embedded wear from the GitHub copy.
# The companion wear APK is uploaded as its own file for sideloaders.
#
# 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` cleanly.
#
# Required signing secrets (Settings -> Secrets and variables -> Actions):
# RELEASE_KEYSTORE : base64-encoded contents of the release .jks file
# (locally: `base64 -w0 path/to/release.jks > out.txt`)
# KEYSTORE_PASSWORD : store password
# KEY_ALIAS : signing key alias
# KEY_PASSWORD : key password
#
# If any signing secret is missing the workflow short-circuits with a clear
# message rather than producing an unsigned APK by accident.
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to attach the artifacts to (e.g. v0.8.11)"
required: true
permissions:
contents: write
jobs:
meta:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.ref }}
version: ${{ steps.tag.outputs.version }}
steps:
- name: Resolve target tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
ref="${{ inputs.tag }}"
else
ref="${GITHUB_REF##*/}"
fi
version="${ref#v}"
echo "ref=${ref}" >> "$GITHUB_OUTPUT"
echo "version=${version}" >> "$GITHUB_OUTPUT"
android:
runs-on: ubuntu-latest
needs: meta
steps:
- name: Checkout tag
uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
fetch-depth: 1
- name: Verify signing secrets are configured
env:
RELEASE_KEYSTORE: ${{ secrets.RELEASE_KEYSTORE }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
missing=()
[ -z "$RELEASE_KEYSTORE" ] && missing+=(RELEASE_KEYSTORE)
[ -z "$KEYSTORE_PASSWORD" ] && missing+=(KEYSTORE_PASSWORD)
[ -z "$KEY_ALIAS" ] && missing+=(KEY_ALIAS)
[ -z "$KEY_PASSWORD" ] && missing+=(KEY_PASSWORD)
if [ "${#missing[@]}" -gt 0 ]; then
echo "::error::Release signing secrets missing: ${missing[*]}"
echo "::error::Add them in Settings -> Secrets and variables -> Actions, then re-run this workflow."
exit 1
fi
- name: Restore release keystore
env:
RELEASE_KEYSTORE: ${{ secrets.RELEASE_KEYSTORE }}
run: |
echo "$RELEASE_KEYSTORE" | base64 -d > release.jks
test -s release.jks || {
echo "::error::Decoded RELEASE_KEYSTORE is empty."; exit 1; }
magic4=$(head -c4 release.jks | xxd -p)
magic2=$(printf '%s' "$magic4" | cut -c1-4)
case "$magic4" in
feedfeed|cececece) : ;;
*)
case "$magic2" in
3082) : ;;
*)
echo "::error::Decoded RELEASE_KEYSTORE has unexpected magic header (got $magic4). Expected feedfeedXX (JKS), cececeXX (JCEKS) or 3082XXXX (PKCS#12). Re-encode with: base64 -w0 path/to/release.jks"
exit 1 ;;
esac ;;
esac
- name: Write keystore.properties
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
cat > keystore.properties <<EOF
storeFile=release.jks
storePassword=$KEYSTORE_PASSWORD
keyAlias=$KEY_ALIAS
keyPassword=$KEY_PASSWORD
EOF
- 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: Verify checkout supports the slim flag
run: |
# The `-PembedWear=false` path was added after v0.8.11 was tagged.
# If we're firing against an older ref that doesn't carry the flag,
# we'd silently build a 60 MB fat APK without realising it, and
# then overwrite the manually-uploaded slim APK on the release.
# Hard-fail with a clear message so the operator knows to either
# cherry-pick the flag onto that tag, or upload the slim APK by
# hand.
if ! grep -q "embedWear" app/build.gradle.kts; then
echo "::error::This ref does not contain the embedWear Gradle flag (added after v0.8.11). The workflow would silently build a fat APK. Skipping."
exit 1
fi
- name: Build slim release phone APK + wear + HUD
run: |
targets=":app:assembleRelease"
if [ -f wear/build.gradle.kts ]; then
targets="$targets :wear:assembleRelease"
fi
if [ -f hud/build.gradle.kts ]; then
targets="$targets :hud:assembleRelease"
fi
./gradlew :app:testDebugUnitTest :hud-protocol:test $targets -PembedWear=false --no-daemon --stacktrace
env:
GRADLE_OPTS: -Xmx4g -Dorg.gradle.jvmargs=-Xmx4g
- name: Stage APKs with canonical filenames
run: |
mkdir -p /tmp/out
cp app/build/outputs/apk/release/phone-release.apk \
"/tmp/out/phone-${{ needs.meta.outputs.version }}-slim.apk"
# Wear release APK lives under wear/build/outputs/apk/release/. Some
# branches build only debug for wear; fall back to that.
if [ -f wear/build/outputs/apk/release/wearos-release.apk ]; then
cp wear/build/outputs/apk/release/wearos-release.apk \
"/tmp/out/wearos-${{ needs.meta.outputs.version }}.apk"
elif [ -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.version }}.apk"
fi
# HUD release APK — same release/debug fallback as wear so older
# tags without a release signing config still produce something
# installable.
if [ -f hud/build/outputs/apk/release/hud-release.apk ]; then
cp hud/build/outputs/apk/release/hud-release.apk \
"/tmp/out/hud-${{ needs.meta.outputs.version }}.apk"
elif [ -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.version }}.apk"
fi
ls -la /tmp/out
- name: Stage for publish
uses: actions/upload-artifact@v4
with:
name: stage-android
path: /tmp/out/*
retention-days: 1
- name: Wipe sensitive files
if: always()
run: |
rm -f release.jks keystore.properties
garmin:
runs-on: ubuntu-latest
needs: meta
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 tag
if: steps.precheck.outputs.skip == 'false'
uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
fetch-depth: 1
- name: Set up JDK 17
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
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 — it doesn't honour GARMIN_DEVICE_DIR or any
# equivalent flag. Symlink the bundled devices into the expected
# location so monkeyc finds every manifest entry, otherwise it
# errors with "Device ids ... not recognized" for everything in
# garmin-watch-app/manifest.xml.
mkdir -p "$HOME/.Garmin/ConnectIQ"
ln -sfn /tmp/ciq/ciq-bundle/devices "$HOME/.Garmin/ConnectIQ/Devices"
- name: Generate one-off developer key
if: steps.precheck.outputs.skip == 'false'
run: |
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 + per-device .prg files
if: steps.precheck.outputs.skip == 'false'
run: |
mkdir -p /tmp/out
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
IQ_OUT="/tmp/out/garmin-${{ needs.meta.outputs.version }}.iq"
monkeyc \
-f garmin-watch-app/monkey.jungle \
-o "$IQ_OUT" \
-y /tmp/ciq/dev_key.der \
-e \
-w
ls -la "$IQ_OUT"
# Build each per-device .prg into /tmp/prgs first, then zip
# the lot into a single garmin-<version>-prgs.zip — uploading
# 17 individual files clutters the release page and most
# riders only need the .iq above. Power-users who specifically
# want a single device's .prg can unzip locally.
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.version }}-${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.version }}-prgs.zip" *.prg)
ls -la /tmp/out
- name: Stage for publish
if: steps.precheck.outputs.skip == 'false'
uses: actions/upload-artifact@v4
with:
name: stage-garmin
path: /tmp/out/*
retention-days: 1
publish:
runs-on: ubuntu-latest
needs: [meta, android, garmin]
# Run even if garmin was skipped, as long as android succeeded.
if: always() && needs.android.result == 'success'
steps:
- name: Download staged artifacts
uses: actions/download-artifact@v4
with:
path: /tmp/stage
pattern: stage-*
merge-multiple: true
- name: Replace prior artifacts on the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
tag="${{ needs.meta.outputs.tag }}"
# Delete only the canonical-named assets we're about to re-upload,
# so manually-uploaded extras on the same release aren't touched.
for f in /tmp/stage/*; do
name=$(basename "$f")
gh release delete-asset "$tag" "$name" --yes 2>/dev/null || true
done
gh release upload "$tag" /tmp/stage/* --clobber
ls -la /tmp/stage
- name: Announce release on Telegram
# Posts the release into the @EUCPlanetApp "Github" topic. Opt-in via the
# TELEGRAM_* secrets; a Telegram hiccup never fails the release (|| true).
# github.event / notes are 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_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }}
TAG: ${{ needs.meta.outputs.tag }}
VERSION: ${{ needs.meta.outputs.version }}
run: |
if [ -z "$BOT_TOKEN" ] || [ -z "$CHAT_ID" ] || [ -z "$THREAD_ID" ]; then
echo "::notice::Telegram secrets not set; skipping announcement."; exit 0
fi
notes=$(gh release view "$TAG" --json body -q .body 2>/dev/null || true)
url="${SERVER_URL}/${GH_REPO}/releases/tag/${TAG}"
esc() { printf '%s' "$1" | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g'; }
text="🚀 <b>EUC Planet $(esc "$VERSION")</b> released
$(esc "$notes")
⬇️ Download (phone, Wear OS, HUD, Garmin):
${url}
⌚ Garmin on the Connect IQ Store:
https://apps.garmin.com/apps/630e5d32-637d-4612-84e3-35e6d0bbee10"
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