ci: the full release pipeline, Codeberg-canonical from commit one
Both siblings' pipelines are near-identical; the real difference between them is which forge is canonical. This is Agendula's spine — it pushes the tag to Codeberg itself and flags pre-1.0 releases as pre-releases — with Calendula's `play` job grafted on unchanged. Taking Agendula's spine means there is no Gitea-canonical phase to migrate out of later, which is the one thing Agendula had to unwind. The Codeberg publish step stays NOT continue-on-error, inherited that way deliberately: in Agendula it reported green through five consecutive releases while never once publishing, which is how a crash-fix release reached F-Droid but not the users who needed it. Comments that recount that history now name Agendula, so an inherited scar isn't misread as ours. The `play` job runs last and isolated, and skips cleanly until PLAY_SERVICE_ACCOUNT_JSON exists — so it stays dormant through the whole pre-1.0 run, which is the correct behaviour anyway. Templates, the contributing guide and verify-release.sh are rewritten for this app's domain rather than renamed: the architectural rule here is that Room types stay in the data layer, and the on-device release check is an alarm that survives a lock screen and a reboot, not a task list that loads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L94fydiJC37LtxVusNQBDy
This commit is contained in:
co-authored by
Claude Opus 5
parent
5ddec0f248
commit
97ed6cf6f5
@@ -0,0 +1,169 @@
|
||||
name: CI
|
||||
|
||||
# One gate per pull request. Branch pushes no longer trigger CI on their own,
|
||||
# so a change is built once on its PR (covering feature -> release/* and
|
||||
# release/* -> main) instead of once per push and again on the merge to main.
|
||||
# The merge itself is handled by release.yaml, which only does heavy work when
|
||||
# the merge actually cuts a release.
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
# Cancel superseded runs for the same PR.
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Single job named `ci` so the required "CI" status check is always reported,
|
||||
# even for docs-only PRs: those just skip the Android build and the job still
|
||||
# succeeds (fast green check) instead of being filtered out and leaving the
|
||||
# required check pending forever.
|
||||
ci:
|
||||
runs-on: docker
|
||||
env:
|
||||
ANDROID_HOME: /opt/android-sdk
|
||||
ANDROID_SDK_ROOT: /opt/android-sdk
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history so the base..HEAD diff below has a merge-base.
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
# Cheap, always-on guard: the release build must stay reproducible for the
|
||||
# official F-Droid repo (no AGP VCS-info embedding). Runs regardless of
|
||||
# change scope so a regression can't slip through on a "docs-only" PR.
|
||||
- name: Reproducible-release invariant
|
||||
run: bash scripts/check_reproducible_release.sh
|
||||
|
||||
# Decide whether anything that affects the app build changed. Docs, store
|
||||
# metadata, licence texts and forge housekeeping don't, so those PRs skip
|
||||
# the SDK + Gradle work below but still report a green `ci`.
|
||||
- name: Classify change scope
|
||||
id: scope
|
||||
env:
|
||||
# Deliberately a skip-list, not a build-list: a path nobody thought
|
||||
# about defaults to building. Only paths the Gradle build provably
|
||||
# never reads belong here — note that the workflows themselves, the
|
||||
# `.gitmodules` submodule pointer and `scripts/` are *not* in it.
|
||||
SKIP_RE: '(\.md$|^docs/|^fastlane/|^fdroid-metadata/|^licenses/|^\.(forgejo|gitea)/ISSUE_TEMPLATE/|^\.editorconfig$|^\.gitattributes$|^\.gitignore$|^LICENSE$)'
|
||||
run: |
|
||||
set -e
|
||||
BASE="${{ github.base_ref }}"
|
||||
# Normally the bare branch name; tolerate a full ref, which would
|
||||
# otherwise make the merge-base lookup fail and quietly degrade this
|
||||
# guard into "always build".
|
||||
BASE="${BASE#refs/heads/}"
|
||||
if [ -z "$BASE" ]; then
|
||||
echo "No base branch on this event — running the full build to be safe."
|
||||
echo "code=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
# Full (not --depth=1) base fetch so the merge-base is present even when
|
||||
# the PR branch forked several commits back; a shallow tip has no merge
|
||||
# base with a divergent branch and `git diff base...HEAD` aborts.
|
||||
git fetch --no-tags origin "$BASE"
|
||||
MB=$(git merge-base "origin/$BASE" HEAD 2>/dev/null || true)
|
||||
if [ -z "$MB" ]; then
|
||||
# No common ancestor available — don't risk skipping the build.
|
||||
echo "No merge base with origin/$BASE — running the full build to be safe."
|
||||
echo "code=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
CHANGED=$(git diff --name-only "$MB" HEAD)
|
||||
echo "Changed files:"; echo "$CHANGED"
|
||||
RELEVANT=$(echo "$CHANGED" | grep -vE "$SKIP_RE" || true)
|
||||
if [ -n "$RELEVANT" ]; then
|
||||
# Naming them makes "why did my docs PR build for four minutes?"
|
||||
# answerable from the log alone.
|
||||
echo "Build-relevant changes:"; echo "$RELEVANT"
|
||||
echo "code=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Docs/metadata-only change — skipping the Android build."
|
||||
echo "code=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Java
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
# Fully qualified on purpose. Codeberg resolves bare `uses:` refs against
|
||||
# data.forgejo.org, Forgejo's own action mirror — actions/checkout,
|
||||
# setup-java and cache all exist there, but android-actions/setup-android
|
||||
# does not, and the job dies with "repository not found". Gitea's instance
|
||||
# defaults to GitHub, which is why this never surfaced before the split.
|
||||
- name: Setup Android SDK
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
uses: https://github.com/android-actions/setup-android@v3
|
||||
with:
|
||||
# Default ("tools platform-tools") drags in the Android Emulator
|
||||
# (~300 MB) which the build never uses.
|
||||
packages: ''
|
||||
|
||||
- name: Setup Android SDK cache
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /opt/android-sdk
|
||||
key: ${{ runner.os }}-android-sdk-37-36.0.0
|
||||
|
||||
- name: Install Android SDK packages
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: |
|
||||
yes | sdkmanager --licenses >/dev/null || true
|
||||
sdkmanager \
|
||||
"platform-tools" \
|
||||
"platforms;android-37.0" \
|
||||
"build-tools;36.0.0"
|
||||
|
||||
- name: Setup Gradle cache
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: chmod +x ./gradlew
|
||||
|
||||
# No --no-daemon: the daemon lives only as long as this job container
|
||||
# and lets the following steps skip JVM startup + reconfiguration.
|
||||
- name: Lint (debug variant only)
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: ./gradlew lintDebug
|
||||
|
||||
- name: Unit tests
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: ./gradlew testDebugUnitTest
|
||||
|
||||
- name: Assemble debug APK
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: ./gradlew assembleDebug
|
||||
|
||||
- name: Trivy filesystem scan
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: |
|
||||
set -e
|
||||
SUDO=""
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y wget apt-transport-https gnupg lsb-release
|
||||
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | $SUDO tee /usr/share/keyrings/trivy.gpg > /dev/null
|
||||
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | $SUDO tee /etc/apt/sources.list.d/trivy.list
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y trivy
|
||||
fi
|
||||
trivy filesystem --severity HIGH,CRITICAL --exit-code 0 .
|
||||
continue-on-error: true
|
||||
Reference in New Issue
Block a user