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:
Claude
2026-08-07 15:50:30 +00:00
parent 7cd2b412e2
commit e8c7162bc0
7 changed files with 67 additions and 14 deletions
+7 -2
View File
@@ -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"
+6 -1
View File
@@ -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"
+1 -1
View File
@@ -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.
+25 -4
View File
@@ -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
View File
@@ -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()
+12
View File
@@ -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