neiam
/media-bubbles
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Version builds from the CI run number
24a90d7 · gmorell · 2026-11-05 16:30
Message
{commit_body(@commit)}
Files changed
modified
.forgejo/workflows/android.yml
+12
−0
@@ -81,9 +81,21 @@ jobs:
| 81 | 81 | |
| 82 | 82 | - name: Build and sign the APK |
| 83 | 83 | if: steps.keystore.outputs.skip != 'true' |
| 84 | + env: | |
| 85 | + RUN_NUMBER: ${{ github.run_number }} | |
| 84 | 86 | run: | |
| 87 | + set -euo pipefail | |
| 88 | + | |
| 89 | + # One stamp for the whole run, computed here rather than | |
| 90 | + # inside Gradle, so every artifact a run produces agrees on | |
| 91 | + # its version even if the build is retried a step at a time. | |
| 92 | + stamp=$(date -u +%s) | |
| 93 | + echo "version: 0.1.0-dev-$stamp (versionCode $RUN_NUMBER)" | |
| 94 | + | |
| 85 | 95 | docker buildx build \ |
| 86 | 96 | --target android-export \ |
| 97 | + --build-arg GITHUB_RUN_NUMBER="$RUN_NUMBER" \ | |
| 98 | + --build-arg SOURCE_DATE_EPOCH="$stamp" \ | |
| 87 | 99 | --secret id=keystore,src=./release.jks \ |
| 88 | 100 | --secret id=keystore_password,src=./keystore.pass \ |
| 89 | 101 | --output type=local,dest=./apk \ |
modified
Dockerfile
+18
−1
@@ -77,9 +77,26 @@ RUN ./gradlew --no-daemon :app:dependencies --configuration releaseRuntimeClassp
| 77 | 77 | FROM deps AS build |
| 78 | 78 | |
| 79 | 79 | ARG BUILD_TOOLS=34.0.0 |
| 80 | + | |
| 81 | +# A container build inherits nothing from the runner's environment, so | |
| 82 | +# the version has to be handed in explicitly. Without these, Gradle's | |
| 83 | +# getenv lookups come back empty inside here and every run builds | |
| 84 | +# versionCode 1 — the same filename with different bytes, which the | |
| 85 | +# registry refuses with a 409. | |
| 86 | +# | |
| 87 | +# Declared in this stage rather than at the top so they don't invalidate | |
| 88 | +# the SDK and dependency layers, which is most of the build time. | |
| 89 | +ARG GITHUB_RUN_NUMBER | |
| 90 | +ARG SOURCE_DATE_EPOCH | |
| 91 | +ARG RELEASE_VERSION | |
| 92 | +ENV GITHUB_RUN_NUMBER=${GITHUB_RUN_NUMBER} \ | |
| 93 | + SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} \ | |
| 94 | + RELEASE_VERSION=${RELEASE_VERSION} | |
| 95 | + | |
| 80 | 96 | COPY app ./app |
| 81 | 97 | |
| 82 | −RUN ./gradlew --no-daemon :app:assembleRelease | |
| 98 | +RUN echo "building versionCode=${GITHUB_RUN_NUMBER:-1}" \ | |
| 99 | + && ./gradlew --no-daemon :app:assembleRelease | |
| 83 | 100 | |
| 84 | 101 | # Sign as a separate step from Gradle so the keystore never reaches a |
| 85 | 102 | # build script, a Gradle cache, or an image layer. BuildKit secrets are |
modified
app/build.gradle.kts
+38
−2
@@ -4,6 +4,27 @@ plugins {
| 4 | 4 | alias(libs.plugins.kotlin.compose) |
| 5 | 5 | } |
| 6 | 6 | |
| 7 | +// Bumped by hand; everything after it is generated. | |
| 8 | +val baseVersion = "0.1.0" | |
| 9 | + | |
| 10 | +// Seconds, and taken from the environment when it's there. CI sets it | |
| 11 | +// once per run so every artifact from that run agrees; a local build | |
| 12 | +// computes it, which is fine because local builds are never published. | |
| 13 | +// | |
| 14 | +// SOURCE_DATE_EPOCH first because it's the cross-toolchain convention | |
| 15 | +// for exactly this, and honouring it means a reproducible-build harness | |
| 16 | +// that already sets it gets a reproducible versionName for free. | |
| 17 | +val buildTimestamp: Long = | |
| 18 | + (System.getenv("SOURCE_DATE_EPOCH") ?: System.getenv("BUILD_TIMESTAMP")) | |
| 19 | + ?.toLongOrNull() | |
| 20 | + ?: (System.currentTimeMillis() / 1000) | |
| 21 | + | |
| 22 | +// A real release overrides the whole string; until there is one, every | |
| 23 | +// build says so in its name rather than claiming to be 0.1.0. | |
| 24 | +val appVersionName: String = | |
| 25 | + System.getenv("RELEASE_VERSION")?.takeIf { it.isNotBlank() } | |
| 26 | + ?: "$baseVersion-dev-$buildTimestamp" | |
| 27 | + | |
| 7 | 28 | android { |
| 8 | 29 | namespace = "org.neiam.mediabubbles" |
| 9 | 30 | compileSdk = 34 |
@@ -12,8 +33,23 @@ android {
| 12 | 33 | applicationId = "org.neiam.mediabubbles" |
| 13 | 34 | minSdk = 26 |
| 14 | 35 | targetSdk = 34 |
| 15 | − versionCode = 1 | |
| 16 | − versionName = "0.1.0" | |
| 36 | + // Android refuses an update whose versionCode isn't higher than | |
| 37 | + // what's installed, and F-Droid orders versions by it — so a | |
| 38 | + // hardcoded 1 means no phone ever sees a second release. It also | |
| 39 | + // means every CI build publishes the same filename with | |
| 40 | + // different bytes, which the registry correctly refuses with a | |
| 41 | + // 409. | |
| 42 | + // | |
| 43 | + // The CI run number is monotonic and needs no state kept | |
| 44 | + // anywhere. Local builds stay at 1, which is fine: they are | |
| 45 | + // never published. | |
| 46 | + versionCode = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 1 | |
| 47 | + | |
| 48 | + // Not the commit: versionCode is the CI run number, and a run | |
| 49 | + // knows its own commit, so the sha is one lookup away and the | |
| 50 | + // timestamp orders builds at a glance in a list that versionCode | |
| 51 | + // alone renders as bare integers. | |
| 52 | + versionName = appVersionName | |
| 17 | 53 | } |
| 18 | 54 | |
| 19 | 55 | buildTypes { |
Parents: eddae77