Pin OTA releases to main and stop dev builds outranking them
Two ways the updater could have served or blocked the wrong build: - release.yml also triggers on workflow_dispatch, which can be aimed at any branch from the Actions UI. That would have built dev code and published it as a real Release, which every install would then offer to download. The job now refuses to run off anything but main. - versionCode came from GITHUB_RUN_NUMBER, but that counter is per workflow, so the dev pipeline had its own independent sequence. A debug APK installed from a CI artifact already carried a higher versionCode than the first several releases would, and the updater's check is latest <= installed, so those users would have been told they were up to date indefinitely. Only release.yml now sets PLG_RELEASE, and only that stamps a real version; everything else stays at 1.0.0-dev. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
This commit is contained in:
@@ -19,7 +19,16 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build APK and publish release
|
name: Build APK and publish release
|
||||||
|
# workflow_dispatch can be aimed at any branch from the Actions UI. Releases
|
||||||
|
# are what the in-app updater installs, so only main may produce one —
|
||||||
|
# anything else is a no-op rather than shipping unreviewed code to everyone.
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
# Tells the build this is a real release, so the APK is stamped with the
|
||||||
|
# run number that matches the tag below. Absent everywhere else, which
|
||||||
|
# keeps dev and local builds at 1.0.0-dev.
|
||||||
|
PLG_RELEASE: "1"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -72,11 +72,20 @@ Requires JDK 17 and the Android SDK (compileSdk 34).
|
|||||||
## CI
|
## CI
|
||||||
|
|
||||||
- `.github/workflows/ci.yml` — builds, tests and lints on `dev` and on pull
|
- `.github/workflows/ci.yml` — builds, tests and lints on `dev` and on pull
|
||||||
requests into `dev` or `main`.
|
requests into `dev` or `main`. Its APK artifact is a dev build, versioned
|
||||||
|
`1.0.0-dev`.
|
||||||
- `.github/workflows/release.yml` — on push to `main`, builds the APK and
|
- `.github/workflows/release.yml` — on push to `main`, builds the APK and
|
||||||
publishes it as a GitHub Release tagged `v1.0.<run-number>`. The in-app updater
|
publishes it as a GitHub Release tagged `v1.0.<run-number>`. The in-app updater
|
||||||
reads that tag, which is why the APK's `versionCode` is the same run number.
|
reads that tag, which is why the APK's `versionCode` is the same run number.
|
||||||
|
|
||||||
|
**OTA updates follow `main`, and only `main`.** The updater asks GitHub for the
|
||||||
|
latest published Release; the release workflow is the only thing that creates
|
||||||
|
one, and it refuses to run off any branch but `main` — so a manual dispatch
|
||||||
|
aimed at `dev` does nothing rather than shipping unreviewed code to every
|
||||||
|
install. Only that workflow sets `PLG_RELEASE`, which is what stamps a real
|
||||||
|
`versionCode`; dev builds stay at `1.0.0-dev` so a side-loaded CI artifact can
|
||||||
|
never outrank a genuine release and suppress future updates.
|
||||||
|
|
||||||
The debug keystore is committed on purpose: every build is signed with the same
|
The debug keystore is committed on purpose: every build is signed with the same
|
||||||
key, so an OTA update installs over the previous one instead of being rejected
|
key, so an OTA update installs over the previous one instead of being rejected
|
||||||
for a signature mismatch.
|
for a signature mismatch.
|
||||||
|
|||||||
+20
-6
@@ -4,10 +4,20 @@ plugins {
|
|||||||
id("org.jetbrains.kotlin.plugin.serialization")
|
id("org.jetbrains.kotlin.plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
// On CI, GITHUB_RUN_NUMBER is set automatically and matches the release tag
|
// Only the release workflow stamps a real version. It sets PLG_RELEASE, and its
|
||||||
// (v1.0.<run-number>), so the shipped APK knows its own version and the
|
// GITHUB_RUN_NUMBER is the same number as the release tag it publishes
|
||||||
// in-app updater can tell whether a newer release exists. Locally it's a dev build.
|
// (v1.0.<run-number>), so the shipped APK knows its own version and the in-app
|
||||||
val ciRunNumber = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 0
|
// updater can compare against the latest published release.
|
||||||
|
//
|
||||||
|
// Everything else — a local build, or the dev CI pipeline — stays at 1.0.0-dev.
|
||||||
|
// GITHUB_RUN_NUMBER alone is NOT enough to key off: it counts per workflow, so
|
||||||
|
// the dev pipeline has its own independent sequence. A debug APK installed from
|
||||||
|
// a CI artifact would otherwise carry that counter as its versionCode, could
|
||||||
|
// outrank a genuine release, and would leave the updater reporting "you're on
|
||||||
|
// the latest version" indefinitely.
|
||||||
|
val isReleaseBuild = System.getenv("PLG_RELEASE") == "1"
|
||||||
|
val releaseRunNumber = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 0
|
||||||
|
val stampReleaseVersion = isReleaseBuild && releaseRunNumber > 0
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.prolibertate.games"
|
namespace = "org.prolibertate.games"
|
||||||
@@ -17,8 +27,12 @@ android {
|
|||||||
applicationId = "org.prolibertate.games"
|
applicationId = "org.prolibertate.games"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = if (ciRunNumber > 0) ciRunNumber else 1
|
// Dev builds sit at 1, below every release except the very first —
|
||||||
versionName = if (ciRunNumber > 0) "1.0.$ciRunNumber" else "1.0.0-dev"
|
// a v1.0.1 release ties with a dev build and so is not offered as an
|
||||||
|
// update. Harmless, and preferable to versionCode 0, which Android
|
||||||
|
// documents as out of range.
|
||||||
|
versionCode = if (stampReleaseVersion) releaseRunNumber else 1
|
||||||
|
versionName = if (stampReleaseVersion) "1.0.$releaseRunNumber" else "1.0.0-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
Reference in New Issue
Block a user