|
| 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