From 78d6c85a06f65eb2c34edc45c22af157777bec7a Mon Sep 17 00:00:00 2001 From: will wade Date: Sat, 22 Aug 2026 10:30:45 +0000 Subject: [PATCH] feat: derive versionName/versionCode from the v* tag (git describe) versionName sat at '0.1.0' for eight releases because nothing forced the bump; the RFC 0016 version line would have shown it. Now the tag is the single source of truth: - versionName = most recent v* tag via git describe (tag builds report the tag exactly; dev builds '0.1.8-3-g1a2b3c4'); constants demoted to no-git fallbacks. - versionCode = 1000 + commit count (deterministic, monotonic, and above every historically shipped code). - release.yml gate: on v* tags, the built APK's versionName must equal the tag or the release fails before publishing. Verified: APK badging shows versionName 0.1.8 / versionCode 1060 on the v0.1.8 commit; simulated dev commit reports 0.1.8-1-g. Signed-off-by: will wade --- .github/workflows/release.yml | 18 ++++++++++++++++++ app/build.gradle.kts | 31 +++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4886189..3f6ba34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,6 +40,24 @@ jobs: - name: Build release APK run: ./gradlew :app:assembleRelease --no-daemon --stacktrace + # The APK's versionName is git-derived (app/build.gradle.kts), so a + # mismatch here means the checkout wasn't at the tag (workflow_dispatch + # on a moving branch, or describe picked an older tag). Fail before the + # release is published rather than ship a mislabelled APK. + - name: Verify APK version matches the tag + if: startsWith(github.ref, 'refs/tags/v') + run: | + set -e + tag="${GITHUB_REF_NAME#v}" + aapt="$(ls $ANDROID_HOME/build-tools/*/aapt2 | sort -V | tail -1)" + apk="$(ls app/build/outputs/apk/release/*.apk | head -1)" + apk_version="$("$aapt" dump badging "$apk" | sed -n "s/^package:.*versionName='\([^']*\)'.*/\1/p" | head -1)" + echo "tag: $tag apk: $apk_version" + if [ "$apk_version" != "$tag" ]; then + echo "::error::APK versionName ($apk_version) does not match the tag being released (v$tag)" + exit 1 + fi + - name: Rename APK with tag run: | tag="${GITHUB_REF_NAME:-untagged}" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6aefbba..527e8ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,32 @@ import org.gradle.api.tasks.Copy +// ── Version from the v* tag (single source of truth) ──────────────────────── +// versionName = the most recent v* tag (via git describe); versionCode = the +// number of commits since the tag began, plus a base, so it is deterministic +// and strictly monotonic across builds without manual bumping — versionName +// sat at "0.1.0" for eight releases because nothing forced the bump. +// Tarball/no-git builds fall back to the constants below; keep them at the +// last released values. +fun gitVersionName(fallback: String): String { + val describe = try { + val p = ProcessBuilder("git", "describe", "--tags", "--match", "v*") + .directory(rootDir).redirectErrorStream(true).start() + p.inputStream.bufferedReader().readText().trim().also { p.waitFor() } + } catch (_: Exception) { "" } + return if (describe.startsWith("v")) describe.removePrefix("v") else fallback +} + +fun gitVersionCode(base: Int, fallback: Int): Int { + // Count commits since the first commit reachable from HEAD; the base keeps + // historical codes (0.1.8 shipped as 8) below any git-derived value. + val count = try { + val p = ProcessBuilder("git", "rev-list", "--count", "HEAD") + .directory(rootDir).redirectErrorStream(true).start() + p.inputStream.bufferedReader().readText().trim().toIntOrNull().also { p.waitFor() } + } catch (_: Exception) { null } + return base + (count ?: (fallback - base)) +} + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -15,8 +42,8 @@ android { applicationId = "at.dasher.android" minSdk = 24 targetSdk = 35 - versionCode = 8 - versionName = "0.1.8" + versionCode = gitVersionCode(base = 1000, fallback = 8) + versionName = gitVersionName(fallback = "0.1.8") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"