Version dev builds 0.1.0-dev-<timestamp>, and actually hand it to the build

59b06a9 · Gabriel Morell · 2026-09-14 00:25

5 files +68 -6
Message
{commit_body(@commit)}

Files changed

modified android/Dockerfile
+18 −1
@@ -72,9 +72,26 @@ RUN ./gradlew --no-daemon :app:dependencies --configuration releaseRuntimeClassp
72 72 FROM deps AS build
73 73
74 74 ARG BUILD_TOOLS=34.0.0
75 +
76 +# A container build inherits nothing from the runner's environment, so
77 +# the version has to be handed in explicitly. Without these, Gradle's
78 +# getenv lookups come back empty inside here and every run builds
79 +# versionCode 1 — the same filename with different bytes, which the
80 +# registry refuses with a 409.
81 +#
82 +# Declared in this stage rather than at the top so they don't invalidate
83 +# the SDK and dependency layers, which is most of the build time.
84 +ARG GITHUB_RUN_NUMBER
85 +ARG SOURCE_DATE_EPOCH
86 +ARG RELEASE_VERSION
87 +ENV GITHUB_RUN_NUMBER=${GITHUB_RUN_NUMBER} \
88 + SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} \
89 + RELEASE_VERSION=${RELEASE_VERSION}
90 +
75 91 COPY android/app ./app
76 92
77 −RUN ./gradlew --no-daemon :app:assembleRelease
93 +RUN echo "building versionCode=${GITHUB_RUN_NUMBER:-1}" \
94 + && ./gradlew --no-daemon :app:assembleRelease
78 95
79 96 # Sign as a separate step from Gradle so the keystore never reaches a
80 97 # build script, a Gradle cache, or an image layer. BuildKit secrets are
modified android/README.md
+26 −0
@@ -260,6 +260,32 @@ secret is the operational one.
260 260 Worth revisiting if more secrets need centralising — one doesn't
261 261 justify the ingress, several might.
262 262
263 +## Versioning
264 +
265 +| | |
266 +|---|---|
267 +| `versionCode` | the CI run number (`GITHUB_RUN_NUMBER`), `1` locally |
268 +| `versionName` | `0.1.0-dev-<unix seconds>`, or `$RELEASE_VERSION` verbatim when set |
269 +
270 +Both are generated rather than committed. `versionCode` has to increase
271 +or Android refuses the update and the registry refuses the upload;
272 +`versionName` carries a timestamp so a list of builds sorts and reads at
273 +a glance, where bare run numbers don't.
274 +
275 +The commit isn't in the name because it doesn't need to be: the
276 +versionCode *is* the CI run number, and a run knows its own commit.
277 +
278 +The timestamp comes from `SOURCE_DATE_EPOCH` (or `BUILD_TIMESTAMP`)
279 +when set — the workflow computes one stamp per run, so every artifact
280 +from a run agrees even if a step is retried. A reproducible-build
281 +harness that already sets `SOURCE_DATE_EPOCH` gets a reproducible
282 +`versionName` for free.
283 +
284 +A container build inherits nothing from the runner, so these are passed
285 +as `--build-arg` in `android/Dockerfile`. They're declared in the build
286 +stage rather than at the top, so changing them doesn't invalidate the
287 +SDK and dependency layers — which are most of the build time.
288 +
263 289 ## What's not shipped yet
264 290
265 291 - **Wiki editing** — the wiki is a git repo of markdown files, and
modified android/app/build.gradle.kts
+24 −5
@@ -5,10 +5,27 @@ plugins {
5 5 alias(libs.plugins.kotlin.serialization)
6 6 }
7 7
8 −// Bumped by hand for a real release; the build metadata after it comes
9 −// from CI.
8 +// Bumped by hand; everything after it is generated.
10 9 val baseVersion = "0.1.0"
11 10
11 +// Seconds, and taken from the environment when it's there. CI sets it
12 +// once per run so every artifact from that run agrees; a local build
13 +// computes it, which is fine because local builds are never published.
14 +//
15 +// SOURCE_DATE_EPOCH first because it's the cross-toolchain convention
16 +// for exactly this, and honouring it means a reproducible-build harness
17 +// that already sets it gets a reproducible versionName for free.
18 +val buildTimestamp: Long =
19 + (System.getenv("SOURCE_DATE_EPOCH") ?: System.getenv("BUILD_TIMESTAMP"))
20 + ?.toLongOrNull()
21 + ?: (System.currentTimeMillis() / 1000)
22 +
23 +// A real release overrides the whole string; until there is one, every
24 +// build says so in its name rather than claiming to be 0.1.0.
25 +val appVersionName: String =
26 + System.getenv("RELEASE_VERSION")?.takeIf { it.isNotBlank() }
27 + ?: "$baseVersion-dev-$buildTimestamp"
28 +
12 29 android {
13 30 namespace = "org.neiam.gitgud.app"
14 31 compileSdk = 34
@@ -29,9 +46,11 @@ android {
29 46 // never published.
30 47 versionCode = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 1
31 48
32 − // The commit is what you want when someone reports a bug
33 − // against a build. Local builds just say "dev".
34 − versionName = "$baseVersion+" + ((System.getenv("GITHUB_SHA") ?: "dev").take(7))
49 + // Not the commit: versionCode is the CI run number, and a run
50 + // knows its own commit, so the sha is one lookup away and the
51 + // timestamp orders builds at a glance in a list that versionCode
52 + // alone renders as bare integers.
53 + versionName = appVersionName
35 54 }
36 55
37 56 buildTypes {

Parents: a2f8319