From 746572c7848219357dbd64ad985a1d728b499880 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:50:52 +0200 Subject: [PATCH] fix: raise the android versionCode with every release `versionCode` was hard-coded to 1, so every release looked like the same version to Android. It is now derived from `VERSION` as `major * 10000000 + minor * 10000 + patch` (0.1.62 gives 10062), which is above the 1 existing installs have and keeps growing, so Android never mistakes an update for a downgrade. The formula only increases while patch stays below 10000 and minor below 1000. `build.gradle` refuses a `VERSION` outside that range or outside Android's 2100000000 ceiling, and `scripts/bump-version.mjs`, which only ever bumps patch, refuses to create such a version before anything is committed or tagged. --- android/app/build.gradle | 27 ++++++++++++++++++++++++++- scripts/bump-version.mjs | 9 +++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a5d18bff..d2ed16b4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,6 +7,31 @@ def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() def appVersionName = file("${projectRoot}/VERSION").text.trim() +/** + * versionCode has to grow with every release, or Android refuses the update as + * a downgrade. It is derived from VERSION as + * + * major * 10000000 + minor * 10000 + patch (0.1.62 -> 10062) + * + * which keeps increasing as long as patch stays below 10000 and minor below + * 1000 (scripts/bump-version.mjs, which only ever bumps patch, enforces both), + * and stays under Android's 2100000000 ceiling up to major 209. + */ +def versionParts = (appVersionName =~ /^(\d+)\.(\d+)\.(\d+)$/) +if (!versionParts.matches()) { + throw new GradleException("VERSION must be MAJOR.MINOR.PATCH, got '${appVersionName}'") +} +def (versionMajor, versionMinor, versionPatch) = (1..3).collect { versionParts.group(it) as long } +if (versionMinor > 999 || versionPatch > 9999) { + throw new GradleException("VERSION ${appVersionName}: minor must stay below 1000 and patch below 10000, " + + "or versionCode stops increasing. Move to the next minor or major version instead.") +} +def appVersionCode = versionMajor * 10000000L + versionMinor * 10000L + versionPatch +if (appVersionCode < 1 || appVersionCode > 2100000000L) { + throw new GradleException("VERSION ${appVersionName} gives versionCode ${appVersionCode}, " + + "outside the 1..2100000000 range Android accepts.") +} + /** * Release signing. The key never lives in the repo: CI passes it as * ORG_GRADLE_PROJECT_BULWARK_RELEASE_* environment variables, and a local @@ -126,7 +151,7 @@ android { applicationId 'com.anonymous.bulwarkmobile' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 + versionCode appVersionCode.toInteger() versionName appVersionName buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs index a014fe56..4a256ad2 100644 --- a/scripts/bump-version.mjs +++ b/scripts/bump-version.mjs @@ -42,6 +42,15 @@ if (parts.length !== 3 || parts.some(Number.isNaN)) { parts[2] += 1; const next = parts.join('.'); +// android/app/build.gradle derives versionCode as +// major * 10000000 + minor * 10000 + patch. Past these limits it would stop +// increasing, and Android refuses an update with a lower versionCode. +if (parts[1] > 999 || parts[2] > 9999) { + console.error(`${next} would break the Android versionCode: minor must stay below 1000 and patch below 10000.`); + console.error('Raise the minor or major version in VERSION by hand and commit it, then bump again.'); + process.exit(1); +} + const existingTags = new Set(git('tag', '--list').split(/\r?\n/).filter(Boolean)); if (existingTags.has(next)) { console.error(`Tag ${next} already exists.`);