# The release APK, on every push to master. # # This is the one job that needs the Android SDK, so it is also where the `:app` # unit tests run — `:core`'s suite is in `tests.yml`, where no SDK is needed # because `settings.gradle.kts` leaves `:app` out when there is none. # # The Glyph Matrix AAR is deliberately *not* fetched: its licence forbids # redistribution, and `app/build.gradle.kts` compiles the `noglyph` source set # without it. A CI APK therefore has an inert rear display, which is also exactly # right for any phone that is not a Nothing. name: android on: push: branches: [master] tags: ["v*"] env: ANDROID_HOME: /opt/android-sdk JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64 DEBIAN_FRONTEND: noninteractive jobs: apk: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install JDK 21 and the tools the SDK installer needs run: | apt-get update -qq apt-get install -y --no-install-recommends \ openjdk-21-jdk-headless curl unzip >/dev/null # Keyed on the pinned SDK versions, so this is a download once and a # restore every time after. - uses: actions/cache@v4 continue-on-error: true with: path: /opt/android-sdk key: android-sdk-${{ hashFiles('android/gradle/libs.versions.toml') }} - uses: actions/cache@v4 continue-on-error: true with: path: ~/.gradle/caches key: gradle-app-${{ hashFiles('android/gradle/libs.versions.toml', 'android/gradle/wrapper/gradle-wrapper.properties') }} - name: Install the Android SDK run: ./android/ci/install-sdk.sh - name: Unit tests (:app, on the JVM via Robolectric) working-directory: android run: ./gradlew --no-daemon :app:testDebugUnitTest - name: Unpack the signing key id: key env: KEYSTORE_BASE64: ${{ secrets.KADENCE_KEYSTORE_BASE64 }} run: | if [ -z "${KEYSTORE_BASE64:-}" ]; then echo "No KADENCE_KEYSTORE_BASE64 secret — nothing to sign with." >&2 echo "signed=false" >> "$GITHUB_OUTPUT" exit 0 fi mkdir -p "$RUNNER_TEMP" printf '%s' "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/kadence.keystore" echo "signed=true" >> "$GITHUB_OUTPUT" - name: Build the release APK working-directory: android env: # The run number, so successive APKs install over each other. It was # pinned at 1, which made every build look like the same version. KADENCE_VERSION_CODE: ${{ gitea.run_number }} KADENCE_KEYSTORE: ${{ runner.temp }}/kadence.keystore KADENCE_KEYSTORE_PASSWORD: ${{ secrets.KADENCE_KEYSTORE_PASSWORD }} KADENCE_KEY_ALIAS: ${{ secrets.KADENCE_KEY_ALIAS }} KADENCE_KEY_PASSWORD: ${{ secrets.KADENCE_KEY_PASSWORD }} # With a keystore, a missing or misnamed secret must fail the build # rather than silently produce a debug-signed "release". KADENCE_REQUIRE_SIGNING: ${{ steps.key.outputs.signed == 'true' && '1' || '0' }} run: ./gradlew --no-daemon :app:assembleRelease - name: Say which key actually signed it run: | APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner | sort | tail -1)" APK=android/app/build/outputs/apk/release/app-release.apk "$APKSIGNER" verify --print-certs "$APK" if [ "${{ steps.key.outputs.signed }}" = "true" ]; then # The debug key is unmistakable, and shipping it would be a trap: the # APK installs, then cannot be upgraded by a properly signed one. if "$APKSIGNER" verify --print-certs "$APK" | grep -q "CN=Android Debug"; then echo "::error::signed with the debug key despite a keystore being provided" exit 1 fi fi - uses: actions/upload-artifact@v4 with: name: kadence-release-apk path: android/app/build/outputs/apk/release/app-release.apk if-no-files-found: error - name: Attach it to the release, when this is a tag if: startsWith(github.ref, 'refs/tags/v') env: TOKEN: ${{ secrets.GITEA_TOKEN }} run: | VERSION="${GITHUB_REF#refs/tags/}" API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" ID="$(curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$VERSION" \ | sed -n 's/.*"id":[ ]*\([0-9]*\).*/\1/p' | head -1)" curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@android/app/build/outputs/apk/release/app-release.apk" \ "$API/releases/$ID/assets?name=kadence-$VERSION.apk"