Merge pull request #11: move to the 1.1 series

Verified green on the feature branch before merging: build, unit tests
and Android lint (ci.yml run 59).
This commit is contained in:
adrianoftyriel
2026-08-07 09:56:31 -06:00
committed by GitHub
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