Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 6s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Skipped
Renovate / renovate (push) Successful in 1m30s
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Skipped
### What this changes Adds beta releases, ported from Agendula (#38 and #40 there). Pushing a `release/*` branch whose `versionName` is `X.Y.Z-beta.N` runs the new `.gitea/workflows/beta.yaml`: unit tests, build + sign with the app key, then a **Codeberg pre-release** (APK + `.sha256`) and a Gitea pre-release (R8 mapping). F-Droid (self-hosted and official) and Play never get a beta; Obtainium only offers it with *Include prereleases* on. - **New versionCode scheme**, derived in one place by `scripts/version_info.sh`. 2.22.3 is the last legacy version (`X*10000 + Y*100 + Z`); from **2.22.4** on it is `X*1000000 + Y*10000 + Z*100 + N` for a beta (N = 1–98) and `+ 99` for stable, so `2.22.4` → `2220499`, `2.23.0-beta.1` → `2230001`. - **`scripts/release_gate.sh`** decides in both `detect` jobs whether the version still needs publishing. Tags are read by exact name via `git ls-remote`: Codeberg's `git/refs/tags/<name>` matches by prefix, so a beta tag would otherwise hide its stable release. A beta counts as done only once its Codeberg pre-release carries the APK (a failed publish is redone by the next push) and must be newer than the latest stable. - **Shared scripts** `publish_codeberg_release.sh`, `publish_gitea_release.sh`, `release_notes.sh`, `write_keystore.sh`, and a local composite action `.gitea/actions/android-env` for the toolchain setup, used by both `release.yaml` and `beta.yaml`. The stable path behaves as before (Codeberg step stays best-effort). - **Guards:** CI fails a PR whose `versionCode` doesn't match its `versionName`, or that brings a beta into `main`; `release.yaml` refuses a beta as a backstop; betas get no store What's New (`sync_changelog_to_fastlane.sh`, `check_changelog_lengths.sh`). - **Docs:** versionCode table and "Cutting a beta" in `docs/RELEASING.md`, the Obtainium note in the README, `build.gradle.kts` comment. - `gradle/gradle-daemon-jvm.properties` now points at JetBrains' own JBR 21.0.11 downloads instead of foojay, which dropped JetBrains 21 from its index (the pinned ids return 400, so a clean runner can't provision the daemon JVM). ### Why To ship test builds of an upcoming version to opted-in testers before the stable release, without them reaching F-Droid or Play users. Infra-only, so this targets `main` directly; no version bump. When cutting 2.22.4, its What's New file is `changelogs/2220499.txt`. ### Checklist - [x] Targeting `main` (infra change, noted above) - [x] No `values-*/strings.xml` touched - [x] `CHANGELOG.md` not updated: release infrastructure, not a user-visible change - [x] No planning or design documents committed Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/369
432 lines
20 KiB
YAML
432 lines
20 KiB
YAML
name: Release — F-Droid repo + Gitea/Codeberg release + Play
|
|
|
|
# A release is cut by merging a release branch into main with a bumped
|
|
# versionName (see docs/RELEASING.md). This workflow reads that versionName and,
|
|
# if no matching tag exists yet, runs tests, builds + signs the APK, publishes
|
|
# it to the F-Droid repo, creates the vX.Y.Z tag + Gitea release, and mirrors
|
|
# that release to Codeberg with the signed APK + a SHA-256 checksum as a
|
|
# direct-download channel — the tag is an output of the pipeline, not its
|
|
# trigger. Ordinary merges (no version bump) fall through `detect` and do
|
|
# nothing. Betas (X.Y.Z-beta.N) never come through here: beta.yaml cuts them
|
|
# from release/* branches as Codeberg-only pre-releases, and `detect` refuses
|
|
# one that reaches main.
|
|
#
|
|
# A trailing `play` job then uploads the App Bundle to Google Play. It is last
|
|
# and separate because Play is the only channel that can reject a good build for
|
|
# reasons the pipeline can't see, and that must not endanger a release which has
|
|
# already shipped to F-Droid and Codeberg. It skips cleanly until the
|
|
# PLAY_SERVICE_ACCOUNT_JSON secret exists.
|
|
#
|
|
# A manual workflow_dispatch (from a branch) runs the re-sign-only recovery
|
|
# path: it re-signs the existing F-Droid index with the repo key and re-uploads,
|
|
# without building an APK or creating a release. Used for key rotation / repo
|
|
# recovery.
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# Cheap gate: resolve the version from the committed build.gradle and decide
|
|
# whether this push actually cuts a new release (no tag for it yet). Keeps the
|
|
# heavy job from running on every merge to main.
|
|
detect:
|
|
# Gitea only. The workflow directory split already keeps this file invisible
|
|
# to Codeberg — Forgejo's lookup is first-match-wins, and .forgejo/workflows
|
|
# exists — but that only holds while .forgejo/ is non-empty. Move the last
|
|
# file out of it and Codeberg would fall back to .gitea/workflows and start
|
|
# running the release pipeline on the contributor-facing runner, with no
|
|
# secrets. repository_owner differs between the two forges regardless of
|
|
# URL, proxy or instance rename, so this closes it permanently.
|
|
if: github.repository_owner == 'makiolaj'
|
|
runs-on: docker
|
|
outputs:
|
|
is_release: ${{ steps.v.outputs.is_release }}
|
|
version: ${{ steps.v.outputs.version }}
|
|
version_code: ${{ steps.v.outputs.version_code }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Resolve version and whether it is a new release
|
|
id: v
|
|
run: |
|
|
set -e
|
|
INFO=$(bash scripts/version_info.sh)
|
|
echo "$INFO"
|
|
echo "$INFO" >> "$GITHUB_OUTPUT"
|
|
VERSION=$(echo "$INFO" | sed -n 's/^version=//p')
|
|
CHANNEL=$(echo "$INFO" | sed -n 's/^channel=//p')
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
echo "Manual dispatch — re-sign path, not a release."
|
|
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# Backstop for CI's guard: betas ship from release/* via beta.yaml.
|
|
if [ "$CHANNEL" != "stable" ]; then
|
|
echo "versionName $VERSION on main is a beta. Set the stable version before merging to main." >&2
|
|
exit 1
|
|
fi
|
|
# Tags are read from Codeberg, the canonical forge: a tag minted here is
|
|
# wiped by the next mirror sync until Codeberg has it. A lookup error is
|
|
# fatal, since guessing "no tag" would re-cut a shipped release.
|
|
GATE=$(bash scripts/release_gate.sh)
|
|
echo "is_release=${GATE#cut=}" >> "$GITHUB_OUTPUT"
|
|
|
|
# Releases: build + sign + publish, then mint the tag and Gitea release.
|
|
# Also runs on manual dispatch, where it skips the build and just re-signs and
|
|
# re-uploads the existing index (recovery path).
|
|
release:
|
|
needs: detect
|
|
if: needs.detect.outputs.is_release == 'true' || github.event_name == 'workflow_dispatch'
|
|
runs-on: docker
|
|
env:
|
|
ANDROID_HOME: /opt/android-sdk
|
|
ANDROID_SDK_ROOT: /opt/android-sdk
|
|
VERSION: ${{ needs.detect.outputs.version }}
|
|
VERSION_CODE: ${{ needs.detect.outputs.version_code }}
|
|
IS_RELEASE: ${{ needs.detect.outputs.is_release }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Android build environment
|
|
uses: ./.gitea/actions/android-env
|
|
|
|
# The committed versionName is the source of truth; pin the derived code.
|
|
- name: Pin versionCode to versionName
|
|
if: env.IS_RELEASE == 'true'
|
|
run: bash scripts/version_info.sh --pin
|
|
|
|
# Test the exact commit being shipped (only on a real release).
|
|
- name: Unit tests
|
|
if: env.IS_RELEASE == 'true'
|
|
run: ./gradlew testDebugUnitTest
|
|
|
|
- name: Setup Android Keystore
|
|
if: env.IS_RELEASE == 'true'
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
run: bash scripts/write_keystore.sh
|
|
|
|
- name: Build release APK
|
|
if: env.IS_RELEASE == 'true'
|
|
run: ./gradlew assembleRelease
|
|
|
|
- name: Setup F-Droid Server Tools
|
|
run: |
|
|
SUDO=""
|
|
if command -v sudo >/dev/null 2>&1; then SUDO="sudo"; fi
|
|
$SUDO apt-get update
|
|
$SUDO apt-get install -y sshpass python3-pip
|
|
pip3 install --break-system-packages --upgrade fdroidserver
|
|
|
|
- name: Fetch existing F-Droid repo from Hetzner
|
|
env:
|
|
HOST: ${{ secrets.HETZNER_HOST }}
|
|
USER: ${{ secrets.HETZNER_USER }}
|
|
PASS: ${{ secrets.HETZNER_PASS }}
|
|
run: |
|
|
set -euo pipefail
|
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
|
|
mkdir -p fdroid
|
|
# Pull only the published repo/ (all apps' APKs), any per-app
|
|
# metadata, and the repo icon — enough to rebuild the index without
|
|
# dropping the other apps. The signing key is deliberately NOT pulled
|
|
# from the box; it comes from CI secrets in the next step so it never
|
|
# has to live in the web-served tree.
|
|
sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/repo" fdroid/ 2>/dev/null || true
|
|
sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/metadata" fdroid/ 2>/dev/null || true
|
|
sshpass -p "$PASS" scp $SSH_OPTS "$USER@$HOST:dev/fdroid/icon.png" fdroid/ 2>/dev/null || true
|
|
mkdir -p fdroid/repo fdroid/metadata
|
|
|
|
- name: Restore F-Droid signing key and config from secrets
|
|
env:
|
|
FDROID_KEYSTORE_BASE64: ${{ secrets.FDROID_KEYSTORE_BASE64 }}
|
|
FDROID_CONFIG_BASE64: ${{ secrets.FDROID_CONFIG_BASE64 }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Fail loudly if the repo key is not configured. NEVER auto-generate
|
|
# one: a fresh key changes the repo fingerprint and breaks every
|
|
# user's pinned repo.
|
|
if [ -z "${FDROID_KEYSTORE_BASE64:-}" ] || [ -z "${FDROID_CONFIG_BASE64:-}" ]; then
|
|
echo "ERROR: FDROID_KEYSTORE_BASE64 / FDROID_CONFIG_BASE64 secrets are not set." >&2
|
|
echo "Refusing to continue — will not auto-generate a new repo key." >&2
|
|
exit 1
|
|
fi
|
|
echo "$FDROID_KEYSTORE_BASE64" | base64 --decode > fdroid/keystore.p12
|
|
echo "$FDROID_CONFIG_BASE64" | base64 --decode > fdroid/config.yml
|
|
test -s fdroid/keystore.p12
|
|
test -s fdroid/config.yml
|
|
mkdir -p fdroid/repo/icons
|
|
|
|
- name: Copy new APK to repo
|
|
if: env.IS_RELEASE == 'true'
|
|
run: |
|
|
set -e
|
|
mkdir -p fdroid/repo
|
|
cp app/build/outputs/apk/release/app-release.apk "fdroid/repo/calendula_v${VERSION}.apk"
|
|
|
|
# Per-version "What's New": ensure this version's changelog exists in the
|
|
# fastlane tree. The committed hand-written summary (kept under Play's
|
|
# 500-char cap) is used as-is; only if it is missing does the script fall
|
|
# back to CHANGELOG.md, so the self-hosted repo never depends on the
|
|
# commit having happened. The transform below then carries it across.
|
|
- name: Ensure this version's changelog is in the fastlane tree
|
|
if: env.IS_RELEASE == 'true'
|
|
run: bash scripts/sync_changelog_to_fastlane.sh
|
|
|
|
- name: Build F-Droid metadata from fastlane (single source of truth)
|
|
run: |
|
|
mkdir -p fdroid/metadata
|
|
# App-level control file (Categories/License/links) for the self-hosted
|
|
# repo's `fdroid update`.
|
|
cp fdroid-metadata/de.jeanlucmakiola.calendula.yml fdroid/metadata/
|
|
# Localized text + graphics + per-version changelogs come from the SAME
|
|
# fastlane tree the official F-Droid repo harvests from source,
|
|
# transformed into the F-Droid repo "localized" layout. One source of
|
|
# truth, both channels.
|
|
bash scripts/fastlane_to_fdroid_localized.sh \
|
|
fastlane/metadata/android \
|
|
fdroid/metadata/de.jeanlucmakiola.calendula
|
|
|
|
- name: Generate F-Droid Index
|
|
run: |
|
|
cd fdroid
|
|
fdroid update -c
|
|
|
|
- name: Upload repo/ to Hetzner
|
|
env:
|
|
HOST: ${{ secrets.HETZNER_HOST }}
|
|
USER: ${{ secrets.HETZNER_USER }}
|
|
PASS: ${{ secrets.HETZNER_PASS }}
|
|
run: |
|
|
set -euo pipefail
|
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
|
|
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
|
|
-mkdir dev
|
|
-mkdir dev/fdroid
|
|
SFTP
|
|
# Publish the signed repo/ plus metadata/ (descriptions, screenshots,
|
|
# per-version changelogs) so changelog history survives across
|
|
# releases. keystore.p12 and config.yml are NEVER uploaded.
|
|
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/repo fdroid/metadata "$USER@$HOST:dev/fdroid/"
|
|
|
|
# The APK is published and the index re-signed — now record the release.
|
|
# Creating it with target_commitish makes Gitea create the vX.Y.Z tag at
|
|
# this commit, so the tag only ever marks a fully-shipped release (and a
|
|
# failure before here leaves no tag, so re-running the workflow retries).
|
|
# Also attaches the R8 mapping, best-effort.
|
|
- name: Create tag + Gitea release
|
|
if: env.IS_RELEASE == 'true'
|
|
env:
|
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -e
|
|
bash scripts/release_notes.sh "$VERSION" > release-notes.md
|
|
TAG="v$VERSION" PRERELEASE=false NOTES_FILE=release-notes.md \
|
|
MAPPING=app/build/outputs/mapping/release/mapping.txt \
|
|
bash scripts/publish_gitea_release.sh
|
|
|
|
# Mirror the release to the Codeberg mirror as a direct-download channel
|
|
# for users who don't want F-Droid. Gitea already push-mirrors branches +
|
|
# tags to Codeberg, but releases aren't git objects so they don't sync —
|
|
# we create the release there over the API and attach the signed APK plus
|
|
# a SHA-256 checksum. The APK is identical to the F-Droid one (same app
|
|
# key), so this adds no trust surface. Best-effort: a Codeberg outage
|
|
# (it 504s under load) must never fail an already-published F-Droid
|
|
# release. Needs the CODEBERG_RELEASE_TOKEN secret; skips cleanly if unset.
|
|
- name: Publish release to Codeberg
|
|
if: env.IS_RELEASE == 'true'
|
|
continue-on-error: true
|
|
env:
|
|
TOKEN: ${{ secrets.CODEBERG_RELEASE_TOKEN }}
|
|
API: https://codeberg.org/api/v1/repos/jlmakiola/calendula
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -e
|
|
[ -s release-notes.md ] || bash scripts/release_notes.sh "$VERSION" > release-notes.md
|
|
TAG="v$VERSION" PRERELEASE=false NOTES_FILE=release-notes.md \
|
|
APK=app/build/outputs/apk/release/app-release.apk \
|
|
bash scripts/publish_codeberg_release.sh
|
|
|
|
# Play takes an App Bundle, not the APK, so it is a second artifact from
|
|
# the same source and the same signing config — not a repackage of the
|
|
# APK. The release key signs it, but Play only ever treats that key as the
|
|
# *upload* key: Play App Signing re-signs with Google's own key before
|
|
# delivery. A Play install and an F-Droid install therefore carry
|
|
# different signatures and cannot update each other. That divergence is a
|
|
# deliberate, documented choice (docs/RELEASING.md), not an accident.
|
|
#
|
|
# Built LAST and `continue-on-error`, both deliberately: everything above
|
|
# has already shipped by this point, and nothing Play-related may put that
|
|
# at risk. Sitting mid-job without continue-on-error, this block took the
|
|
# whole 2.17.0 release down with it — no F-Droid publish, no tag, no
|
|
# Codeberg mirror — over an artifact upload. A failure here now costs the
|
|
# Play upload and nothing else.
|
|
#
|
|
# Nothing here touches the F-Droid path: the AAB is never copied into the
|
|
# repo, never attached to a release, and its build cannot change the APK
|
|
# published above.
|
|
#
|
|
# AGP embeds the R8 mapping in the bundle's BUNDLE-METADATA, so Play gets
|
|
# deobfuscated stacktraces without a separate mapping upload.
|
|
- name: Build release AAB
|
|
if: env.IS_RELEASE == 'true'
|
|
continue-on-error: true
|
|
run: ./gradlew bundleRelease
|
|
|
|
# NOT actions/upload-artifact@v4: it runs @actions/artifact v2, which
|
|
# refuses to start whenever GITHUB_SERVER_URL is not github.com — it reads
|
|
# any other forge as an unsupported GHES instance and fails before it ever
|
|
# talks to the server (go-gitea/gitea#36024). Gitea 1.25 serves the v4
|
|
# artifact API fine; only the client-side check is wrong. This fork is that
|
|
# client with the check removed. Pinned to a commit, not the v4 branch: a
|
|
# third-party action in the signing pipeline must not change under us.
|
|
- name: Hand the AAB to the Play job
|
|
if: env.IS_RELEASE == 'true'
|
|
continue-on-error: true
|
|
uses: https://github.com/ChristopherHX/gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7 # v4
|
|
with:
|
|
name: release-aab-${{ needs.detect.outputs.version }}
|
|
path: app/build/outputs/bundle/release/app-release.aab
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
# Google Play channel.
|
|
#
|
|
# A separate job, on purpose, running only AFTER the F-Droid publish and both
|
|
# forge releases have completed. Play is the one channel that can reject a
|
|
# perfectly good build for reasons outside the pipeline (listing rules, policy
|
|
# review, API outage, a track that needs manual promotion). Isolating it means
|
|
# such a rejection surfaces as one red job next to a release that already
|
|
# shipped everywhere else, instead of failing the workflow that publishes it.
|
|
#
|
|
# Not a `container:` job even though a fastlane image exists: act_runner does
|
|
# not provide node inside custom job containers, so JavaScript actions
|
|
# (checkout, download-artifact) can't run there. The Renovate job gets away
|
|
# with a container because its only step is a shell command. Ruby is installed
|
|
# the same way sshpass, jq and fdroidserver are in the job above.
|
|
play:
|
|
needs: [detect, release]
|
|
# workflow_dispatch is the F-Droid re-sign recovery path — it must never
|
|
# touch Play, so gate on a real release only.
|
|
if: needs.detect.outputs.is_release == 'true'
|
|
runs-on: docker
|
|
env:
|
|
VERSION: ${{ needs.detect.outputs.version }}
|
|
VERSION_CODE: ${{ needs.detect.outputs.version_code }}
|
|
# Where the bundle lands. `production` — the release itself is already the
|
|
# gate (a bumped versionName only reaches main after on-device review), so
|
|
# a second manual promotion in the Play Console bought nothing but delay.
|
|
# Override with the PLAY_TRACK repo variable to stage a release instead.
|
|
PLAY_TRACK: ${{ vars.PLAY_TRACK || 'production' }}
|
|
PLAY_RELEASE_STATUS: ${{ vars.PLAY_RELEASE_STATUS || 'completed' }}
|
|
# Set PLAY_DRY_RUN=true to validate the edit against the API and discard
|
|
# it instead of committing — used to rehearse the first upload.
|
|
PLAY_DRY_RUN: ${{ vars.PLAY_DRY_RUN || 'false' }}
|
|
BUNDLE_PATH: vendor/bundle
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# Skip cleanly (not fatally) when Play isn't configured yet, so the rest
|
|
# of the release pipeline keeps working during setup — same contract as
|
|
# the Codeberg mirror step.
|
|
- name: Write the Play service-account key
|
|
id: key
|
|
env:
|
|
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "${PLAY_SERVICE_ACCOUNT_JSON:-}" ]; then
|
|
echo "PLAY_SERVICE_ACCOUNT_JSON not set — skipping the Play upload."
|
|
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > play-service-account.json
|
|
# Fail here, with a clear message, rather than inside fastlane: a
|
|
# mangled multi-line secret is the likeliest setup mistake.
|
|
python3 -c "import json,sys; d=json.load(open('play-service-account.json')); sys.exit(0 if d.get('type')=='service_account' else 1)" \
|
|
|| { echo "PLAY_SERVICE_ACCOUNT_JSON is not a valid service-account JSON." >&2; exit 1; }
|
|
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
|
|
# Same GHES-detection problem as the upload side, same fix — see the
|
|
# handoff step in the release job.
|
|
- name: Download the AAB
|
|
if: steps.key.outputs.configured == 'true'
|
|
uses: https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # v4
|
|
with:
|
|
name: release-aab-${{ needs.detect.outputs.version }}
|
|
path: dist
|
|
|
|
- name: Install Ruby
|
|
if: steps.key.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
SUDO=""
|
|
if command -v sudo >/dev/null 2>&1; then SUDO="sudo"; fi
|
|
$SUDO apt-get update
|
|
# ruby-dev + build-essential: several of fastlane's dependencies build
|
|
# native extensions.
|
|
$SUDO apt-get install -y ruby-full ruby-dev build-essential
|
|
ruby -v
|
|
|
|
# Only the first release pays the full gem build; afterwards this restores.
|
|
- name: Cache bundled gems
|
|
if: steps.key.outputs.configured == 'true'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: vendor/bundle
|
|
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-gems-
|
|
|
|
- name: Install fastlane
|
|
if: steps.key.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
gem install bundler --no-document
|
|
bundle config set --local path vendor/bundle
|
|
bundle install --jobs 4
|
|
bundle exec fastlane --version
|
|
|
|
- name: Upload to Play
|
|
if: steps.key.outputs.configured == 'true'
|
|
env:
|
|
SUPPLY_JSON_KEY: play-service-account.json
|
|
# supply is chatty on a TTY-less runner otherwise.
|
|
FASTLANE_SKIP_UPDATE_CHECK: '1'
|
|
FASTLANE_HIDE_CHANGELOG: '1'
|
|
run: |
|
|
set -euo pipefail
|
|
AAB="$GITHUB_WORKSPACE/dist/app-release.aab"
|
|
# Absolute, because a lane body runs from fastlane/, not the
|
|
# workspace root — a relative path resolves against the wrong
|
|
# directory there and 2.17.1 died on exactly that.
|
|
test -f "$AAB" || { echo "No AAB at $AAB — the artifact handoff failed." >&2; ls -la dist || true; exit 1; }
|
|
bundle exec fastlane deploy \
|
|
aab:"$AAB" \
|
|
track:"$PLAY_TRACK" \
|
|
release_status:"$PLAY_RELEASE_STATUS" \
|
|
dry_run:"$PLAY_DRY_RUN"
|
|
echo "Uploaded $VERSION (code $VERSION_CODE) to the '$PLAY_TRACK' track."
|
|
|
|
# The workspace is reused between runs on a self-hosted runner, so the
|
|
# credential must not outlive the job.
|
|
- name: Shred the service-account key
|
|
if: always()
|
|
run: shred -u play-service-account.json 2>/dev/null || rm -f play-service-account.json
|