Skip to content

Commit 47940d4

Browse files
Claudeclaude
andcommitted
Add a build-and-test workflow
This repo had no CI at all. Every push and pull request against main now builds the app and runs its JVM unit tests. Two jobs, because they fail for different reasons. sdk-aar builds URnetworkSdk.aar with gomobile. It is not optional: the app has no source of its own for the SDK -- app/app/build.gradle's only dependency on it is a fileTree over ${bringyourHomeDir}/sdk/build/android, nothing is published to download, and over a hundred files import com.bringyour.sdk -- so without the .aar nothing compiles, including the main sources the unit tests pull in. It checks out sdk beside connect, glog and goidenticons (the `replace ../` directives in sdk/go.mod and sdk/build/go.mod require exactly those directory names), carries the same conditional goidenticons RenderPngV2 shim as urnetwork/windows, installs the pinned NDK, and runs `make init_tools build_android` -- init_tools rather than init because init ends in `go clean -cache && go clean -modcache`, which would discard everything setup-go had just restored. Go is pinned via go-version-file: sdk/build/go.mod (1.26.5) rather than the house-usual `stable`: stable is 1.27, and sdk/build/Makefile states that gomobile must be built with go <= 1.26 or the runtime fatals on the GODEBUG its own recipe exports. gradle consumes that .aar and runs :app:testGithubDebugUnitTest -- 19 test classes under app/app/src/test that the release pipeline never executes -- plus :app:assembleGithubDebug to prove the app still packages, plus compile*ReleaseKotlin for all four flavors, which is the only thing that compiles each flavor's own java srcDir. It writes app/local.properties first, because versionName/versionCode are read from it at CONFIGURATION time and otherwise the build shells out to a warpctl that does not exist on a runner and dies before any task starts. Build and test only. No release: assemble*Release resolves a keystore under ${WARP_HOME}/release that is not in this repo, and the instrumented suite needs an emulator and a live account fixture from the vault. Neither is attempted and no secret is referenced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAXFxG1EK4jTxQ1iW73BUr
1 parent adeb792 commit 47940d4

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)