Move to the 1.1 series, from one place rather than three
The series a build is named for was written out three times — in the build file and in both workflows' tag lines — so bumping it meant changing all three and a release could be tagged one thing and built as another. It now lives in gradle.properties as plgVersionSeries, which the build reads when it stamps the APK and both workflows read when they name the tag. Set to 1.1. versionCode is deliberately left as the CI run number alone, which is what makes the bump safe: the run number keeps climbing across a change of series, so 1.1 never looks like a downgrade to Android and an OTA update is not interrupted. The series is a name for people; the run number is what the machines compare. The updater already read only the trailing number, and its comments now say so rather than naming 1.0. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019Pr6B8xDDtJJcb6WJMw7Ud
This commit is contained in:
@@ -34,7 +34,7 @@ jobs:
|
||||
# Stamp the APK as a dev-channel build exactly when it is a build of the
|
||||
# dev branch, which is exactly when it can be published below. Pull
|
||||
# request builds run against refs/pull/N/merge, so they stay unstamped at
|
||||
# 1.0.0-dev: they are never published and must not claim a channel
|
||||
# <series>.0-dev: they are never published and must not claim a channel
|
||||
# version.
|
||||
#
|
||||
# This condition has to stay in step with the publish steps' own. Stamping
|
||||
@@ -96,7 +96,12 @@ jobs:
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="v1.0.${{ github.run_number }}-dev"
|
||||
# The series comes from gradle.properties, which is the same file the
|
||||
# build read when it stamped the APK — so the tag and what is inside
|
||||
# it cannot name different versions.
|
||||
series="$(sed -n 's/^plgVersionSeries=//p' gradle.properties)"
|
||||
test -n "$series"
|
||||
version="v${series}.${{ github.run_number }}-dev"
|
||||
src="app/build/outputs/apk/debug/app-debug.apk"
|
||||
dest="ProLibertateGames-${version}.apk"
|
||||
cp "$src" "$dest"
|
||||
|
||||
@@ -49,7 +49,12 @@ jobs:
|
||||
id: stage
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="v1.0.${{ github.run_number }}"
|
||||
# The series comes from gradle.properties, which is the same file the
|
||||
# build read when it stamped the APK — so the tag and what is inside
|
||||
# it cannot name different versions.
|
||||
series="$(sed -n 's/^plgVersionSeries=//p' gradle.properties)"
|
||||
test -n "$series"
|
||||
version="v${series}.${{ github.run_number }}"
|
||||
src="app/build/outputs/apk/debug/app-debug.apk"
|
||||
dest="ProLibertateGames-${version}.apk"
|
||||
cp "$src" "$dest"
|
||||
|
||||
@@ -46,7 +46,7 @@ Skipping step 4 leaves a branch whose head was never built and no release.
|
||||
Both long-lived branches publish, so a merge is a release, not just an
|
||||
integration:
|
||||
|
||||
- **`dev`** → `ci.yml` publishes a `v1.0.N-dev` **prerelease**, offered by the
|
||||
- **`dev`** → `ci.yml` publishes a `v<series>.N-dev` **prerelease**, offered by the
|
||||
in-app updater to anyone on the Dev channel.
|
||||
- **`main`** → `release.yml` publishes a **production** release, which the
|
||||
updater installs for everyone on the Production channel.
|
||||
|
||||
@@ -165,9 +165,10 @@ production package instead.
|
||||
|
||||
- `.github/workflows/ci.yml` — builds, tests and lints on `dev` and on pull
|
||||
requests into `dev` or `main`. Its APK artifact is a dev build, versioned
|
||||
`1.0.0-dev`.
|
||||
`<series>.0-dev` — the series being whatever `plgVersionSeries` says in
|
||||
`gradle.properties`, currently **1.1**.
|
||||
- `.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 `v<series>.<run-number>`. The in-app updater
|
||||
reads that tag, which is why the APK's `versionCode` is the same run number.
|
||||
|
||||
### Installing without the app
|
||||
@@ -186,14 +187,34 @@ prereleases by design, and that is exactly what keeps a production install from
|
||||
being handed a dev build. Dev builds are reached through the in-app updater with
|
||||
the channel set to dev, or from the releases page.
|
||||
|
||||
### The version series
|
||||
|
||||
A version is **`<series>.<run number>`**. The series is set by hand, once, in
|
||||
`gradle.properties`:
|
||||
|
||||
```properties
|
||||
plgVersionSeries=1.1
|
||||
```
|
||||
|
||||
Both workflows read that same property when they name a release tag, and the
|
||||
build reads it when it stamps the APK, so the tag and what is inside it cannot
|
||||
name different versions. Bumping a series is a one-line change to that file and
|
||||
nothing else.
|
||||
|
||||
The number after it is the CI run number and keeps climbing on its own, which is
|
||||
what makes a series bump safe: `versionCode` is the run number *alone*, so
|
||||
going from 1.0 to 1.1 never looks like a downgrade to Android and never
|
||||
interrupts an OTA update. The series is a name for people; the run number is
|
||||
what the machines compare.
|
||||
|
||||
### Update channels
|
||||
|
||||
Both pipelines publish, to two separate channels:
|
||||
|
||||
| Channel | Published by | Tag | Marked |
|
||||
| --- | --- | --- | --- |
|
||||
| Production | `release.yml`, push to `main` | `v1.0.<n>` | release |
|
||||
| Dev | `ci.yml`, any build of `dev` | `v1.0.<n>-dev` | prerelease |
|
||||
| Production | `release.yml`, push to `main` | `v<series>.<n>` | release |
|
||||
| Dev | `ci.yml`, any build of `dev` | `v<series>.<n>-dev` | prerelease |
|
||||
|
||||
Dev builds are published as **prereleases** deliberately: GitHub's "latest
|
||||
release" endpoint skips prereleases, so a production-channel install can never
|
||||
|
||||
+12
-4
@@ -16,6 +16,11 @@ val buildChannel = System.getenv("PLG_CHANNEL").orEmpty()
|
||||
val runNumber = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 0
|
||||
val stampVersion = buildChannel.isNotEmpty() && runNumber > 0
|
||||
|
||||
// The version series, from gradle.properties. Both workflows read the same
|
||||
// property when they name a release tag, so the tag and the APK inside it
|
||||
// cannot disagree about which series they belong to.
|
||||
val versionSeries = (project.findProperty("plgVersionSeries") as String?) ?: "1.0"
|
||||
|
||||
// Anything that is not an explicit production build is a dev build: ci.yml
|
||||
// stamps "dev", and a local build with nothing set counts as dev too.
|
||||
//
|
||||
@@ -43,12 +48,15 @@ android {
|
||||
// know its own channel, so it must survive into the shipped APK. It
|
||||
// tracks isDevBuild rather than the stamp alone, so an unstamped local
|
||||
// build cannot end up claiming a channel its applicationId contradicts.
|
||||
// versionCode stays the run number and nothing else, so it keeps
|
||||
// climbing across a change of series: bumping 1.0 to 1.1 must not make
|
||||
// an update look like a downgrade to Android.
|
||||
versionCode = if (stampVersion) runNumber else 1
|
||||
versionName = when {
|
||||
stampVersion && buildChannel == "production" -> "1.0.$runNumber"
|
||||
stampVersion -> "1.0.$runNumber-dev"
|
||||
isDevBuild -> "1.0.0-dev"
|
||||
else -> "1.0.0"
|
||||
stampVersion && buildChannel == "production" -> "$versionSeries.$runNumber"
|
||||
stampVersion -> "$versionSeries.$runNumber-dev"
|
||||
isDevBuild -> "$versionSeries.0-dev"
|
||||
else -> "$versionSeries.0"
|
||||
}
|
||||
|
||||
// The launcher label. With both copies on the phone the app drawer shows
|
||||
|
||||
@@ -38,7 +38,7 @@ private const val DEV_SUFFIX = "-dev"
|
||||
/**
|
||||
* Over-the-air updates from the repository's GitHub Releases.
|
||||
*
|
||||
* Release tags are v1.0.<run-number> and the APK's versionCode is that same run
|
||||
* Release tags are v<series>.<run-number> and the APK's versionCode is that same run
|
||||
* number, so comparing the two is enough to tell whether a newer build exists.
|
||||
* The repository's releases must be publicly downloadable — no token is
|
||||
* embedded in the app, and GitHub blocks anonymous access to private assets.
|
||||
@@ -112,7 +112,9 @@ class Updater(private val activity: Activity) {
|
||||
|
||||
private fun toRelease(json: JSONObject): Release? {
|
||||
val tag = json.optString("tag_name").ifBlank { return null }
|
||||
// Tags are v1.0.<n> or v1.0.<n>-dev; the run number is the trailing part.
|
||||
// Tags are v<series>.<n>, with -dev on a prerelease. Only the trailing
|
||||
// run number is read: the series is a name for people and changing it
|
||||
// must not make an older build look newer.
|
||||
val versionCode = tag.substringAfterLast('.')
|
||||
.removeSuffix(DEV_SUFFIX)
|
||||
.toIntOrNull()
|
||||
|
||||
@@ -11,3 +11,15 @@ kotlin.code.style=official
|
||||
|
||||
# Only build the resources needed for the configuration being built.
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# The version series every build and every release tag is named for: an APK is
|
||||
# <series>.<run number>, a production tag is v<series>.<n> and a dev tag adds
|
||||
# -dev. It lives here rather than in the build file because the workflows have
|
||||
# to name the tag as well, and they read it straight out of this file — three
|
||||
# copies of "1.1" would be three chances for a release to be tagged one thing
|
||||
# and built as another.
|
||||
#
|
||||
# Only the series is set by hand. The number after it is the CI run number, so
|
||||
# it keeps climbing on its own and an APK's versionCode never goes backwards
|
||||
# when this changes.
|
||||
plgVersionSeries=1.1
|
||||
|
||||
Reference in New Issue
Block a user