A Gradle Plugin for lazily generating Android app's versionCode & versionName from Git tags.
Modern versions of Android Gradle Plugin provide new Variant APIs that support lazily computing the versionCode and versionName of an APK or App Bundle.
This plugin builds on top of these APIs to support common Git-based app versioning use cases with full Configuration Cache and Isolated Projects support.
The minimum version of Android Gradle Plugin required is 9.0.0.
| App Versioning Plugin | Android Gradle Plugin |
|---|---|
| 2.0.0+ | 9.0.0+ |
| 1.6.0 | 8.2.2+ |
| 1.5.0 | 8.0.0+ |
| 1.0.0 - 1.4.0 | 7.0.0+ |
| 0.5.0 - 0.10.0 | 4.2 |
| 0.1.0 - 0.4.0 | 4.0 - 4.1 |
The Android App Versioning Gradle Plugin is available from Maven Central. Make sure you have added mavenCentral() to the plugin repositories:
// in settings.gradle.kts
pluginManagement {
repositories {
mavenCentral()
}
}The plugin can now be applied to your Android Application module (Gradle subproject).
Kotlin
plugins {
id("com.android.application")
id("io.github.reactivecircus.app-versioning") version "x.y.z"
}Groovy
plugins {
id 'com.android.application'
id 'io.github.reactivecircus.app-versioning' version "x.y.z"
}Tip
Upgrading from 1.x? See the 2.0 Migration Guide.
The plugin offers 2 Gradle tasks for each build variant:
generateAppVersionInfoFor<BuildVariant>- generates theversionCodeandversionNamefor theBuildVariant. This task is automatically triggered when assembling the APK or AAB (e.g. by runningassemble<BuildVariant>orbundle<BuildVariant>), and the generatedversionCodeandversionNameare injected into the final mergedAndroidManifest.xml.printAppVersionInfoFor<BuildVariant>- prints the latestversionCodeandversionNamegenerated by the plugin to the console if available.
Without any configuration, the plugin fetches the latest Git tag in the repository, parses it as a SemVer string, and computes versionCode using positional notation:
versionCode = MAJOR * 10000 + MINOR * 100 + PATCH
For example, for a tag 1.3.1, the generated versionCode is 1 * 10000 + 3 * 100 + 1 = 10301.
The default versionName generated is the name of the latest Git tag.
> Task :app:generateAppVersionInfoForRelease
Generated app version code: 10301.
Generated app version name: "1.3.1".
You can customize how versionCode and versionName are computed by configuring versionCode (Property<VersionCodeSpec>) and versionName (Property<VersionNameSpec>):
appVersioning {
versionCode.set {
// generate an Int
}
versionName.set {
// generate a String
}
}Inside the lambda receiver (VersionContext):
tag(GitTag) encapsulatesname,commitsSinceLatestTag, andcommitHash.variant(VariantInfo) encapsulatesbuildType,flavorName,variantName,isDebugBuild, andisReleaseBuild.
The default VersionCodeSpec.SemVer reserves 2 digits for each of the MAJOR, MINOR, and PATCH components in a SemVer tag (accepting an optional "v" prefix).
To allocate 3 digits per component instead (up to 999 per component) or strip a custom tag prefix:
Kotlin
appVersioning {
versionCode.set {
tag.toSemVer(prefix = "android-").toVersionCode(digitsPerComponent = 3)
}
}Groovy
appVersioning {
versionCode {
tag.toSemVer("android-").toVersionCode(3)
}
}tag.toSemVer(prefix = "") parses GitTag#name (after stripping prefix if specified) into a SemVer object, and SemVer#toVersionCode(digitsPerComponent = 2) converts it to an integer using positional notation. You can also parse arbitrary strings directly via SemVer.parse(text) or SemVer.parseOrNull(text).
When computing versionCode or versionName, you can use an environment variable or Gradle property by mapping its Provider to the VersionCodeSpec or VersionNameSpec property:
Kotlin
import io.github.reactivecircus.appversioning.VersionCodeSpec
val buildNumber = providers.environmentVariable("BUILD_NUMBER").map(String::toInt).orElse(0)
appVersioning {
versionCode.set(
buildNumber.map { buildNum ->
VersionCodeSpec {
tag.toSemVer().toVersionCode() + buildNum
}
}
)
}Groovy
import io.github.reactivecircus.appversioning.VersionCodeSpec
def buildNumber = providers.environmentVariable("BUILD_NUMBER").map { it.toInteger() }.orElse(0)
appVersioning {
versionCode.set(
buildNumber.map { buildNum ->
{
tag.toSemVer().toVersionCode() + buildNum
} as VersionCodeSpec
}
)
}Sometimes you might want to customize versionCode or versionName based on the build variants (product flavor, build type). To do this, use variant (VariantInfo) from the VersionContext receiver when generating custom versionCode or versionName:
Kotlin
appVersioning {
versionCode.set {
// add 1 to the versionCode for builds with the "paid" product flavor
val offset = if (variant.flavorName == "paid") 1 else 0
tag.toSemVer().toVersionCode() + offset
}
versionName.set {
// append build variant to the versionName for debug builds
val suffix = if (variant.isDebugBuild) " (${variant.variantName})" else ""
"${tag.name} (${tag.commitHash})$suffix"
}
}Groovy
appVersioning {
versionCode {
// add 1 to the versionCode for builds with the "paid" product flavor
def offset = variant.flavorName == "paid" ? 1 : 0
tag.toSemVer().toVersionCode() + offset
}
versionName {
// append build variant to the versionName for debug builds
def suffix = variant.debugBuild ? " (${variant.variantName})" : ""
"${tag.name} (${tag.commitHash})${suffix}"
}
}By default the plugin uses the latest tag in the current branch for versionCode and versionName generation.
Sometimes it's useful to be able to use the latest tag that follows a specific glob pattern.
For example a codebase might build and publish 3 different apps separately using the following tag pattern:
<maj>.<min>.<patch>[-<pre-release version>]+<app-identifier>
where app-identifier is the build metadata component in SemVer.
Some of the possible tags are:
1.5.8+app-a
2.29.0-rc01+app-b
10.87.9-alpha04+app-c
To configure the plugin to generate version info specific to app-b:
appVersioning {
tagFilter.set("[0-9]*.[0-9]*.[0-9]*+app-b")
}To disable the plugin so the versionCode and versionName defined in the defaultConfig block or manifest are used instead:
appVersioning {
enabled.set(false)
}To generate versionCode and versionName only for the release build type:
appVersioning {
releaseBuildOnly.set(true)
}With releaseBuildOnly set to true, tasks are only registered for release variants:
./gradlew tasks --group=versioning
Versioning tasks
----------------
generateAppVersionInfoForRelease - Generates app's versionCode and versionName based on git tags for the release variant.
printAppVersionInfoForRelease - Prints the versionCode and versionName generated by Android App Versioning plugin (if available) to the console for the release variant.
When a local checkout does not contain Git tags (e.g. cloned with --no-tags), you can configure the plugin to fetch tags from remote when none exist locally:
appVersioning {
fetchTagsWhenNoneExistsLocally.set(true)
}The plugin assumes the root Gradle project is the git root directory containing .git. If your root Gradle project is not the git root, specify it explicitly:
appVersioning {
gitRootDirectory.set(rootDir.resolve("../"))
}If your .git is a symbolic link to a bare git repository, specify the directory of the bare git repository explicitly (overrides gitRootDirectory):
appVersioning {
bareGitRepoDirectory.set(rootDir.resolve("../.repo/projects/app.git"))
}Many CI providers only fetch a single commit by default when checking out the repository. Make sure Git tags and history are fetched (e.g. with GitHub Actions):
- uses: actions/checkout@v5
with:
fetch-depth: 0Both the versionCode and versionName generated by app-versioning are written to the build output directory:
app/build/outputs/app_versioning/<buildVariant>/version_code.txt
app/build/outputs/app_versioning/<buildVariant>/version_name.txt
You can read the contents of these files into shell variables:
VERSION_CODE=$(cat app/build/outputs/app_versioning/<buildVariant>/version_code.txt)
VERSION_NAME=$(cat app/build/outputs/app_versioning/<buildVariant>/version_name.txt)Copyright 2020 Yang Chen
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.