-
Notifications
You must be signed in to change notification settings - Fork 2
359 lines (329 loc) · 14.5 KB
/
Copy pathbranch-apk.yml
File metadata and controls
359 lines (329 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
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