How to change the generated filename for App Bundles with Gradle? - android

So to change the generated APK filename inside gradle android I could do something like:
applicationVariants.output.all {
outputFileName = "the_file_name_that_i_want.apk"
}
Is there a similar thing for the generated App Bundle file? How can I change the generated App Bundle filename?

You could use something like this:
defaultConfig {
applicationId "com.test.app"
versionCode 1
versionName "1.0"
setProperty("archivesBaseName", applicationId + "-v" + versionCode + "(" + versionName + ")")
}

As a more generic way to Martin Zeitlers answer the following will listen for added tasks, then insert rename tasks for any bundle* task that gets added.
Just add it to the bottom of your build.gradle file.
Note: It will add more tasks than necessary, but those tasks will be skipped since they don't match any folder. e.g. > Task :app:renameBundleDevelopmentDebugResourcesAab NO-SOURCE
tasks.whenTaskAdded { task ->
if (task.name.startsWith("bundle")) {
def renameTaskName = "rename${task.name.capitalize()}Aab"
def flavor = task.name.substring("bundle".length()).uncapitalize()
tasks.create(renameTaskName, Copy) {
def path = "${buildDir}/outputs/bundle/${flavor}/"
from(path)
include "app.aab"
destinationDir file("${buildDir}/outputs/renamedBundle/")
rename "app.aab", "${flavor}.aab"
}
task.finalizedBy(renameTaskName)
}
}

Solution from #SaXXuM works great! Task is not necessary for renaming artifact. You can call setProperty() directly in the android {} block. I prefer to have in the file name:
app id
module name
version name
version code
date
build type
This is how I use it in my projects:
build.gradle:
apply from: "../utils.gradle"
android {
...
setProperty("archivesBaseName", getArtifactName(defaultConfig))
}
utils.gradle:
ext.getArtifactName = {
defaultConfig ->
def date = new Date().format("yyyyMMdd")
return defaultConfig.applicationId + "-" + project.name + "-" + defaultConfig.versionName + "-" + defaultConfig.versionCode + "-" + date
}
The result is:
com.example-app-1.2.0-10200000-20191206-release.aab
It works for both - APK and AAB.

Now I've wrote kind of a Exec template for cross-platform CLI execution, no matter what the commandLine is. My RenameTask can detect Linux & Windows, as well as release & debug.
Property archivesBaseName needs to be defined in defaultConfig:
android {
defaultConfig {
setProperty("archivesBaseName", "SomeApp_" + "1.0.0")
}
}
RenameTask extends Exec performs the renaming (not to be confused with type: Rename):
import javax.inject.Inject
/**
* App Bundle RenameTask
* #author Martin Zeitler
**/
class RenameTask extends Exec {
private String buildType
#Inject RenameTask(String value) {this.setBuildType(value)}
#Input String getBuildType() {return this.buildType}
void setBuildType(String value) {this.buildType = value}
#Override
#TaskAction
void exec() {
def baseName = getProject().getProperty('archivesBaseName')
def basePath = getProject().getProjectDir().getAbsolutePath()
def bundlePath = "${basePath}/build/outputs/bundle/${this.getBuildType()}"
def srcFile = "${bundlePath}/${baseName}-${this.getBuildType()}.aab"
def dstFile = "${bundlePath}/${baseName}.aab"
def os = org.gradle.internal.os.OperatingSystem.current()
if (os.isUnix() || os.isLinux() || os.isMacOsX()) {
commandLine "mv -v ${srcFile} ${dstFile}".split(" ")
} else if (os.isWindows()) {
commandLine "ren ${srcFile} ${dstFile}".split(" ")
} else {
throw new GradleException("Cannot move AAB with ${os.getName()}.")
}
super.exec()
}
}
And it finalizes two other tasks:
// it defines tasks :renameBundleRelease & :renameBundleDebug
task renameBundleRelease(type: RenameTask, constructorArgs: ['release'])
task renameBundleDebug(type: RenameTask, constructorArgs: ['debug'])
// it sets finalizedBy for :bundleRelease & :bundleDebug
tasks.whenTaskAdded { task ->
switch (task.name) {
case 'bundleRelease': task.finalizedBy renameBundleRelease; break
case 'bundleDebug': task.finalizedBy renameBundleDebug; break
}
}
The advance is, that it leaves nothing behind and one can move the files wherever one wants.

Why no one is using existing gradle tasks for this?
There is a gradle task with the type FinalizeBundleTask and it is called as the last step of bundle generation and it is doing two things:
Signing generated AAB package
Move and rename AAB package where was requested
All You need to do is just to change the "output" of this task to any that You want. This task contains a property finalBundleFile - full path to the final AAB package.
I'm using it something like that:
applicationVariants.all {
outputs.all {
// AAB file name that You want. Falvor name also can be accessed here.
val aabPackageName = "$App-v$versionName($versionCode).aab"
// Get final bundle task name for this variant
val bundleFinalizeTaskName = StringBuilder("sign").run {
// Add each flavor dimension for this variant here
productFlavors.forEach {
append(it.name.capitalizeAsciiOnly())
}
// Add build type of this variant
append(buildType.name.capitalizeAsciiOnly())
append("Bundle")
toString()
}
tasks.named(bundleFinalizeTaskName, FinalizeBundleTask::class.java) {
val file = finalBundleFile.asFile.get()
val finalFile = File(file.parentFile, aabPackageName)
finalBundleFile.set(finalFile)
}
}
}
It works perfectly with any flavors, dimensions, and buildTypes. No any additional tasks, works with any path set for output in Toolbar -> Generate signed Bundle, a unique name can be set for any flavor.

I've found a much better option to auto increment your app versioning and auto renaming when you generate an apk / aab. Solution as below (do remember to create "version.properties" file on your root folder:
android {
...
...
Properties versionProps = new Properties()
def versionPropsFile = file("${project.rootDir}/version.properties")
versionProps.load(new FileInputStream(versionPropsFile))
def value = 0
def runTasks = gradle.startParameter.taskNames
if ('assemble' in runTasks || 'assembleRelease' in runTasks) {
value = 1
}
def versionMajor = 1
def versionPatch = versionProps['VERSION_PATCH'].toInteger() + value
def versionBuild = versionProps['VERSION_BUILD'].toInteger() + 1
def versionNumber = versionProps['VERSION_NUMBER'].toInteger() + value
versionProps['VERSION_PATCH'] = versionPatch.toString()
versionProps['VERSION_BUILD'] = versionBuild.toString()
versionProps['VERSION_NUMBER'] = versionNumber.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
applicationId "com.your.applicationname"
versionCode versionNumber
versionName "${versionMajor}.${versionPatch}.${versionBuild}(${versionNumber})"
archivesBaseName = versionName
minSdkVersion 26
targetSdkVersion 29
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.release
setProperty("archivesBaseName","${applicationId}-v${versionName}")
...
}
Credits to this website and this post

Based on Martin Zeitler's answer I did this on Windows:
Please note that on my setup, .aab files are created in release folder and it deletes everything else in that folder as per this bug report.
In my app's module gradle:
apply from: "../utils.gradle"
...
tasks.whenTaskAdded { task ->
switch (task.name) {
case 'bundleRelease':
task.finalizedBy renameBundle
break
}
}
And in utils.gradle:
task renameBundle (type: Exec) {
def baseName = getProperty('archivesBaseName')
def stdout = new ByteArrayOutputStream()
def stderr = new ByteArrayOutputStream()
commandLine "copy.bat", rootProject.getProjectDir().getAbsolutePath() + "\\release\\${baseName}-release.aab", "<MY_AAB_PATH>\\${baseName}.aab", "D:\\Android\\studio\\release"
workingDir = rootProject.getProjectDir().getAbsolutePath()
ignoreExitValue true
standardOutput stdout
errorOutput stderr
doLast {
if (execResult.getExitValue() == 0) {
println ":${project.name}:${name} > ${stdout.toString()}"
} else {
println ":${project.name}:${name} > ${stderr.toString()}"
}
}
}
The copy.bat is created in project's folder and contains this:
COPY %1 %2
RMDIR /Q/S %3
Be careful with 3rd argument to make sure you don't use a folder that's important to you.
EDIT: Why a .BAT for 2 commands you might ask. If you try commandLine "copy", ... on Windows it results in "system does not recognize the command copy". Put anything, like COPY, REN, RENAME, etc, won't work.

Related

Android Studio Gradle specifying output directory specific to Flavor

I have implemented Flavors in Android Studio and am now trying to place each Flavor in it's own directory, with its own unique name - sadly with a name different, in some cases, than the flavor name. :(
We have tools depending on it being the same, so if I can pull that off in gradle, all the better.
I have a sample that is using the version name suffix value as the directory name and that works. But what I would like to do is specify a value somewhere in the flavor config that would be used, however I find that when you set a property with the same name the last one wins - rather than each being used as specified in the config.
So, for example, lets say I have two Flavors : Jimbo and Randolph. However I want to place the Jimbo.apk in the "jimmy" folder and the Randolph.apk in the "randy" folder. How can I specify a value (directory) for each that will be picked up and used to store the generated APK? To add to the complexity I am renaming the APK current in the applicationVariants.all .
In the code below I am looking to somehow replace the versionNameSuffix with a variable I can somehow specify.
Here is what I have:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.mycompany.default"
minSdkVersion 14
targetSdkVersion 23
versionCode 11
versionName "1.0.11"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
versionNameSuffix 'randy'
}
Jimbo {
applicationId 'com.mycompany.jimmy'
versionNameSuffix 'jimmy'
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].versionNameSuffix + "/"
logger.error("Path = " + path)
def SEP = "-"
def flavor = variant.productFlavors[0].name
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
}
}
}
dependencies {
}
}
UPDATE
Per the question of using a task, I tried this approach but the problem of setting the directory remains - using a property (archivesBaseName) the last one set is used so all the files are copied to that directory. Here is a sample of that. Since I have upwards of 100 flavors to create I want each sent to it's own directory and config driven. Here is what I tried:
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
setProperty("archivesBaseName", "randy")
}
Jimbo {
applicationId 'com.mycompany.jimmy'
setProperty("archivesBaseName", "jimmy")
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + archivesBaseName + "/"
logger.error("Path = " + path)
def SEP = "-"
def flavor = variant.productFlavors[0].name
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
def copyApkTask = tasks.create(name: "copy" + variant.name + "Apk") {
copy {
def newName = newApkName + ".apk"
logger.error("from = " + newName)
logger.error("into = " + path)
logger.error("old name = " + version + SEP + flavor + ".apk")
logger.error("new name = " + flavor + ".apk")
from newName
into path
rename (version + SEP + flavor + ".apk", flavor + ".apk")
}
}
copyApkTask.mustRunAfter variant.assemble
}
}
In the example above I added a task to additionally copy the APK with different name to a flavor specific directory. All the APKs end up copied to the last specified `archivesBaseName, which is "jimmy". So last one wins. I was hoping it would act like a variable. I would prefer not to have to have 100+ if statements to do this and would prefer to do this in Gradle. I am starting to wonder if I will need to make an external Ant call to make this all work.
Ok, in the end this specific link REALLY helped on the variable assignment which is what I needed:
Android Studio: Gradle Product Flavors: Define custom properties
Basically you can assign variables within the flavor. Here is what I ended up doing, which actually went a bit further than when I started, since now I can use the Flavor as the APK name or specify one (I know, it is messed up, but history can be that way!):
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.mycompany.default"
minSdkVersion 14
targetSdkVersion 23
versionCode 11
versionName "1.0.11"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
productFlavors.whenObjectAdded { flavor ->
// Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
flavor.ext.set('directoryPath', '')
flavor.ext.set('apkName', '')
}
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
directoryPath = 'randy'
apkName = 'RandyBoy' // If you want the APK name be different than the flavor
}
Jimbo {
applicationId 'com.mycompany.jimmy'
directoryPath = 'jimmy'
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.productFlavors[0].directoryPath + "/"
def SEP = "-"
def apkName = variant.productFlavors[0].apkName
def flavor = variant.productFlavors[0].name
if (apkName != '')
flavor = apkName;
def version = variant.versionCode
def newApkName = path + version + SEP + flavor
logger.error("newApkName = " + newApkName)
output.outputFile = new File(newApkName + ".apk")
}
}
}
dependencies {
}
}
So productFlavors.whenObjectAdded sets the default values for each flavor, which are then overridden by each flavor. In the applicationVariants.all a check is made to see if the apkName has been overridden, if so it uses it, otherwise it uses the flavor name (and the version code is tacked in front of it). The directory is set directly by the flavor.
Big thanks to #lionscribe. He got me thinking this thru more clearly.
The problem is that setProperty is setting the Project property, so it is always being overwritten. A simple solution is to rather use the Varients Extra Property. Something like this.
productFlavors {
Randolph {
applicationId 'com.mycompany.randy'
ext.archivesBaseName = "randy" }
Jimbo {
applicationId 'com.mycompany.jimmy'
ext.archivesBaseName=jimmy" }
}
Then you will access it in the task as
def path = "C:/AndroidBuilds/MyCompany.Build/" + variant.ext.archivesBaseName + "/"
I haven't tested it, and it may have a bug, and need some tweaking.
Update
This is not enough, as Gradle will set to the ext property of the flavor object, only if it is defined in the flavor object. Otherwise it will set it in the parent or root object, which is the project. So for this to work, we first have to define the property in the flavor object. This can be done as #Stephen has answered below. Follow his tested method.
There are 3 more options:
1. Use a different variable name for each flavir, by pre-pending the flavor name, like "Jimbo_archivesBaseName". Then access it using property(flavorName + "_archivesBaseName);
2. Use a global HashMap variable, setting a path for each flavor name.
3. Using a function, that returns a path based on flavor name.

Apk version name - jenkins

build.gradle
compileSdkVersion 23
buildToolsVersion "25.0.2"
applicationVariants.all { variant ->
def flavor = variant.mergedFlavor
if (variant.buildType.isDebuggable()) {
flavor.versionName = "Beta Revision: ${svnRevisionDebug()}";
flavor.versionCode = 1;
} else {
if (project.hasProperty('projVersion')) {
println "Assemble release with parameter " + project.projVersion;
flavor.versionName = ""+ project.projVersion;
} else {
flavor.versionName = '10.0.0'
}
flavor.versionCode = 1;
}
}
On Jenkins execute shell
./gradlew assembleRelease -PprojVersion=123
Jenkins Output console
+ ./gradlew assembleRelease -PprojVersion=123
Incremental java compilation is an incubating feature.
Unix runtime
Assemble release with parameter 123
The output
Unix runtime
Come from svnRevisionDebug()
def svnRevisionDebug() {
if (System.properties['os.name'].toLowerCase().contains('windows')) {
println "Windows runtime"
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'svn'
args = ['info', '-r', 'HEAD']
standardOutput = os
}
def outputAsString = os.toString()
def matchLastChangedRev = outputAsString =~ /Last Changed Rev:(\d+)/
ext.svnRev = "${matchLastChangedRev[0][1]}".toInteger()
}
return svnRev
} else {
println "Unix runtime"
def p = ['/bin/bash', '-c', /svn info -r HEAD | grep '^Revision:' | sed -e 's\/^Revision: \/\/'/].execute()
p.waitFor()
return p.text.trim()
}
}
But when i decompile the apk from the manifest I see
android:versionName="1.0"
Wtf?
Could be the method used with applicationVariants.all that is called multiple times while creating apk ( you see jenkins log "Unix runtime" and "Assemble release with parameter"
Which is the right way to Inject a parameter in versionName ?
When passing a property as parameter, you should be able to access the value with the variable name itself.
Try to change project.projVersion in your gradle file to projVersion, that should work.
Meaning that the resulting line would be
println "Assemble release with parameter " + projVersion;
flavor.versionName = "" + projVersion;
Or you can try
flavor.versionName = projVersion.toString()

Auto increment versioncode only on releases

Currently I'm trying to improve our buildscript with auto incrementing the versioncode so our QA team has got a clue what build they are testing and can log against a specific versioncode. We have got 3 productFlavors (staging, qa, production) and 2 signing configs (debug, release).
I looked into different solutions:
How to autoincrement versionCode in Android Gradle
How to autoincrement versionCode in Android Gradle
Autoincrement VersionCode with gradle extra properties
Based on those answers I've built a versioncode incrementor using a version.properties file.
Now the problem arises that for EVERY productFlavor and SigningConfig combination (+ the gradle sync in android studio) a new versioncode is generated. I want the versioncode to increment whenever I hit the play button to create a qaRelease build. So our buildcycle would be:
development (never change the versioncode)
qual (Update the versioncode)
production (never change the versioncode)
How I solved it:
I created a file version.properties in the root of my android project and added the versioncode in it.
Then I created method called getCurrentVersionCode() in my build.gradle file that reads the current versioncode.
Code:
def getCurrentVersionCode() {
def versionPropsFile = file('version.properties')
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
return versionProps['version_code'].toInteger()
}
I also created a method to generate a new code:
Code:
// http://stackoverflow.com/questions/30523393/auto-increment-versioncode-only-on-releases
//
def setUpdatedVersionCode() {
def versionPropsFile = file('version.properties')
def Properties versionProps = new Properties()
def code = getCurrentVersionCode() + 1
versionProps['version_code'] = code.toString()
versionProps['version_name'] = generateVersionName()
versionProps.store(versionPropsFile.newWriter(), null)
}
and then I created a task that triggers on a QA build.
Code:
task('increaseVersionCode') << {
setUpdatedVersionCode()
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleQaRelease') {
task.dependsOn 'increaseVersionCode'
}
}
That way it saves the versioncode in a separate file so I don't need to edit my build.gradle.
You could try something like that
import java.util.regex.Pattern
task('increaseVersionCode') << {
... // You could code in your increment system, for eg
// Using build.gradle (recommended)
def buildFile = file("build.gradle")
def pattern = Pattern.compile("versionCode\\s+(\\d+)")
def manifestText = buildFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
def versionCode = Integer.parseInt(matcher.group(1))
def manifestContent = matcher.replaceAll("versionCode " + ++versionCode)
buildFile.write(manifestContent)
// Using manifest
def manifestFile = file('AndroidManifest.xml')
def matcher = Pattern.compile('versionCode=\"(\\d+)\"')
.matcher(manifestFile.getText())
matcher.find()
def manifestContent = matcher.replaceAll('versionCode=\"' +
++Integer.parseInt(matcher.group(1)) + '\"')
manifestFile.write(manifestContent)
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleQaRelease') {
task.dependsOn 'increaseVersionCode'
}
}
You can adapt the 'assembleQaRelease' to increment version code to the wanted task.

Android Gradle: Dynamically change versionName at build time

I'm trying to emulate Maven release plugin in Android by using a customized version of gradle-release plugin: https://github.com/townsfolk/gradle-release
The interesting steps are:
Check uncommitted changes
Step version code and remove -SNAPSHOT
suffix from version name
Build
Step version name and add -SNAPSHOT
suffix for next development version
However the generated APK always has the previous versions (i.e. 1.0.0-SNAPSHOT instead of 1.0.0).
Version numbers are stored and correctly updated in gradle.properties, so I'm assuming that I need to update the versions in the data model as well for the changes to take effect.
My android plugin config:
defaultConfig {
versionCode versionCode as int // taken from gradle.properties
versionName versionName // taken from gradle.properties
minSdkVersion 10
targetSdkVersion 19
}
Things I tried:
preBuild << {
android.applicationVariants.each { variant ->
variant.versionName = versionName
}
}
But there's no versionName in a variant.
preBuild << {
android.buildTypes.each { type ->
type.versionName = versionName
}
}
But there's no versionName in a type.
preBuild << {
android.productFlavors.each { flavor ->
flavor.versionName = versionName
}
}
But there are no flavors in my app (plain debug and release build types only).
My alternative is to write a bash/bat script to step the versions before invoking Gradle, which pretty much defeats the purpose of using Groovy to improve build customization.
How can I update versions dynamically in the Android Gradle plugin in the execution phase?
That's what buildTypes are for. What you're describing is a release build, IMO.
Here's an example: when executing assembleDebug it will give you a snapshot build, and executing assembleRelease will give you a clean build without any suffix and incremented version number. The next debug build will also use the incremented number.
The following is a fully functional build when the files are created in a folder. It should also work with flavors, but that's just a side product :). Gradle 2.2.1, Android plugin 1.1.3
build.gradle
apply plugin: 'com.android.application'
apply from: 'auto-version.gradle'
buildscript {
repositories { jcenter() }
dependencies { classpath 'com.android.tools.build:gradle:1.1.3' }
}
android {
buildToolsVersion = "21.1.2"
compileSdkVersion = "android-21"
buildTypes {
debug {
versionNameSuffix "-SNAPSHOT"
}
}
}
println "config code: ${calculateVersionCode()}, name: ${calculateVersionName()}"
src/main/AndroidManifest.xml
<manifest package="com.example" />
auto-version.gradle
ext {
versionFile = new File(project.rootDir, 'version.properties')
calculateVersionName = {
def version = readVersion()
return "${version['major']}.${version['minor']}.${version['build']}"
}
calculateVersionCode = {
def version = readVersion()
def major = version['major'] as int // 1..∞
def minor = version['minor'] as int // 0..99
def build = version['build'] as int // 0..999
return (major * 100 + minor) * 1000 + build
}
}
Properties readVersion() {
def version = new Properties()
def stream
try {
stream = new FileInputStream(versionFile)
version.load(stream)
} catch (FileNotFoundException ignore) {
} finally {
if (stream != null) stream.close()
}
// safety defaults in case file is missing
if(!version['major']) version['major'] = "1"
if(!version['minor']) version['minor'] = "0"
if(!version['build']) version['build'] = "0"
return version
}
void incrementVersionNumber() {
def version = readVersion()
// careful with the types, culprits: "9"++ = ":", "9" + 1 = "91"
def build = version['build'] as int
build++
version['build'] = build.toString()
def stream = new FileOutputStream(versionFile)
try {
version.store(stream, null)
} finally {
stream.close()
}
}
task incrementVersion {
description "Increments build counter in ${versionFile}"
doFirst {
incrementVersionNumber()
}
}
if (plugins.hasPlugin('android') || plugins.hasPlugin('android-library')) {
android {
defaultConfig {
versionName = calculateVersionName()
versionCode = calculateVersionCode()
}
afterEvaluate {
def autoIncrementVariant = { variant ->
if (variant.buildType.name == buildTypes.release.name) { // don't increment on debug builds
variant.preBuild.dependsOn incrementVersion
incrementVersion.doLast {
variant.mergedFlavor.versionName = calculateVersionName()
variant.mergedFlavor.versionCode = calculateVersionCode()
}
}
}
if (plugins.hasPlugin('android')) {
applicationVariants.all { variant -> autoIncrementVariant(variant) }
}
if (plugins.hasPlugin('android-library')) {
libraryVariants.all { variant -> autoIncrementVariant(variant) }
}
}
}
}
Execute gradle assembleDebug to build normally, gradle assembleRelease to increment and build, and gradle incrementVersion to just increment.
Note: be careful with gradle assemble because the order of assembleDebug and assembleRelease will yield different results.
Check the generated files in the build directory to see if the values are to your liking.
Manual execution (from comments)
It is possible you have multiple flavors in which case the version is incremented multiple times because multiple variants match the release build type. The original quesion was for no flavors. If you want to have more control when the version number is incremented just remove the afterEvaluate block and call the incrementVersion task whenever you want:
gradle incrementVersion assembleFreeRelease assemblePaidRelease
(The above manual execution is an untested idea.)
Check uncommitted changes
The "Check uncommitted changes" are not covered in this answer, that's another game. You could hook on to tasks.preBuild.doFirst { /*fail here if uncommited changes*/ } if I understand correctly. But that highly depends on your version control. Ask another question for more!
I needed to append current git commit count of code revision to the version name. Its real handy in many situation. I ended up with below simple gradle file
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
def gitCommitCount = "git rev-list HEAD --count".execute().text.trim()
defaultConfig {
applicationId "my.app.package.name"
minSdkVersion 16
targetSdkVersion 21
versionCode 6
versionName "0.8"
}
buildTypes {
debug {
versionNameSuffix ".${gitCommitCount}"
}
release {
versionNameSuffix ".${gitCommitCount}"
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Similar to gitCommitCount, You can generate variables of your own to customise version name. As i am just executing a terminal command to store its result in a variable.
This doesn't directly address your question of how to completely change the versionName, but this is what I use to append a suffix for my buildTypes:
defaultConfig {
versionName "1.0"
}
buildTypes {
debug {
versionNameSuffix "-SNAPSHOT"
}
}
I just used Javanator's answer and modified it a bit so that commit count not only helps in changing the name but also makes sure that version code also remains unique. Here is a sample of what I did (Maybe a couple of things can be optimized, but nevertheless does the job for me) :
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
def gitCommitCount = "git rev-list HEAD --count".execute().text.trim().toBigInteger()
project.ext.set("versionCode", gitCommitCount)
project.ext.set("versionNameSuffix", "(${gitCommitCount})")
defaultConfig {
applicationId "my.app.package.name"
minSdkVersion 15
targetSdkVersion 25
versionCode project.versionCode
versionName "1.0"
versionNameSuffix project.versionNameSuffix
setProperty("archivesBaseName", "MyProject-$versionName")
....
}
signingConfigs {
config {
.........
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
packagingOptions {
.....
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(
output.outputFile.parent,
output.outputFile.name.replace(".apk", "-${variant.versionName}.apk"))
}
}
}
Edit :
The last bit could also be like
applicationVariants.all { variant ->
if (variant.name.contains('release')) {
variant.outputs.each { output ->
variant.outputs.all {
outputFileName = "MyProject-${variant.versionName}${variant.versionCode}.apk"
}
}
}
}
I was facing similar need of having separate build logic for release and non-release builds.
Apart from different versioning, I had to use a different set of dependencies, even different repositories.
None of the available plugins had all of the features that I needed, so I developed my own solution, based on simple approach - command line argument.
You can pass a command line parameter when invoking gradle build script like this:
gradle build -PmyParameter=myValue
or in my case
gradle build -PisRelease=true
Gradle will parse it, and it would automagically be available as a property of the project object.
You could then use it like this:
if (project.hasProperty('isRelease') && project.isRelease) {
// Here be the logic!
}
I extracted this logic into a separate plugin, and I've been successfully using it across different projects.
Although this doesn't answer your question directly, I hope I gave you another angle to think about the problem and another possible solution.
Late to this question but you can try the below way to attach the dynamic build suffix to the versionName in build.gradle .
def buildCode = (int)(((new Date().getTime()/1000) - 1451606400) / 10)
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
...
versionName "0.1.${buildCode}"
...
}
}
Both version name and version code can be generated dynamically as follows.
Comments in code.
/**
* computedVersionCode()
* do not name this to getVersionCode. getVersionCode conflicts with the automatic getter of versionCode
* version code is an int a value between 0 and max int value 2147483647 is expected.
* This function returns at int in yyyMMddHH format
* For example, 2022061121 for 11 June 2022 between 21:00 to 21:59
* This gives a new versioncode for every different hour of day and same code within same hour of hour of day
* Max int value is 2147483647. So after year 2147 it will overflow to -ve values.
* max value in year 2147 will be 2147121223 so Lot of scope of manually incrementing up-to 2147483647 will be there.
* #return an int corresponding to current hour in yyyyMMddHH format
*/
static def computedVersionCode() {
def date = new Date()
def formattedDate = date.format('yyyyMMddHH')
int versionCodeInt = (int) Long.parseLong(formattedDate)
return versionCodeInt
}
/**
* computedVersionCode2()
* do not name this to getVersionCode. getVersionCode conflicts with automatic getter of versionCode
* version code is an int a value between 0 and Max int value 2147483647 is expected.
* This function returns total hours since epoch
* For example, it returns 459711 for 11 June 2022 at 21:21 IST
* This gives a new versioncode for every different hour
* Max int value is 2147483647. This format is good till 09-Oct-246953 12:30:00 PM
*
* #return hours since epoch which can be used as version code
*/
static def computedVersionCode2() {
long millisSinceEpoch = System.currentTimeMillis();
long hoursSinceEpoch = millisSinceEpoch/(3600*1000);
int hoursSinceEpochInt = (int)hoursSinceEpoch;
//Hours since epoch changes every hour automatically.
//If data type int remains of same size forever this value will be good till year 4419.
return hoursSinceEpochInt;
}
static def computedVersionSuffix() {
def date = new Date()
def formattedDate = date.format('yyyyMMdd.HH.mm')
return formattedDate
}
android {
compileSdkVersion 32
defaultConfig {
...
versionCode computedVersionCode()
versionName "1.0.8.".concat(computedVersionSuffix())
....
}

Automatic versioning of Android build using git describe with Gradle

I have searched extensively, but likely due to the newness of Android Studio and Gradle. I haven't found any description of how to do this. I want to do basically exactly what is described in this post, but with Android Studio, Gradle and Windows rather than Eclipse and Linux.
Put the following in your build.gradle file for the project. There's no need to modify the manifest directly: Google provided the necessary hooks into their configuration.
def getVersionCode = { ->
try {
def code = new ByteArrayOutputStream()
exec {
commandLine 'git', 'tag', '--list'
standardOutput = code
}
return code.toString().split("\n").size()
}
catch (ignored) {
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
android {
defaultConfig {
versionCode getVersionCode()
versionName getVersionName()
}
}
Note that if git is not installed on the machine, or there is some other error getting the version name/code, it will default to what is in your android manifest.
After seeing moveaway00's answer and Avinash R's comment on that answer, I've ended up using this:
apply plugin: 'android'
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--first-parent', '--count', 'master'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
return null;
}
}
android {
defaultConfig {
versionCode getVersionCode()
versionName getVersionName()
}
}
I've edited moveaway00's code to also include Avinash R's comment: the version code is now the number of commits since master, as this is what the version code is supposed to be.
Note that I didn't need to specify the version code and the version name in the manifest, Gradle took care of it.
A more proper and lean way to achieve the result which gained traction lately would be to use grgit integration, which uses JGit Java libray. As it uses JGit it doesn't even require git to be installed to work (which simplifies things in build pipelines).
Here's a basic example showing a similar (but with some additional information in gitVersionName string) solution:
plugins {
id 'org.ajoberstar.grgit' version '4.1.1'
}
ext {
gitVersionCode = grgit.tag.list().size()
gitVersionName = grgit.describe(tags: true, always: true)
}
android {
defaultConfig {
versionCode gitVersionCode
versionName gitVersionName
}
}
[...]
As you can see in Grgit API documentation the describe operation provides additional information other than most recent tag reachable in history:
Find the most recent tag that is reachable from HEAD. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.
Anyhow, it won't tell if the state is dirty or not. This information can be easily added by looking at the clean status of the repo, and appending a string if it's not clean.
Yet another way:
https://github.com/gladed/gradle-android-git-version is a new gradle plugin that calculates android-friendly version names and version codes automatically.
It handles a lot of special cases that are not possible using the accepted solution:
version tags for multiple projects in the same repo
expanded version codes like 1002003 for 1.2.3
gradle tasks for easily extracting version info for CI tools
etc.
Disclaimer: I wrote it.
Here is another solution that requires statements instead of functions to access the commandline. Warning: *nix only solution
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
// Auto-incrementing commit count based on counting commits to master (Build #543)
def commitCount = Integer.parseInt('git rev-list master --count'.execute([], project.rootDir).text.trim())
// I want to use git tags as my version names (1.2.2)
def gitCurrentTag = 'git describe --tags --abbrev=0'.execute([], project.rootDir).text.trim()
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.some.app"
minSdkVersion 16
targetSdkVersion 22
versionCode commitCount
versionName gitCurrentTag
buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Another way, using Android Studio (Gradle):
Check out this blog post: http://blog.android-develop.com/2014/09/automatic-versioning-and-increment.html
Here's the implementation from the blog:
android {
defaultConfig {
...
// Fetch the version according to git latest tag and "how far are we from last tag"
def longVersionName = "git -C ${rootDir} describe --tags --long".execute().text.trim()
def (fullVersionTag, versionBuild, gitSha) = longVersionName.tokenize('-')
def(versionMajor, versionMinor, versionPatch) = fullVersionTag.tokenize('.')
// Set the version name
versionName "$versionMajor.$versionMinor.$versionPatch($versionBuild)"
// Turn the version name into a version code
versionCode versionMajor.toInteger() * 100000 +
versionMinor.toInteger() * 10000 +
versionPatch.toInteger() * 1000 +
versionBuild.toInteger()
// Friendly print the version output to the Gradle console
printf("\n--------" + "VERSION DATA--------" + "\n" + "- CODE: " + versionCode + "\n" +
"- NAME: " + versionName + "\n----------------------------\n")
...
}
}
If it can be of any help, I've set up an example Gradle script that uses Git tags and Git describe to achieve this. Here's the code (you can also find it here).
1) First create a versioning.gradle file containing:
import java.text.SimpleDateFormat
/**
* This Gradle script relies on Git tags to generate versions for your Android app
*
* - The Android version NAME is specified in the tag name and it's 3 digits long (example of a valid tag name: "v1.23.45")
* If the tag name is not in a valid format, then the version name will be 0.0.0 and you should fix the tag.
*
* - The Android version CODE is calculated based on the version name (like this: (major * 1000000) + (minor * 10000) + (patch * 100))
*
* - The 4 digits version name is not "public" and the forth number represents the number of commits from the last tag (example: "1.23.45.178")
*
*/
ext {
getGitSha = {
return 'git rev-parse --short HEAD'.execute().text.trim()
}
getBuildTime = {
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
df.setTimeZone(TimeZone.getTimeZone("UTC"))
return df.format(new Date())
}
/**
* Git describe returns the following: [GIT_TAG_NAME]-[BUILD_NUMBER]-[GIT_SHA]
*/
getAndroidGitDescribe = {
return "git -C ${rootDir} describe --tags --long".execute().text.trim()
}
/**
* Returns the current Git branch name
*/
getGitBranch = {
return "git rev-parse --abbrev-ref HEAD".execute().text.trim()
}
/**
* Returns the full version name in the format: MM.mm.pp.ccc
*
* The version name is retrieved from the tag name which must be in the format: vMM.mm.pp, example: "v1.23.45"
*/
getFullVersionName = {
def versionName = "0.0.0.0"
def (tag, buildNumber, gitSha) = getAndroidGitDescribe().tokenize('-')
if (tag && tag.startsWith("v")) {
def version = tag.substring(1)
if (version.tokenize('.').size() == 3) {
versionName = version + '.' + buildNumber
}
}
return versionName
}
/**
* Returns the Android version name
*
* Format "X.Y.Z", without commit number
*/
getAndroidVersionName = {
def fullVersionName = getFullVersionName()
return fullVersionName.substring(0, fullVersionName.lastIndexOf('.'))
}
/**
* Returns the Android version code, deducted from the version name
*
* Integer value calculated from the version name
*/
getAndroidVersionCode = {
def (major, minor, patch) = getAndroidVersionName().tokenize('.')
(major, minor, patch) = [major, minor, patch].collect{it.toInteger()}
return (major * 1000000) + (minor * 10000) + (patch * 100)
}
/**
* Return a pretty-printable string containing a summary of the version info
*/
getVersionInfo = {
return "\nVERSION INFO:\n\tFull version name: " + getFullVersionName() +
"\n\tAndroid version name: " + getAndroidVersionName() +
"\n\tAndroid version code: " + getAndroidVersionCode() +
"\n\tAndroid Git branch: " + getGitBranch() +
"\n\tAndroid Git describe: " + getAndroidGitDescribe() +
"\n\tGit SHA: " + getGitSha() +
"\n\tBuild Time: " + getBuildTime() + "\n"
}
// Print version info at build time
println(getVersionInfo());
}
2) Then edit your app/build.gradle to use it like this:
import groovy.json.StringEscapeUtils;
apply plugin: 'com.android.application' // << Apply the plugin
android {
configurations {
// ...
}
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 17
targetSdkVersion 22
applicationId "app.example.com"
versionCode getAndroidVersionCode() // << Use the plugin!
versionName getAndroidVersionName() // << Use the plugin!
// Build config constants
buildConfigField "String", "GIT_SHA", "\"${getGitSha()}\""
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
buildConfigField "String", "FULL_VERSION_NAME", "\"${getVersionName()}\""
buildConfigField "String", "VERSION_DESCRIPTION", "\"${StringEscapeUtils.escapeJava(getVersionInfo())}\""
}
signingConfigs {
config {
keyAlias 'MyKeyAlias'
keyPassword 'MyKeyPassword'
storeFile file('my_key_store.keystore')
storePassword 'MyKeyStorePassword'
}
}
buildTypes {
debug {
minifyEnabled false
debuggable true
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
debuggable false
}
}
productFlavors {
// ...
}
dependencies {
// ...
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
/**
* Save a build.info file
*/
task saveBuildInfo {
def buildInfo = getVersionInfo()
def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0]
assetsDir.mkdirs()
def buildInfoFile = new File(assetsDir, 'build.info')
buildInfoFile.write(buildInfo)
}
gradle.projectsEvaluated {
assemble.dependsOn(saveBuildInfo)
}
The most important part is to apply the plugin
apply plugin: 'com.android.application'
And then use it for the android version name and code
versionCode getAndroidVersionCode()
versionName getAndroidVersionName()
Based on Léo Lam's answer and my earlier explorations on the same solution for ant, I have devised a purely cross-platform solution using jgit:
(original source)
File: git-version.gradle
buildscript {
dependencies {
//noinspection GradleDynamicVersion
classpath "org.eclipse.jgit:org.eclipse.jgit:4.1.1.+"
}
repositories {
jcenter()
}
}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import static org.eclipse.jgit.lib.Constants.MASTER
def git = Git.wrap(new FileRepositoryBuilder()
.readEnvironment()
.findGitDir()
.build())
ext.readVersionCode = {
def repo = git.getRepository()
def walk = new RevWalk(repo)
walk.withCloseable {
def head = walk.parseCommit(repo.getRef(MASTER).getObjectId())
def count = 0
while (head != null) {
count++
def parents = head.getParents()
if (parents != null && parents.length > 0) {
head = walk.parseCommit(parents[0])
} else {
head = null
}
}
walk.dispose()
println("using version name: $count")
return count
}
}
ext.readVersionName = {
def tag = git.describe().setLong(false).call()
def clean = git.status().call().isClean()
def version = tag + (clean ? '' : '-dirty')
println("using version code: $version")
return version
}
The usage will be:
apply from: 'git-version.gradle'
android {
...
defaultConfig {
...
versionCode readVersionCode()
versionName readVersionName()
...
}
...
}
Define simple function in gradle file:
def getVersion(){
def out = new ByteArrayOutputStream();
exec {
executable = 'git'
args = ['describe', '--tags', '--abbrev=0']
standardOutput = out
}
return out.toString().replace('\n','')
}
Use it:
project.version = getVersion()
This is a slightly changed version of Diego's answer, which fulfils my desire to have version name in following style:
{latest tag} - {short hash of current commit} - {time of current commit}
import java.text.SimpleDateFormat
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.ajoberstar.grgit:grgit-core:3.1.1'
}
}
/**
* Version name will be in following format:
*
* "{latest release tag}-{short commit hash of current commit}-{time of current commit}"
*
* Example: 1.6.0-5ae9b86-2019-07-04-13:20
*/
ext {
git = org.ajoberstar.grgit.Grgit.open(currentDir: projectDir)
listOfTags = git.tag.list()
noTags = listOfTags.isEmpty()
head = git.head()
if (noTags) {
gitVersionCode = 0
gitVersionName = "no-tag-${head.abbreviatedId}-${head.time}"
} else {
tagNames = listOfTags.collect { git.describe(commit: it.commit, tags: true) }
mostRecentVersion = mostRecentVersion(tagNames)
def date = new SimpleDateFormat('yyyy-MM-dd-HH:mm').format(new Date(head.time * 1000))
gitVersionCode = listOfTags.size()
gitVersionName = "$mostRecentVersion-${head.abbreviatedId}-${date}"
}
}
/**
* Shamelessly stolen from StackOverflow.
*/
static String mostRecentVersion(List versions) {
def sorted = versions.sort(false) { a, b ->
List verA = a.tokenize('.')
List verB = b.tokenize('.')
def commonIndices = Math.min(verA.size(), verB.size())
for (int i = 0; i < commonIndices; ++i) {
def numA = verA[i].toInteger()
def numB = verB[i].toInteger()
if (numA != numB) {
return numA <=> numB
}
}
// If we got this far then all the common indices are identical, so whichever version is longer must be more recent
verA.size() <=> verB.size()
}
// println "Sorted versions: $sorted"
sorted[-1]
}
task printVersion() {
println("Version Code: $gitVersionCode")
println("Version Name: $gitVersionName")
}
Assuming you have also specified versionNameSuffix in app module's build.gradle following way:
android {
...
productFlavors {
debug {
versionCode gitVersionCode
versionName gitVersionName
versionNameSuffix '-DEBUG'
...
}
// ... other flavors here
}
}
Then this will be the version name:

Categories

Resources