From 9f0af9c24b54663bae9d7a83cff4099b16b34386 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 2 Jun 2026 14:07:57 -0400 Subject: [PATCH] feat(android): CI-injected versionName with commit-count iteration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The APK was shipping with a hardcoded versionName="0.1.0-native" and versionCode=1 — so the About card never reflected the actual release, and two same-day re-cuts of the per-day mutable tag v2026.06.02 looked identical to the update-banner comparator. Operator wants the iteration restored on the APK side (the docker tag stays plain per the earlier intentional change). Scheme: - Per release: versionName = "${tag}.${commit_count}", e.g. "2026.06.02.142", where commit_count = `git rev-list --count HEAD`. Monotonic across the project lifetime, deterministic, no manual counter to maintain. - versionCode = commit_count. Monotonic, fits in Int forever (we're not hitting 2.1B commits). - Local / debug / dev builds fall back to versionName="dev" / versionCode=1 so the About card reads honestly. build.gradle.kts: - defaultConfig reads MINSTREL_VERSION_NAME / MINSTREL_VERSION_CODE Gradle properties via project.findProperty with the dev fallbacks. .gitea/workflows/release.yml: - android-release: checkout with fetch-depth: 0 (the default shallow clone would return 1 for `git rev-list --count HEAD`); new Compute release version step exports name + code as step outputs; assembleRelease passes them via -P; new job-level outputs propagate them to the downstream image-release job. - image-release: Stage bundled APK + version sidecar pulls the computed version_name from needs.android-release.outputs and writes it into client/minstrel.apk.version, so the server's /api/client/version reports the exact string baked into the APK. Without this the sidecar would say "v2026.06.02" while the installed APK has "2026.06.02.142" — isVersionNewer would call the bundled APK older and the update banner would thrash. The existing isVersionNewer comparator already handles the 4-component shape ("2026.06.02.142" > "2026.06.02.141"), so no client-side logic changes are needed. --- .gitea/workflows/release.yml | 39 +++++++++++++++++++++++++++++++++--- android/app/build.gradle.kts | 15 ++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 83789234..be5cfa43 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -60,9 +60,35 @@ jobs: ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + # Job outputs propagate the computed release version to image-release + # so the bundled sidecar file matches what's baked into the APK — + # otherwise the server would report a different version string than + # the installed client and the update banner could thrash. + outputs: + version_name: ${{ steps.ver.outputs.name }} + version_code: ${{ steps.ver.outputs.code }} + steps: - name: Checkout uses: actions/checkout@v4 + with: + # fetch-depth: 0 retrieves full history; default shallow clone + # would return 1 for `git rev-list --count HEAD`, breaking the + # iteration suffix. + fetch-depth: 0 + + - name: Compute release version + id: ver + shell: bash + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + TAG="${GITHUB_REF#refs/tags/v}" + COMMIT_COUNT=$(git rev-list --count HEAD) + VERSION_NAME="${TAG}.${COMMIT_COUNT}" + echo "name=${VERSION_NAME}" >> "$GITHUB_OUTPUT" + echo "code=${COMMIT_COUNT}" >> "$GITHUB_OUTPUT" + echo "::notice::APK version: ${VERSION_NAME} (code=${COMMIT_COUNT})" - name: Cache Gradle dirs uses: actions/cache@v4 @@ -91,7 +117,10 @@ jobs: echo "ANDROID_KEYSTORE_PATH=${KEYSTORE_PATH}" >> "${GITHUB_ENV}" - name: Build release APK - run: ./gradlew assembleRelease + run: | + ./gradlew assembleRelease \ + -PMINSTREL_VERSION_NAME=${{ steps.ver.outputs.name }} \ + -PMINSTREL_VERSION_CODE=${{ steps.ver.outputs.code }} - name: Upload APK as workflow artifact # @v3 because Gitea Actions emulates GHES and the v2 artifact @@ -204,14 +233,18 @@ jobs: - name: Stage bundled APK + version sidecar if: steps.guard.outputs.ready == 'true' && startsWith(github.ref, 'refs/tags/v') shell: bash + env: + # Pulled from android-release.outputs.version_name so the + # sidecar string the server hands clients matches the + # versionName baked into the APK they're comparing against. + APK_VERSION_NAME: ${{ needs.android-release.outputs.version_name }} run: | set -euxo pipefail - TAG="${GITHUB_REF#refs/tags/}" # The artifact lands as `app-release.apk` (the original Gradle # output name). The Dockerfile COPYs client/* into /app/client/ # and the server reads minstrel.apk + minstrel.apk.version. mv client/app-release.apk client/minstrel.apk - echo "${TAG}" > client/minstrel.apk.version + echo "${APK_VERSION_NAME}" > client/minstrel.apk.version ls -lh client/ - name: Build and push diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2535edf2..4e72d441 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -21,8 +21,19 @@ android { applicationId = "com.fabledsword.minstrel" minSdk = 26 targetSdk = 36 - versionCode = 1 - versionName = "0.1.0-native" + // versionName / versionCode are released-build values injected by + // CI from the git tag + commit count. Local / debug builds fall + // back to "dev" so the About card reads honestly. Releases ship + // versionName="YYYY.MM.DD." (e.g. "2026.06.02.142") and + // versionCode=, which is monotonic forever and lets the + // shared isVersionNewer comparator distinguish two same-day + // re-cuts (the iteration suffix differs). + val versionNameOverride = + (project.findProperty("MINSTREL_VERSION_NAME") as String?)?.takeIf { it.isNotBlank() } + val versionCodeOverride = + (project.findProperty("MINSTREL_VERSION_CODE") as String?)?.toIntOrNull() + versionCode = versionCodeOverride ?: 1 + versionName = versionNameOverride ?: "dev" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } }