ci: one definition of what ships decides both the version and whether to build
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 16s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m5s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m24s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m12s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 16s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m5s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m24s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m12s
Step 6 of M314. Two changes that only make sense together.
## The image tag set rule 145 mandates
dev push -> :dev
main push -> :latest + :<sha>
a v* tag -> nothing; the trigger is gone
`:<sha>` was going out on EVERY branch — a rollback target nobody has ever
pulled, accumulating forever, for a channel whose entire contract is that it
moves. It is on main only now, where rollback matters and where gated merges
(rule 2) make it dozens per year rather than one per push.
No version-shaped image tag in any lane. Verified the way rule 145 asks — by
looking for a CONSUMER, not for whether one is imaginable: `docker-compose.yml`
is parameterised for a pin and the docs describe the option, but no compose
file, deploy script or CI job reads one.
## Skip-if-exists, adapted, because §4 assumes a registry §5 removed
Note 3127 §4 says to ask the registry whether that exact version exists. There
is no `:<version>` tag to ask about any more. What there IS, for both clients,
is a channel that publishes the version it serves — and that answers the same
question: if the channel already serves what this source derives, the artifact
would be byte-identical.
So the `paths:` filters are gone from the desktop and Android lanes, replaced
by a `decide` job reading the real file set. That duplication is not
theoretical: `packaging/` was added to the sets and not to the filters, so the
commit that fixed a derivation bug never ran on the two lanes it fixed
(85ead4d). One definition, one reader.
The cost is that both workflows now start on every push rather than a matching
one — a ~15s container for a decision, against a lane that cannot silently fail
to run.
## The server always builds, deliberately
Its image is ~15 seconds against 6 and 9 minutes for the clients, so there is
little to save. And always building is strictly BETTER for something that can
face the internet: it picks up `python:3.12-slim` base updates on every push.
That also dissolves §4's base-image tension for this project rather than
deciding it — the artifact most exposed to base staleness is the one that never
skips. Resolving a base digest at derive time was the alternative and it is
forbidden: §7's corollary bars an external lookup, because two lanes would then
derive different values for one source.
## The guard runs on the skip path
It moved into `decide`, ahead of the decision. §6.3 is explicit that skipping
because "this version already exists" is indistinguishable from "we derived a
stale value that happens to match" unless something checks. It also now runs
once per lane instead of once per job.
## Two defects found while wiring this
`ci.yml`'s gate greps a path list that MUST match Android's file set, and
`packaging/` was missing from it. A packaging-only push would have had the
Android lane build and dispatch while the gate ALSO let the image through —
two images for one commit, and on main a second push of the same `:<sha>` with
different bytes. Rule 145's exact prohibition.
`guard-forward.sh` ends every fetch in `|| true`, so a runner image without
curl would have read as "nothing published yet" and passed without checking
anything. Missing curl is now fatal.
#3146
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,18 +19,9 @@ name: Android
|
||||
|
||||
on:
|
||||
push:
|
||||
# NO `paths:` FILTER — the `decide` job below reads the real file set instead.
|
||||
# See desktop.yml for why, and 85ead4d for what the duplication cost.
|
||||
branches: [dev, main]
|
||||
paths:
|
||||
- "android/**"
|
||||
# The Rust the .so is built from. A core change reaches the phone exactly
|
||||
# as it reaches the desktop, so this lane has to rebuild on it.
|
||||
- "core/**"
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
# The version deriver — see the note in desktop.yml. This lane did not run on
|
||||
# 85ead4d, which changed it.
|
||||
- "packaging/**"
|
||||
- ".forgejo/workflows/android.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -45,8 +36,46 @@ env:
|
||||
JAVA_TOOL_OPTIONS: "--enable-native-access=ALL-UNNAMED"
|
||||
|
||||
jobs:
|
||||
# Does the APK need rebuilding, or is the channel already serving this source?
|
||||
# See the equivalent job in desktop.yml — same reasoning, same replacement of a
|
||||
# hand-kept `paths:` filter with the one file set in `packaging/version.sh`.
|
||||
#
|
||||
# The guard runs here so it covers the skip path too (§6.3).
|
||||
#
|
||||
# NOTE THE COUPLING WITH ci.yml: when this lane builds, its last step dispatches
|
||||
# ci.yml so the image bakes in the APK just published. When it SKIPS, no dispatch
|
||||
# happens — and that is correct, because ci.yml's `gate` stands down only when the
|
||||
# push touched Android's files, which is the same condition that makes this build.
|
||||
# The two decisions agree because they read the same fact; they are still two
|
||||
# readers of it, which is why the gate's grep carries a comment pointing here.
|
||||
decide:
|
||||
name: Build, or is the channel already serving this?
|
||||
runs-on: python-ci
|
||||
container:
|
||||
image: git.fabledsword.com/bvandeusen/ci-python:3.14
|
||||
outputs:
|
||||
build: ${{ steps.d.outputs.build }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Decide
|
||||
id: d
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) channel=stable ;;
|
||||
*) channel=dev ;;
|
||||
esac
|
||||
sh packaging/guard-forward.sh android "$channel"
|
||||
echo "build=$(sh packaging/should-build.sh android "$channel")" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
name: Kotlin + Rust (APK)
|
||||
needs: [decide]
|
||||
if: needs.decide.outputs.build == 'true'
|
||||
# runs-on is only a scheduling label (Label Model B). flutter-ci is the
|
||||
# proven-working label that can pull our container images.
|
||||
runs-on: flutter-ci
|
||||
@@ -126,26 +155,6 @@ jobs:
|
||||
echo "apk=android/app/build/outputs/apk/debug/app-debug.apk" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# THE ONE CHECK THAT LOOKS AT REALITY (note 3127 §6.3). Everything else in this
|
||||
# lane derives a number and trusts it; this compares the derived value against
|
||||
# what the channel is actually serving, and fails the lane if it went DOWN.
|
||||
#
|
||||
# Placed before the build, not after: a bad derivation should cost seconds, not
|
||||
# a five-minute compile and a publish that has to be undone. Too-low is the
|
||||
# unrecoverable direction — every installed client reports "up to date" forever
|
||||
# and no later build fixes it until one climbs back above the bad number
|
||||
# (#2183, #2993).
|
||||
- name: Guard — the version must not go backwards
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
env:
|
||||
GITHUB_TOKEN: ${ github.token }
|
||||
run: |
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) channel=stable ;;
|
||||
*) channel=dev ;;
|
||||
esac
|
||||
sh ../packaging/guard-forward.sh android "$channel"
|
||||
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x ./gradlew
|
||||
|
||||
|
||||
Reference in New Issue
Block a user