diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2976f9..fed4e80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,16 @@ concurrency: jobs: build: 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 + 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: - name: Checkout uses: actions/checkout@v4 diff --git a/README.md b/README.md index a85fc6a..8f68e4f 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,20 @@ Requires JDK 17 and the Android SDK (compileSdk 34). ## CI - `.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 publishes it as a GitHub Release tagged `v1.0.`. The in-app updater 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 key, so an OTA update installs over the previous one instead of being rejected for a signature mismatch. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d97dd7c..de1c796 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,10 +4,20 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") } -// On CI, GITHUB_RUN_NUMBER is set automatically and matches the release tag -// (v1.0.), so the shipped APK knows its own version and the -// in-app updater can tell whether a newer release exists. Locally it's a dev build. -val ciRunNumber = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 0 +// Only the release workflow stamps a real version. It sets PLG_RELEASE, and its +// GITHUB_RUN_NUMBER is the same number as the release tag it publishes +// (v1.0.), so the shipped APK knows its own version and the in-app +// 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 { namespace = "org.prolibertate.games" @@ -17,8 +27,12 @@ android { applicationId = "org.prolibertate.games" minSdk = 24 targetSdk = 34 - versionCode = if (ciRunNumber > 0) ciRunNumber else 1 - versionName = if (ciRunNumber > 0) "1.0.$ciRunNumber" else "1.0.0-dev" + // Dev builds sit at 1, below every release except the very first — + // 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 {