-
Notifications
You must be signed in to change notification settings - Fork 0
331 lines (291 loc) · 11.8 KB
/
android.yml
File metadata and controls
331 lines (291 loc) · 11.8 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
name: Android CI
# W3 Android wrapper app — see android/README.md. Runs on every PR + every
# push to main that touches the Android workspace or its CI config.
#
# What this validates that the rest of CI doesn't:
# * Gradle + AGP can resolve the dependency graph
# * Kotlin compiles cleanly (the four Wave-3 parallel agents shipped
# ~5,000 lines of Android code; no JVM was available in their
# sandbox so this is the first real compile check)
# * Unit tests pass (Robolectric + Turbine + the Poseidon vectors)
# * Lint stays clean on release variant
# * verifyProverAssets succeeds (ADR-0010's SHA-256 gate)
#
# Three jobs:
# 1. build — JVM-only: compile, unit tests, lint, debug APK
# 2. instrumented — emulator boot + MainActivitySmokeTest (API 30, x86_64)
# 3. release — signed AAB + APK on tag pushes or workflow_dispatch
# with `release=true`. Reads the keystore from a
# base64-encoded GH secret. See android/RELEASE.md.
#
# Play Console internal-track upload is intentionally still manual for
# the W3 demo — the operator downloads the signed AAB from the workflow
# artifacts and uploads through the console. A future iteration wires
# r0adkll/upload-google-play once the service-account JSON is filed.
on:
push:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/android.yml'
tags:
- 'android-v*' # signed-release trigger
pull_request:
paths:
- 'android/**'
- '.github/workflows/android.yml'
workflow_dispatch:
inputs:
release:
description: 'Also build the signed release AAB/APK'
required: false
default: 'false'
type: choice
options: ['false', 'true']
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build + lint + unit tests
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: android
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
# sdkmanager wants packages as a single space-separated string,
# NOT a YAML multi-line block (the block joins lines with \n,
# which sdkmanager parses as one giant package name and dies).
# AGP only ever compiles against compileSdk = 34; the API 30
# platform install is for Robolectric's runtime classpath.
# build-tools 34.0.0 covers both AGP 8.5 + Robolectric needs.
packages: 'platforms;android-34 platforms;android-30 build-tools;34.0.0 platform-tools'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
# AGP 8.5 pins Gradle 8.7 (see gradle-wrapper.properties).
gradle-version: '8.7'
# Aggressively cache Gradle artifacts. The dependency graph is
# large (Compose BOM + Camera + ML Kit + snarkjs prover assets)
# but stable PR-over-PR.
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Bootstrap Gradle wrapper
# The wrapper jar isn't committed (yet — TODO bring it in-tree once
# we've done a real `gradle wrapper --gradle-version 8.7` locally).
# The `gradle` binary installed by setup-gradle@v4 generates a
# fresh wrapper that matches the gradle-wrapper.properties version
# pin. Idempotent.
run: gradle wrapper --gradle-version 8.7 --distribution-type bin
- name: Show toolchain
run: |
java -version
./gradlew --version
echo "ANDROID_HOME=$ANDROID_HOME"
- name: Verify prover-asset integrity (ADR-0010)
# Hash-gate ahead of the build so a tampered asset never even
# reaches kotlinc. Mirrors the preBuild hook but runs explicitly
# so the CI log carries a "PASS: prover assets pinned" record.
run: ./gradlew :app:verifyProverAssets --info
- name: Compile (debug variant)
run: ./gradlew :app:assembleDebug --stacktrace
- name: Unit tests
run: ./gradlew :app:testDebugUnitTest --stacktrace
- name: Lint (release variant)
# Release lint is stricter than debug; catches things like
# missing-required permissions, leaking PendingIntents, and
# unscoped exported components.
run: ./gradlew :app:lintRelease --stacktrace
continue-on-error: true # W3 deliberately doesn't error on
# lint findings yet — we want the
# baseline report. Flip to false in W4.
- name: Upload lint report
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-report
path: android/app/build/reports/lint-results-release.html
retention-days: 14
if-no-files-found: ignore
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: android/app/build/reports/tests/
retention-days: 14
if-no-files-found: ignore
- name: Upload debug APK
if: success()
uses: actions/upload-artifact@v4
with:
name: zeroauth-android-debug-apk
path: android/app/build/outputs/apk/debug/*.apk
retention-days: 14
if-no-files-found: error
instrumented:
name: Instrumented smoke (API 30 emulator)
runs-on: ubuntu-latest
timeout-minutes: 45
needs: build
# The emulator job is flaky-by-construction: x86_64 system images on
# GitHub-hosted runners go through software acceleration (no KVM in
# the kernel boundary), so boot can take 3-8 minutes and the next
# framework crash is one Android image roll away. Don't gate PRs on
# it — surface the failure as a status check the human can read.
continue-on-error: true
defaults:
run:
working-directory: android
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Enable KVM
# The runner image has /dev/kvm available; the udev rule below
# makes it world-rw so the emulator can claim it.
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: Set up Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'platforms;android-34 platforms;android-30 build-tools;34.0.0 platform-tools'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.7'
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Bootstrap Gradle wrapper
run: gradle wrapper --gradle-version 8.7 --distribution-type bin
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
# Tie the cache to the AVD knobs below — bumping any of them
# invalidates the cache so we don't boot stale snapshots.
key: avd-api30-x86_64-aosp-atd
- name: Create AVD + warm boot snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
arch: x86_64
target: aosp_atd # automated-test-device image — smaller, faster
profile: pixel_6
ram-size: 2048M
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: echo "snapshot warmed"
- name: Run instrumented smoke test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
arch: x86_64
target: aosp_atd
profile: pixel_6
ram-size: 2048M
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
working-directory: android
script: ./gradlew :app:connectedDebugAndroidTest --stacktrace
- name: Upload instrumented test report
if: always()
uses: actions/upload-artifact@v4
with:
name: instrumented-test-reports
path: android/app/build/reports/androidTests/
retention-days: 14
if-no-files-found: ignore
release:
name: Signed release (AAB + APK)
runs-on: ubuntu-latest
timeout-minutes: 30
needs: build
if: |
startsWith(github.ref, 'refs/tags/android-v')
|| (github.event_name == 'workflow_dispatch' && inputs.release == 'true')
defaults:
run:
working-directory: android
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'platforms;android-34 platforms;android-30 build-tools;34.0.0 platform-tools'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.7'
- name: Bootstrap Gradle wrapper
run: gradle wrapper --gradle-version 8.7 --distribution-type bin
- name: Restore release keystore
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
run: |
if [ -z "${KEYSTORE_BASE64}" ]; then
echo "::error::ANDROID_RELEASE_KEYSTORE_BASE64 secret is not set. See android/RELEASE.md."
exit 1
fi
# Decode into a tmp file outside the working tree so a stray
# `git add .` can't commit it.
mkdir -p "${RUNNER_TEMP}/signing"
echo "${KEYSTORE_BASE64}" | base64 -d > "${RUNNER_TEMP}/signing/release.jks"
chmod 600 "${RUNNER_TEMP}/signing/release.jks"
echo "ZEROAUTH_RELEASE_KEYSTORE=${RUNNER_TEMP}/signing/release.jks" >> "$GITHUB_ENV"
- name: Bundle release (AAB)
env:
ZEROAUTH_RELEASE_KEYSTORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_PASSWORD }}
ZEROAUTH_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
ZEROAUTH_RELEASE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
run: ./gradlew :app:bundleRelease --stacktrace
- name: Assemble release (APK fallback for direct sideload)
env:
ZEROAUTH_RELEASE_KEYSTORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_PASSWORD }}
ZEROAUTH_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
ZEROAUTH_RELEASE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
run: ./gradlew :app:assembleRelease --stacktrace
- name: Wipe keystore from runner
if: always()
run: rm -f "${RUNNER_TEMP}/signing/release.jks"
- name: Upload signed AAB
uses: actions/upload-artifact@v4
with:
name: zeroauth-android-release-aab
path: android/app/build/outputs/bundle/release/*.aab
retention-days: 90
if-no-files-found: error
- name: Upload signed APK
uses: actions/upload-artifact@v4
with:
name: zeroauth-android-release-apk
path: android/app/build/outputs/apk/release/*.apk
retention-days: 90
if-no-files-found: error