Skip to content

Commit a2a911d

Browse files
authored
Merge pull request #470 from urnetwork/ci/build-and-test
Add a build-and-test workflow
2 parents adeb792 + 324d4c9 commit a2a911d

1 file changed

Lines changed: 300 additions & 0 deletions

File tree

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# Build and test the Android app on every push and pull request.
2+
#
3+
# THIS WORKFLOW DOES NOT RELEASE ANYTHING. No release keystore, no vault, no
4+
# store upload -- a gate, not a pipeline. See "what this deliberately skips"
5+
# at the bottom of this comment.
6+
#
7+
# Two jobs, because they fail for completely different reasons:
8+
#
9+
# sdk-aar The Go half. This repo has NO source of its own for the SDK; its
10+
# only dependency on it is
11+
# implementation fileTree(dir: "${bringyourHomeDir}/sdk/build/android",
12+
# include: ['*.aar', '*.jar'])
13+
# (app/app/build.gradle), and nothing is published to download. So
14+
# gomobile has to run here or NOTHING in the app compiles -- over a
15+
# hundred files import com.bringyour.sdk, including the sources the
16+
# unit tests pull in. This is the same build/all/android/setup.sh
17+
# does locally, and the same shape urnetwork/linux's build-sdk job
18+
# uses for the cgo variant.
19+
#
20+
# gradle The Kotlin half -- the JVM unit tests, the debug APK, and a
21+
# compile check of each shipping flavor's release variant.
22+
#
23+
# What this deliberately skips, and why:
24+
# - assemble*Release / bundle*Release. Every release signingConfig resolves
25+
# ${WARP_HOME}/release/android/signing/app.jks, which is not in this repo
26+
# and is a real secret. The release variants get compile*ReleaseKotlin
27+
# instead, which never schedules a packaging or validateSigning task.
28+
# - The instrumented suite (app/app/src/androidTest, driven by test-main.sh).
29+
# It needs an emulator AND a live account fixture from
30+
# vault/main/test-acceptance.yml.
31+
# - The localizations sync (build.sh's `npm run gen:android`). The generated
32+
# res/values*/strings.xml are committed, so CI does not need the sibling
33+
# store -- it just will not catch key drift, which the release pipeline
34+
# regenerates and would.
35+
name: Build and test
36+
37+
on:
38+
push:
39+
branches: [main]
40+
pull_request:
41+
branches: [main]
42+
workflow_dispatch:
43+
44+
permissions:
45+
contents: read
46+
47+
# This workflow is expensive (the gomobile bind dominates), so a busy branch
48+
# keeps one run at a time.
49+
concurrency:
50+
group: ${{ github.workflow }}-${{ github.ref }}
51+
cancel-in-progress: true
52+
53+
env:
54+
# app/app/build.gradle pins `ndkVersion '29.0.14206865'`, and
55+
# build/all/android/setup.sh installs exactly this one. The runner image does
56+
# not carry it, and both jobs need it: the SDK build finds llvm-objcopy under
57+
# it, and AGP strips the .aar's native libraries with it.
58+
UR_ANDROID_NDK: "29.0.14206865"
59+
# compileSdk/targetSdk are 36 (app/app/build.gradle).
60+
UR_ANDROID_PLATFORM: "36"
61+
UR_ANDROID_BUILD_TOOLS: "36.0.0"
62+
# versionName/versionCode are read from app/local.properties, which is
63+
# git-ignored. Absent, the build shells out to warpctl and dies at
64+
# CONFIGURATION time. These values only name the archive; nothing ships.
65+
UR_CI_VERSION: "0.0.0-ci"
66+
UR_CI_VERSION_CODE: "1"
67+
68+
jobs:
69+
sdk-aar:
70+
name: SDK (gomobile .aar)
71+
runs-on: ubuntu-latest
72+
# Bounded well under GitHub's 6h default so a stalled network fetch fails in
73+
# minutes rather than burning the afternoon. Observed: ~3.5 min.
74+
timeout-minutes: 45
75+
env:
76+
# setup-go pins 1.26.5 below, but a `go` directive anywhere in the graph
77+
# could still make the toolchain silently self-upgrade to 1.27 and
78+
# re-break the bind. `local` turns that into a loud, obvious failure.
79+
GOTOOLCHAIN: local
80+
steps:
81+
# This repository is ~700 MB, most of it design art under res/ that the
82+
# gradle build never reads. checkout's DEFAULT fetch-depth of 1 is what
83+
# keeps that off the runner -- do not set fetch-depth: 0 in either job.
84+
- name: Check out the Android app
85+
uses: actions/checkout@v4
86+
with:
87+
path: android
88+
89+
- name: Check out the SDK
90+
uses: actions/checkout@v4
91+
with:
92+
repository: urnetwork/sdk
93+
ref: main
94+
path: sdk
95+
96+
# sdk/build/go.mod resolves these with `replace ... => ../../<name>` (and
97+
# sdk/go.mod with `replace ... => ../<name>`), so they must sit as
98+
# siblings of the sdk checkout. The directory names must match the
99+
# replace targets exactly -- `connect`, not whatever a fork is named.
100+
# glog's default branch is master, so it is cloned without --branch.
101+
- name: Check out the sibling modules
102+
run: |
103+
git clone --depth 1 --branch main https://github.com/urnetwork/connect.git connect
104+
git clone --depth 1 https://github.com/urnetwork/glog.git glog
105+
git clone --depth 1 https://github.com/urnetwork/goidenticons.git goidenticons
106+
107+
# The sdk references RenderPngV2. goidenticons publishes it today, and an
108+
# unconditional shim is a REDECLARATION that fails the build. Write it
109+
# only when the symbol is genuinely absent, so this works against either
110+
# version of the dependency. Same guard as urnetwork/windows.
111+
- name: Shim goidenticons RenderPngV2 if unpublished
112+
run: |
113+
if grep -qR '^func RenderPngV2(' goidenticons/; then
114+
echo "goidenticons publishes RenderPngV2 -- no shim needed"
115+
else
116+
echo "goidenticons lacks RenderPngV2 -- shimming to RenderPng"
117+
echo 'package goidenticons' > goidenticons/render_v2_ci_shim.go
118+
echo 'func RenderPngV2(data []byte, size int) ([]byte, error) { return RenderPng(data, size) }' >> goidenticons/render_v2_ci_shim.go
119+
fi
120+
121+
# NOT `go-version: stable`, which the sdk/connect workflows use. Stable is
122+
# 1.27 now, and sdk/build/Makefile says so itself in the build_android
123+
# recipe: "gomobile/gobind must be built with go <= 1.26 -- the go 1.27
124+
# runtime fatals when this GODEBUG is set", GODEBUG=gotypesalias=0 being
125+
# exactly what that recipe exports. sdk/build/go.mod pins 1.26.5.
126+
- name: Set up Go
127+
uses: actions/setup-go@v5
128+
with:
129+
go-version-file: sdk/build/go.mod
130+
cache-dependency-path: sdk/build/go.sum
131+
132+
# gomobile bind generates and compiles the Java half of the binding, and
133+
# the build_android recipe then repacks the .aar with `jar cvf`. Both
134+
# need a JDK. Pin the same one the gradle job uses rather than inheriting
135+
# whatever the runner image defaults to.
136+
- name: Set up JDK 21
137+
uses: actions/setup-java@v4
138+
with:
139+
distribution: temurin
140+
java-version: "21"
141+
142+
# The recipe locates llvm-objcopy under ANDROID_NDK_HOME and `test -n`s
143+
# it, so it fails immediately without the NDK. Licenses are accepted
144+
# first, the same way build/all/android/setup.sh does it -- the runner
145+
# image ships them pre-accepted, but matching the repo removes a flake.
146+
- name: Install the pinned Android NDK
147+
run: |
148+
sdkmanager="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
149+
yes | timeout 300 "$sdkmanager" --licenses >/dev/null || true
150+
"$sdkmanager" --install "ndk;$UR_ANDROID_NDK"
151+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$UR_ANDROID_NDK" >> "$GITHUB_ENV"
152+
153+
# init_tools, NOT init. `make init` ends with `go clean -cache && go clean
154+
# -modcache`, which throws away everything setup-go just restored, and it
155+
# `go get`s x/mobile/bind (already pinned as an indirect require in
156+
# build/go.mod at the same GOMOBILE_VERSION). init_tools installs the
157+
# pinned gomobile/gobind/checksec and stops -- checksec is not optional,
158+
# build_android runs it over every .so. This is the split the Makefile
159+
# documents and the split :app:buildSdkAcceptance relies on.
160+
- name: Install the pinned gomobile toolchain
161+
working-directory: sdk/build
162+
run: make init_tools
163+
164+
- name: Build the Android SDK .aar
165+
working-directory: sdk/build
166+
env:
167+
WARP_VERSION: ${{ env.UR_CI_VERSION }}
168+
run: make build_android
169+
170+
# build_android already gates on gobind's "// skipped" output, so a
171+
# binding that silently stopped exporting a type fails inside make.
172+
# Assert the artifacts anyway -- they are the contract with the next job.
173+
- name: Assert the .aar actually built
174+
run: |
175+
for f in sdk/build/android/URnetworkSdk.aar sdk/build/android/URnetworkSdk-sources.jar; do
176+
[ -s "$f" ] || { echo "::error::$f is missing or empty"; exit 1; }
177+
done
178+
ls -la sdk/build/android
179+
180+
- name: Upload the .aar
181+
uses: actions/upload-artifact@v4
182+
with:
183+
name: urnetwork-sdk-android
184+
path: sdk/build/android
185+
if-no-files-found: error
186+
187+
gradle:
188+
name: Unit tests + app build
189+
needs: sdk-aar
190+
runs-on: ubuntu-latest
191+
# Same reasoning as sdk-aar. Observed: ~7 min.
192+
timeout-minutes: 40
193+
env:
194+
# app/app/build.gradle reads the .aar from
195+
# "${bringyourHomeDir}/sdk/build/android", where bringyourHomeDir is
196+
# $BRINGYOUR_HOME or, unset, rootDir/../.. . Setting it explicitly is what
197+
# test-main.sh does too, and it keeps the contract from depending on how
198+
# deep the checkout happens to sit.
199+
BRINGYOUR_HOME: ${{ github.workspace }}
200+
steps:
201+
- name: Check out the Android app
202+
uses: actions/checkout@v4
203+
with:
204+
path: android
205+
206+
- name: Download the SDK .aar
207+
uses: actions/download-artifact@v4
208+
with:
209+
name: urnetwork-sdk-android
210+
path: sdk/build/android
211+
212+
# The project sets a Java 21 toolchain and JvmTarget.JVM_21, and
213+
# build/all/run.sh refuses to build unless `java -version` is 21.0.x.
214+
- name: Set up JDK 21
215+
uses: actions/setup-java@v4
216+
with:
217+
distribution: temurin
218+
java-version: "21"
219+
220+
- name: Set up Gradle
221+
uses: gradle/actions/setup-gradle@v4
222+
223+
# The .aar carries native libraries, so packaging the debug APK runs
224+
# AGP's strip step, which resolves the NDK named by `ndkVersion`. Install
225+
# it (and the pinned platform/build-tools) explicitly rather than letting
226+
# AGP silently download whatever it decides it wants mid-build.
227+
- name: Install the pinned Android SDK components
228+
run: |
229+
sdkmanager="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
230+
yes | timeout 300 "$sdkmanager" --licenses >/dev/null || true
231+
"$sdkmanager" --install \
232+
"ndk;$UR_ANDROID_NDK" \
233+
"platforms;android-$UR_ANDROID_PLATFORM" \
234+
"build-tools;$UR_ANDROID_BUILD_TOOLS"
235+
236+
# local.properties is git-ignored, and versionName/versionCode are read
237+
# from it AT CONFIGURATION TIME. Without it the build shells out to
238+
# warp/warpctl/build/<os>/<arch>/warpctl, which does not exist here, and
239+
# every task fails before it starts -- with an opaque IOException, not a
240+
# message naming the cause. BUNDLER_RPC_URL and WALLETCONNECT_PROJECT_ID
241+
# are deliberately left out: build.gradle defaults both to "" with a
242+
# warning, and neither is needed to compile or to run the unit tests.
243+
- name: Write the CI local.properties
244+
run: |
245+
printf 'warp.version=%s\nwarp.version_code=%s\n' \
246+
"$UR_CI_VERSION" "$UR_CI_VERSION_CODE" > android/app/local.properties
247+
cat android/app/local.properties
248+
249+
# The debug build type signs with signingConfigs.debug, whose storeFile is
250+
# the standard ~/.android/debug.keystore with the published
251+
# android/androiddebugkey constants. AGP normally creates it on demand;
252+
# doing it here makes the debug APK's one signing input explicit and
253+
# independent of that behaviour. It is not a secret and nothing signed
254+
# with it ships.
255+
- name: Generate the Android debug keystore
256+
run: |
257+
mkdir -p "$HOME/.android"
258+
if [ ! -f "$HOME/.android/debug.keystore" ]; then
259+
keytool -genkeypair -v \
260+
-keystore "$HOME/.android/debug.keystore" \
261+
-storepass android -alias androiddebugkey -keypass android \
262+
-keyalg RSA -keysize 2048 -validity 10000 \
263+
-dname "CN=Android Debug,O=Android,C=US"
264+
fi
265+
266+
# testGithubDebugUnitTest is the real JVM suite -- 19 test classes under
267+
# app/app/src/test/java/com/bringyour/network, on junit 4.13.2, that the
268+
# release pipeline never runs (build/all/run.sh only assembles and
269+
# bundles). Reaching them also drags in aapt2 resource processing,
270+
# manifest merging and kapt/Hilt, so those break here too.
271+
#
272+
# assembleGithubDebug is the only step that proves the app still
273+
# packages. `splits { abi }` with universalApk means it produces three
274+
# APKs, and it is the slowest step here -- it is the first thing to drop
275+
# if this job needs to get cheaper.
276+
#
277+
# The other three flavors get compile*ReleaseKotlin rather than an
278+
# assemble, for the signing reason at the top of this file. It is not a
279+
# token check: each flavor adds its own java srcDir (src/ungoogle,
280+
# src/google, src/solana_dapp, src/ethos_dapp), so this is the only thing
281+
# that compiles those sources at all. build.sh uses the same
282+
# compileGithubReleaseKotlin idiom for the flavor it cannot assemble.
283+
- name: Build and test
284+
working-directory: android/app
285+
run: |
286+
./gradlew --no-daemon \
287+
:app:testGithubDebugUnitTest \
288+
:app:assembleGithubDebug \
289+
:app:compileGithubReleaseKotlin \
290+
:app:compilePlayReleaseKotlin \
291+
:app:compileSolana_dappReleaseKotlin \
292+
:app:compileEthos_dappReleaseKotlin
293+
294+
- name: Upload the unit test report
295+
if: always()
296+
uses: actions/upload-artifact@v4
297+
with:
298+
name: unit-test-report
299+
path: android/app/app/build/reports/tests
300+
if-no-files-found: warn

0 commit comments

Comments
 (0)