From 2884ddd3c78231cb3273d08ea721d75fb7184fd8 Mon Sep 17 00:00:00 2001 From: vjrj Date: Mon, 20 Jul 2026 22:37:42 +0200 Subject: [PATCH] ci(release): split fdroid_reference into a 3-way ABI matrix to fit the runner's max job time --- .forgejo/workflows/release.yml | 89 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index cad685a..953dd7a 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -1,7 +1,7 @@ # Release automation for git.comunes.org (Forgejo Actions). # # Pushing a tag `v*` runs two independent jobs: -# play - build the signed AAB and ship it to Google Play (internal). +# play - build the signed AAB and ship it to Google Play (production, 100%). # fdroid_reference- build the per-ABI reference APKs the SAME way F-Droid will # (fdroid build inside the fdroidserver buildserver image), # sign them with the tane-upload key, and attach them to the @@ -18,8 +18,10 @@ # SUPPLY_JSON_KEY_DATA Google Play service-account JSON # # NOTE: `runs-on` must match a label your Forgejo runner registered with. -# The fdroid_reference job pulls a large image (~2 GB) and builds tesseract from -# source, so it is slow (~20-40 min); the play job is independent and unaffected. +# fdroid_reference runs as a 3-way ABI matrix (not one job building all 3 +# sequentially): each ABI needs its own NDK/tesseract-native/flutter compile +# from a cold cache, which triggered the runner's max-job-runtime kill when +# done back-to-back in a single job. One ABI per job fits comfortably. name: release @@ -85,7 +87,7 @@ jobs: gem install bundler --no-document bundle install - - name: Publish to Google Play (internal track) + - name: Publish to Google Play (production track, 100%) working-directory: apps/app_seeds env: SUPPLY_JSON_KEY_DATA: ${{ secrets.SUPPLY_JSON_KEY_DATA }} @@ -97,17 +99,29 @@ jobs: run: rm -f android/key.properties "$GITHUB_WORKSPACE/tane-upload.jks" fdroid_reference: + strategy: + matrix: + abi: [armeabi-v7a, arm64-v8a, x86_64] + include: + - abi: armeabi-v7a + offset: 1 + - abi: arm64-v8a + offset: 2 + - abi: x86_64 + offset: 3 runs-on: docker container: image: registry.gitlab.com/fdroid/fdroidserver:buildserver-trixie steps: - - name: Build reference APKs with fdroid, sign, and attach to the release + - name: Build the ${{ matrix.abi }} reference APK with fdroid, sign, and attach it env: TOKEN: ${{ github.token }} TANE_KEYSTORE_BASE64: ${{ secrets.TANE_KEYSTORE_BASE64 }} TANE_KEYSTORE_PASSWORD: ${{ secrets.TANE_KEYSTORE_PASSWORD }} TANE_KEY_ALIAS: ${{ secrets.TANE_KEY_ALIAS }} TANE_KEY_PASSWORD: ${{ secrets.TANE_KEY_PASSWORD }} + ABI: ${{ matrix.abi }} + OFFSET: ${{ matrix.offset }} run: | set -eu @@ -129,7 +143,7 @@ jobs: git config --global url."http://x-access-token:${TOKEN}@forgejo:3000/".insteadOf "https://git.comunes.org/" # 3) Fetch our in-repo recipe (the single source of truth) at this commit, - # plus the pubspec versionCode base for the per-ABI codes. + # plus the pubspec versionCode base for this ABI's versionCode. work=/tmp/tane-src mkdir -p "$work" && cd "$work" git init -q . @@ -138,6 +152,7 @@ jobs: git checkout -q FETCH_HEAD base=$(sed -n -E 's/^version:.*\+([0-9]+)$/\1/p' apps/app_seeds/pubspec.yaml) [ -n "$base" ] + vc=$((base * 10 + OFFSET)) # 4) Assemble a minimal fdroiddata (config.yml + srclibs + our recipe) from the # in-repo files. No external host needed — the job container reaches @@ -163,59 +178,47 @@ jobs: git clone -q https://git.comunes.org/comunes/tane.git build/org.comunes.tane git -C build/org.comunes.tane checkout -q "${GITHUB_SHA}" - # 5) Build the three per-ABI splits with F-Droid's own toolchain. - fdroid build --verbose --no-tarball \ - "org.comunes.tane:$((base * 10 + 1))" \ - "org.comunes.tane:$((base * 10 + 2))" \ - "org.comunes.tane:$((base * 10 + 3))" + # 5) Build ONLY this ABI's split with F-Droid's own toolchain. Splitting + # per-ABI into its own job/matrix-entry (rather than all 3 sequentially in + # one job) is what keeps each build inside the runner's max-job-runtime — + # each needs a full cold NDK+tesseract-native+flutter compile. + fdroid build --verbose --no-tarball "org.comunes.tane:${vc}" - # `fdroid build` exits 0 even when builds fail — demand the 3 APKs. - apk_count=$(ls unsigned/org.comunes.tane_*.apk 2>/dev/null | wc -l) - if [ "$apk_count" -ne 3 ]; then - echo "expected 3 unsigned APKs, got $apk_count" >&2 + # `fdroid build` exits 0 even when builds fail — demand the APK. + f="unsigned/org.comunes.tane_${vc}.apk" + if [ ! -f "$f" ]; then + echo "expected $f, not found" >&2 tail -n 80 logs/*.log 2>/dev/null || true exit 1 fi - # 6) Sign each unsigned APK with tane-upload and attach to the Forgejo release. + # 6) Sign the unsigned APK with tane-upload and attach it to the Forgejo release. apksigner=$(find "$ANDROID_HOME" -name apksigner -type f 2>/dev/null | sort -V | tail -1) if [ -z "$apksigner" ]; then yes | sdkmanager "build-tools;34.0.0" >/dev/null 2>&1 || true apksigner=$(find "$ANDROID_HOME" -name apksigner -type f 2>/dev/null | sort -V | tail -1) fi echo "$TANE_KEYSTORE_BASE64" | base64 -d > /tmp/ks.jks + out="/tmp/app-${ABI}-release.apk" + "$apksigner" sign --ks /tmp/ks.jks --ks-key-alias "$TANE_KEY_ALIAS" \ + --ks-pass "pass:$TANE_KEYSTORE_PASSWORD" --key-pass "pass:$TANE_KEY_PASSWORD" \ + --out "$out" "$f" + # Prove the signer cert matches AllowedAPKSigningKeys. + "$apksigner" verify --print-certs "$out" | grep -i 'SHA-256' || true + rm -f /tmp/ks.jks + api="http://forgejo:3000/api/v1/repos/${GITHUB_REPOSITORY}" tag="${GITHUB_REF_NAME}" - is_tag=false; [ "${GITHUB_REF_TYPE:-}" = tag ] && is_tag=true - rid="" - if $is_tag; then + if [ "${GITHUB_REF_TYPE:-}" = tag ]; then curl -sf -X POST "$api/releases" \ -H "Authorization: token ${TOKEN}" -H 'Content-Type: application/json' \ -d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\"}" >/dev/null || true rid=$(curl -sf -H "Authorization: token ${TOKEN}" "$api/releases/tags/${tag}" \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') + curl -sf -X POST "$api/releases/${rid}/assets?name=app-${ABI}-release.apk" \ + -H "Authorization: token ${TOKEN}" \ + -F "attachment=@${out};type=application/vnd.android.package-archive" + echo "uploaded app-${ABI}-release.apk (from ${f##*/})" + else + echo "built+signed app-${ABI}-release.apk (dispatch: upload skipped)" fi - for f in unsigned/org.comunes.tane_*.apk; do - vc=$(echo "$f" | sed -E 's/.*_([0-9]+)\.apk/\1/') - case $((vc % 10)) in - 1) abi=armeabi-v7a ;; - 2) abi=arm64-v8a ;; - 3) abi=x86_64 ;; - *) echo "unexpected versionCode $vc"; exit 1 ;; - esac - out="/tmp/app-${abi}-release.apk" - "$apksigner" sign --ks /tmp/ks.jks --ks-key-alias "$TANE_KEY_ALIAS" \ - --ks-pass "pass:$TANE_KEYSTORE_PASSWORD" --key-pass "pass:$TANE_KEY_PASSWORD" \ - --out "$out" "$f" - # Prove the signer cert matches AllowedAPKSigningKeys. - "$apksigner" verify --print-certs "$out" | grep -i 'SHA-256' || true - if $is_tag; then - curl -sf -X POST "$api/releases/${rid}/assets?name=app-${abi}-release.apk" \ - -H "Authorization: token ${TOKEN}" \ - -F "attachment=@${out};type=application/vnd.android.package-archive" - echo "uploaded app-${abi}-release.apk (from ${f##*/})" - else - echo "built+signed app-${abi}-release.apk (dispatch: upload skipped)" - fi - done - rm -f /tmp/ks.jks