ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 3m15s
android / android (push) Canceled after 4m30s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 4m51s
ci / bench (push) Canceled after 4m42s
ci / rust (push) Canceled after 5m2s
ci / rust-arm64 (push) Canceled after 5m3s
deb / build-publish (push) Canceled after 4m25s
deb / build-publish-client-arm64 (push) Canceled after 36s
deb / build-publish-host (push) Canceled after 1m45s
decky / build-publish (push) Canceled after 10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 3s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 2s
The bump itself came from Android Studio; this makes the rest of the repo agree with it. The toolchain line at the top of the root build file still claimed AGP 9.2.0 and Gradle 9.4.1, and both Android workflows named "AGP 9.2" as the reason they pin JDK 21 — the sort of comment that is trusted precisely because it looks maintained. The Gradle caches also needed telling: they cache the DISTRIBUTION under `~/.gradle/wrapper`, keyed on a hash of `*.gradle.kts` only. This bump moved both files so the key changed anyway, but a wrapper-only bump would have restored a key that can never contain the new distribution. The wrapper properties are in the key now. Verified with the workflows' own commands on the new toolchain: `:app:testDebugUnitTest -PskipRustBuild --stacktrace --rerun-tasks` (the screenshot job, 45 tasks from cold) and `:app:assembleDebug` including the cargo-ndk native build. AGP 9.3.1 resolves no build-tools newer than the 37.0.0 both jobs install, so their sdkmanager lines still cover it.
193 lines
11 KiB
YAML
193 lines
11 KiB
YAML
# Android client CI (Gitea Actions). Builds the Rust JNI core (clients/android/native) via
|
||
# cargo-ndk for all three shipping ABIs and assembles the debug APK (clients/android). Mirrors apple.yml
|
||
# but on a Linux runner — the NDK is cross-platform, so no self-hosted host is needed.
|
||
#
|
||
# Prereq: the runner needs ~6 GB free + internet (it pulls the Android SDK/NDK and the Gradle
|
||
# distribution in-job). If android-actions/setup-android is not mirrored on this Gitea instance,
|
||
# replace that step with a manual cmdline-tools download, or bake an `android-ci` image like
|
||
# ci/rust-ci.Dockerfile. Emulator instrumentation tests are deferred until a KVM-capable runner
|
||
# exists (they self-skip otherwise, like apple.yml's RemoteFirstLightTests).
|
||
name: android
|
||
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
|
||
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
|
||
# cancel each other). Keeps a busy push cadence from piling ~10 queued runs per commit onto the
|
||
# runner fleet. Gitea honors this for push triggers (PR triggers: see gitea#35933).
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
# Single project version: a `vX.Y.Z` tag is THE release (uploads to Play's `alpha` closed
|
||
# track for manual promotion + attaches the .aab/.apk to the unified Gitea Release). A main
|
||
# push is canary (Play `internal`).
|
||
tags: ['v*']
|
||
pull_request:
|
||
workflow_dispatch:
|
||
|
||
jobs:
|
||
android:
|
||
runs-on: ubuntu-24.04
|
||
timeout-minutes: 60
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: JDK 21 (AGP 9.3 runs on JDK 17–21, not the host default)
|
||
uses: actions/setup-java@v4
|
||
with:
|
||
distribution: temurin
|
||
java-version: "21"
|
||
|
||
- name: Rust toolchain + Android targets (self-healing on a fresh runner)
|
||
run: |
|
||
if ! command -v rustup >/dev/null && [ ! -x "$HOME/.cargo/bin/rustup" ]; then
|
||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||
| sh -s -- -y --no-modify-path --profile minimal
|
||
fi
|
||
RUSTUP="$(command -v rustup || echo "$HOME/.cargo/bin/rustup")"
|
||
dirname "$RUSTUP" >> "$GITHUB_PATH"
|
||
"$RUSTUP" target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
|
||
|
||
- name: Android SDK
|
||
# SHA-pinned: this workflow's release job carries the signing keystore + Play service-account
|
||
# secrets, so a moved tag on a third-party action could exfiltrate them. v3 = 9fc6c4e.
|
||
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
|
||
with:
|
||
# Only platform-tools — NOT the action's default legacy `tools`, whose dependency chain
|
||
# drags in the ~250 MB emulator nobody here runs (instrumentation tests are deferred).
|
||
# That download was the single flakiest piece of this job: the shared runner fleet drops
|
||
# packets under parallel-job load and sdkmanager's streamed unzip turns a truncated
|
||
# stream into "Error on ZipFile unknown archive" (observed 2026-07-22, twice).
|
||
packages: platform-tools
|
||
|
||
- name: NDK r30 + platform 36 + build-tools + CMake (libopus cross-build)
|
||
# cmake;3.22.1 installs cmake + ninja under $ANDROID_SDK/cmake/3.22.1/bin — the exact path
|
||
# kit/build.gradle.kts prepends to PATH for cargo-ndk's audiopus_sys (libopus) CMake build.
|
||
# Note: platforms;android-37 is sometimes missing from standard channels; AGP will
|
||
# auto-download it if needed during the build.
|
||
# retry.sh: sdkmanager is a single-shot multi-hundred-MB fetch, exactly the class the
|
||
# helper exists for (fleet-load packet drops truncate the stream mid-unzip); a failed
|
||
# attempt leaves no partial package behind, so a plain re-invoke is safe.
|
||
run: bash scripts/ci/retry.sh 4 sdkmanager "platform-tools" "platforms;android-36" "build-tools;37.0.0" "ndk;30.0.14904198" "cmake;3.22.1"
|
||
|
||
- name: Caches (cargo + gradle)
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry
|
||
~/.cargo/git
|
||
~/.gradle/caches
|
||
~/.gradle/wrapper
|
||
target
|
||
# gradle-wrapper.properties is in the key on purpose — see android-screenshots.yml.
|
||
key: android-${{ hashFiles('Cargo.lock', 'clients/android/**/*.gradle.kts', 'clients/android/gradle/wrapper/gradle-wrapper.properties') }}
|
||
restore-keys: android-
|
||
|
||
- name: cargo-ndk
|
||
run: command -v cargo-ndk >/dev/null || cargo install cargo-ndk
|
||
|
||
- name: assembleDebug (cargo-ndk → jniLibs → APK)
|
||
working-directory: clients/android
|
||
env:
|
||
VERSION_CODE: ${{ github.run_number }}
|
||
run: ./gradlew :app:assembleDebug --stacktrace
|
||
|
||
# Single source of the version name + the Play track for the release steps below. versionCode
|
||
# stays github.run_number (monotonic across both tracks; Play rejects a regressed code).
|
||
- name: Version + channel
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||
run: |
|
||
eval "$(bash scripts/ci/pf-version.sh)" # -> PF_BASE (one minor ahead of the latest stable tag)
|
||
case "$GITHUB_REF" in
|
||
refs/tags/v*) VN="${GITHUB_REF_NAME#v}"; TRACK="alpha" ;; # alpha = built-in closed testing
|
||
*) VN="${PF_BASE}-ci${GITHUB_RUN_NUMBER}"; TRACK="internal" ;;
|
||
esac
|
||
echo "VERSION_NAME=$VN" >> "$GITHUB_ENV"
|
||
echo "PLAY_TRACK=$TRACK" >> "$GITHUB_ENV"
|
||
echo "android version $VN -> Play track '$TRACK'"
|
||
|
||
- name: Build Release (signed AAB + universal APK)
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||
working-directory: clients/android
|
||
env:
|
||
VERSION_CODE: ${{ github.run_number }} # VERSION_NAME comes from the Version+channel step (GITHUB_ENV)
|
||
RELEASE_KEYSTORE_FILE: "../release.jks"
|
||
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
|
||
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
|
||
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
|
||
run: |
|
||
echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > release.jks
|
||
# AAB for Play; a universal APK (all ABIs) for direct sideload/testing — same upload key.
|
||
./gradlew :app:bundleRelease :app:assembleRelease --stacktrace
|
||
|
||
# Attach the built APK to the workflow run so it's a one-click sideload download straight from the
|
||
# run page (no need to know the generic-registry URL) — on EVERY trigger, incl. a PR or a
|
||
# workflow_dispatch. The debug APK is built on every run; the signed universal release APK exists
|
||
# only on a main/tag push (its build step above is push-gated), so grab whichever were produced.
|
||
- name: Attach APK(s) to the workflow run
|
||
if: always()
|
||
# v3, not v4: Gitea's artifact backend identifies as GHES, which upload-artifact@v4 refuses
|
||
# (same reason as apple.yml / *-screenshots.yml). Download is a zip of the matched APK(s).
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: punktfunk-android-apk
|
||
path: |
|
||
clients/android/app/build/outputs/apk/release/*.apk
|
||
clients/android/app/build/outputs/apk/debug/*.apk
|
||
if-no-files-found: warn
|
||
retention-days: 30
|
||
|
||
# Publish BEFORE the Play upload so artifacts land even while the Play step is still failing.
|
||
# Generic registry is public for reads — matches windows-msix.yml / deb.yml (REGISTRY_TOKEN, user enricobuehler).
|
||
# main = canary store + `canary/` sideload alias; a `vX.Y.Z` tag = `latest/` alias + attached
|
||
# to the unified Gitea Release.
|
||
- name: Publish to generic registry + attach to Gitea release
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||
env:
|
||
REGISTRY: git.unom.io
|
||
OWNER: unom
|
||
PKG: punktfunk-android
|
||
VERSION: ${{ github.run_number }}
|
||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||
run: |
|
||
AAB=clients/android/app/build/outputs/bundle/release/app-release.aab
|
||
APK=clients/android/app/build/outputs/apk/release/app-release.apk
|
||
base="https://$REGISTRY/api/packages/$OWNER/generic/$PKG"
|
||
# 1) immutable, run-number-versioned store (sideload + provenance)
|
||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$AAB" "$base/$VERSION/punktfunk-android-r$VERSION.aab"
|
||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$VERSION/punktfunk-android-r$VERSION.apk"
|
||
echo "published store version $VERSION (versionCode)"
|
||
# 2) channel alias for a predictable sideload URL: stable -> latest/, canary -> canary/
|
||
case "$GITHUB_REF" in refs/tags/v*) ALIAS=latest ;; *) ALIAS=canary ;; esac
|
||
curl -fsS -o /dev/null --user "enricobuehler:$REGISTRY_TOKEN" -X DELETE "$base/$ALIAS/punktfunk-android.apk" || true
|
||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$ALIAS/punktfunk-android.apk"
|
||
echo "sideload alias: $base/$ALIAS/punktfunk-android.apk"
|
||
# 3) on a real release, attach the .aab + .apk to the unified Gitea Release (X.Y.Z names)
|
||
case "$GITHUB_REF" in
|
||
refs/tags/v*)
|
||
. scripts/ci/gitea-release.sh
|
||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||
upsert_asset "$RID" "$AAB" "punktfunk-${VERSION_NAME}.aab"
|
||
upsert_asset "$RID" "$APK" "punktfunk-${VERSION_NAME}.apk"
|
||
;;
|
||
esac
|
||
|
||
# Direct Publishing-API upload instead of r0adkll/upload-google-play — that action hides the
|
||
# real API error behind "Unknown error occurred."; this prints it. stdlib + openssl only (no
|
||
# pip), reuses SERVICE_ACCOUNT_JSON (raw JSON or base64), auto-handles changesNotSentForReview.
|
||
# Track: canary main -> `internal`; a vX.Y.Z release -> `alpha` (closed testing) for manual
|
||
# promotion to production in the Play console.
|
||
- name: Upload to Google Play
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||
env:
|
||
SERVICE_ACCOUNT_JSON: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||
run: |
|
||
echo "uploading to Play track '$PLAY_TRACK'"
|
||
python3 clients/android/ci/play-upload.py \
|
||
--package io.unom.punktfunk \
|
||
--aab clients/android/app/build/outputs/bundle/release/app-release.aab \
|
||
--track "$PLAY_TRACK" --status completed
|