Related
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.
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()
I have a task that updates my app's version code, called changeVersionCode. This task runs before my Android build tasks (assembleRelease), but obviously it happens after the android { } closure. This seems to mean that versionCode is set and cannot be changed even when changeVersionCode runs.
Here's a simplified build script that demonstrates how I have tried to approach this problem:
// .version is a file that starts at "1" the first time I call this
def loadVersionCode() {
// fetch version code from a file, '.version'
return loadVersionCodeFromFile('.version')
}
def incrementVersionCode() {
// fetch version code, update it, and save it back to a file
def newVersion = loadVersionCode() + 1
saveVersionCodeToFile('.version', newVersion)
}
apply plugin: 'com.android.application'
android {
// ... snip ...
defaultConfig {
// Set the version code to contents of .version (eg. 1)
versionCode loadVersionCode()
// ...
}
}
task incrementVersionCode << {
println "Old version code: " + android.defaultConfig.versionCode // prints 1
incrementVersionCode()
def newVersion = loadVersion() // this now returns 2
android.defaultConfig.versionCode = loadVersionCode()
// Also tried:
// android.defaultConfig.versionCode loadVersionCode()
println "New version code: " + android.defaultConfig.versionCode // prints 2
// android.defaultConfig.versionCode is now 2, but APK still has version 1 (until next time I run gradle)
}
Then:
# Build an APK with versionCode 1
$ ./gradlew assembleRelease
# This seems to change versionCode to 2, but still builds an APK with versionCode 1
#
# Prints:
# Old version code: 1
# New version code: 2
$ ./gradlew incrementVersionCode assembleRelease
I am using:
Gradle 2.5
Groovy 2.3.10
Ant 1.9.3
Java 1.8.0_45
Mac OS X 10.10.5
Android build tools 22.0.1
Is there any way I can change my version code from a task before invoking Android build tasks?
How to configure versionCode before a task is launched
You can use the DSL tasks.whenTaskAdded. You can read the official doc, chapter 58.6.2. Task creation.
You can receive a notification immediately after a task is added to a project. This can be used to set some default values or add behaviour before the task is made available in the build file.
You can define a task:
task incrementVersionCode << {
//do something
}
Then define the dependency :
tasks.whenTaskAdded { task ->
if (task.name == 'xxxxx') {
task.dependsOn incrementVersionCode
}
}
In your case you can do somenthing like this:
tasks.whenTaskAdded { task ->
if (task.name == 'generateReleaseBuildConfig' || task.name == 'generateDebugBuildConfig') {
task.dependsOn 'increaseVersionCode'
}
}
How to configure versionCode with a function
In the top-level file you can configure a function like this:
ext {
buildVersionCode = {
//...
}
}
In your module/build.gradle you can do somehing like this:
defaultConfig {
versionCode buildVersionCode()
//....
}
Otherwise you can do in your build.gradle something like:
defaultConfig {
//...
versionCode getMyNumber()
}
def getMyNumber() {
return //.... ;
}
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())
....
}
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: