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:
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.
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.
Problem
I want to create a custom gradle test task to only run JUNIT tests and omit Robolectric tests. I am attempting to achieve this task by creating a new test annotation and omitting any tests that include that new annotation.
Error
JUNIT packages are not included when I run the the gradle task.
error: package android.test.suitebuilder.annotation does not exist
import android.test.suitebuilder.annotation.SmallTest;
Details
New Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface RobolectricTest {
}
Gradle File
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
repositories {
mavenCentral()
maven { url 'http://artifactory.ops.am1.qa.ext.bamgrid.com/artifactory/mobile-resources' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.gms:google-services:1.3.0-beta1'
}
}
android {
compileSdkVersion rootProject.ext.compileSDKVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.example"
minSdkVersion 17
targetSdkVersion rootProject.ext.targetSdkVersion
buildConfigField "String", "BUILD_TIME", "\"" + getDateAndTime() + "\""
buildConfigField "String", "VERSION_BUILD", "\"" + project["VERSION_BUILD"] + "\""
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
signingConfigs {
debug {
storeFile file(project.DEBUG_KEYSTORE)
storePassword project.DEBUG_KEYSTORE_PASSWORD
keyAlias project.DEBUG_KEYSTORE_ALIAS
keyPassword project.DEBUG_KEY_PASS
}
release {
storeFile file(project.RELEASE_KEYSTORE)
storePassword project.RELEASE_KEYSTORE_PASSWORD
keyAlias project.RELEASE_KEYSTORE_ALIAS
keyPassword project.RELEASE_KEY_PASS
}
}
sourceSets {
main {
res.srcDirs = ['src/main/res/',
'src/main/abc']
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
testCoverageEnabled = true
debuggable true
signingConfig signingConfigs.debug
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile ('junit:junit:4.12')
testCompile ('org.apache.maven:maven-ant-tasks:2.1.3')
testCompile ('org.robolectric:robolectric:3.0')
testCompile ('org.robolectric:shadows-support-v4:3.0')
}
sourceSets {
unitTest {
java.srcDirs = ['src/test/java']
resources.srcDirs = ['src/test/resources']
}
}
ClassLoader getClassLoader() {
List urls = sourceSets.test.runtimeClasspath.collect {
it.toURI().toURL()
}
return URLClassLoader.newInstance( urls as URL[] )
}
/**
* Filters out files that have specific annotation
* #param map - map of things to filter
* #return - list of acceptable files after filter
*/
List annotationFilter( Map map ) {
map.prefix = map?.prefix ?: '' // prefix: provide convenience for passing in annotation names
ClassLoader loader = classLoader
List result
// filter with annotations
if( !map.includes ) {
result = map?.names
} else {
result = []
map?.names.each { name ->
Class klass = loader.loadClass( name )
map?.includes.each { annotationName ->
String fullName = map.prefix + annotationName
Class annotation = loader.loadClass( fullName ).asSubclass( Annotation )
if( !klass.isAnnotationPresent( annotation ) ) {
result << name
}
}
}
}
if( result?.size() == 0 ) result = [ 'no.tests.to.run' ]
return result
}
/**
* Gradle task to run only robolectric tests.
*/
task unitTest( type: Test, description: 'Run all junit tests' ) {
android.sourceSets.main.java.srcDirs.each { dir ->
def buildDir = dir.getAbsolutePath().split('/')
buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
sourceSets.unitTest.compileClasspath += files(buildDir)
sourceSets.unitTest.runtimeClasspath += files(buildDir)
}
testClassesDir = sourceSets.unitTest.output.classesDir
classpath = sourceSets.unitTest.runtimeClasspath
doLast {
println "Doing Last"
List names = testClassNames()
List filtered = annotationFilter( names: names, includes: ['testUtils.RobolectricTest'] )
println 'Running ' + filtered.size() + ' tests:\n' + filtered*.toString()*.replaceAll('^','\t').join('\n')
filter {
setIncludePatterns( filtered as String[] )
}
}
}
Not so much Robolectric-specific, but in regards to declaring a custom test task for Android with Gradle, I ran into a lot of trouble with this. As you found, all of the documentation and examples are using the Java plugin, but the Android plugin subverts most of it.
The only solution I found with the Android plugin is to create another build type, which will then result, under the hood, in the Android plugin creating new test tasks for me. Then I can easily modify the tasks.
Here is the relevant setup, which I keep in a <rootProject>/gradle/test.gradle file (This specific example uses Category annotation to filter unit tests into one task and integration tests into a separate task. For information on that part of it see https://github.com/junit-team/junit4/wiki/Categories):
android {
buildTypes {
integration
}
testOptions {
unitTests.all {
useJUnit()
if (it.name == 'testIntegrationUnitTest') {
options {
excludeCategories 'com.example.categories.UnitTest'
}
} else {
options {
excludeCategories 'com.example.categories.IntegrationTest'
}
}
}
}
}
Then the <module>/build.gradle file applies from this file with apply from: "../gradle/test.gradle"
This causes the android closure from the two files to be merged, results in a new integration build type, which in turn results in a new testIntegrationUnitTest task on the project in addition to the standard testDebugUnitTest and testReleaseUnitTest tasks.
But now, testDebugUnitTest runs only "real" unit tests, while testIntegrationUnitTest runs only integration tests.
Your mileage/implementation may vary. Once you're inside the unitTest.all closure you can do whatever manipulation you need to the options.
Custom Gradle Task To Only Run Specific Tests
Building on #alphonzo79 answer, I was able to solve the issue.
The things to know
Android gradle omits common testing features in gradle, like exclude and custom sourceSets.
Retention and Target was not helpful. Only categories worked.
You don't need a new build type, you only need to change the test option when compiling your own tasks.
Don't use this - https://github.com/pkainulainen/gradle-examples/blob/master/integration-tests/build.gradle
The complete answer was to create a custom task that changed a flag for the android testOptions to excludeCategories.
LINK
CODE
def integrationTests = false
...
testOptions {
unitTests.all {
useJUnit()
if (integrationTests.toBoolean()) {
println "Integration Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.UnitTest'
}
} else {
println "Unit Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.IntegrationTest'
}
}
}
}
...
task integrationTest(
type: Test,
description: 'Run integration tests only. Pass in \'-Pintegration=true\'',
dependsOn: ['testDebugUnitTest', 'clean'] ) {
//Here for task completion, not actually used since sub task of testDebugUnitTest
testClassesDir = file("src/integrationTest/java/");
classpath = files("$System.env.ANDROID_HOME/sources/android-18")
//
//Turn on integration testing when argument exists and is true
//
if (project.hasProperty('integration')) {
println integration
if (integration == 'true') {
integrationTests = true
}
}
}
We can use the following configuration to exclude multiple tests by name:
def integrationTests = project.hasProperty('integrationTests') ? project.getProperty('integrationTests') : false //Default value false
android {
//...
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
all {
test {
filter {
if (integrationTests.toBoolean()) {
includeTestsMatching "*IntegrationTest"
} else {
includeTestsMatching "*UnitTest"
}
}
}
}
}
}
}
In command line:
gradlew test // Run only *UnitTest, Default value is false.
gradlew test -PintegrationTests=false // Run only *UnitTest
gradlew test -PintegrationTests=true // Run only *IntegrationTest
You said Robolectric can't find AndroidManifest.xml when testing multidimensional flavor project on Ubuntu
try to explicitly give the path to your manifest file in the #Config annotation
#Config(constants = BuildConfig.class, manifest = "../<path to>/AndroidManifest.xml")
I'm building an Android app with gradle. Until now I used the Manifest file to increase the versionCode, but I would like to read the versionCode from an external file and depending if it is the release flavor or the debug flavor increase the versionCode. I tried the extra properties, but you can't save them, which means that next time I build it I'm getting the same versionCode.
Any help would be very much appreciated!
project.ext{
devVersionCode = 13
releaseVersionCode = 1
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
apply plugin: 'android'
repositories {
mavenCentral()
}
dependencies {
compile project(':Cropper')
compile "com.android.support:appcompat-v7:18.0.+"
compile "com.android.support:support-v4:18.0.+"
compile fileTree(dir: 'libs', include: '*.jar')
}
def getReleaseVersionCode() {
def version = project.releaseVersionCode + 1
project.releaseVersionCode = version
println sprintf("Returning version %d", version)
return version
}
def getDevVersionCode() {
def version = project.devVersionCode + 1
project.devVersionCode = version
println sprintf("Returning version %d", version)
return version
}
def getLastVersioName(versionCode) {
return "0.0." + versionCode
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
buildTypes {
release {
runProguard true
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard.cfg'
debuggable false
signingConfig null
zipAlign false
}
debug {
versionNameSuffix "-DEBUG"
}
}
productFlavors {
dev {
packageName = 'com.swisscom.docsafe.debug'
versionCode getDevVersionCode()
versionName getLastVersioName(project.devVersionCode)
}
prod {
packageName = 'com.swisscom.docsafe'
versionCode getReleaseVersionCode()
versionName getLastVersioName(project.releaseVersionCode)
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '1.8'
}
I would like to read the versionCode from an external file
I am sure that there are any number of possible solutions; here is one:
android {
compileSdkVersion 18
buildToolsVersion "18.1.0"
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def code = versionProps['VERSION_CODE'].toInteger() + 1
versionProps['VERSION_CODE']=code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
versionCode code
versionName "1.1"
minSdkVersion 14
targetSdkVersion 18
}
}
else {
throw new GradleException("Could not read version.properties!")
}
// rest of android block goes here
}
This code expects an existing version.properties file, which you would create by hand before the first build to have VERSION_CODE=8.
This code simply bumps the version code on each build -- you would need to extend the technique to handle your per-flavor version code.
You can see the Versioning sample project that demonstrates this code.
Here comes a modernization of my previous answer which can be seen below. This one is running with Gradle 4.4 and Android Studio 3.1.1.
What this script does:
Creates a version.properties file if none exists (up vote Paul Cantrell's answer below, which is where I got the idea from if you like this answer)
For each build, debug release or any time you press the run button in Android Studio the VERSION_BUILD number increases.
Every time you assemble a release your Android versionCode for the play store increases and your patch number increases.
Bonus: After the build is done copies your apk to projectDir/apk to make it more accessible.
This script will create a version number which looks like v1.3.4 (123) and build an apk file like AppName-v1.3.4.apk.
Major version ⌄ ⌄ Build version
v1.3.4 (123)
Minor version ⌃|⌃ Patch version
Major version: Has to be changed manually for bigger changes.
Minor version: Has to be changed manually for slightly less big changes.
Patch version: Increases when running gradle assembleRelease
Build version: Increases every build
Version Number: Same as Patch version, this is for the version code which Play Store needs to have increased for each new apk upload.
Just change the content in the comments labeled 1 - 3 below and the script should do the rest. :)
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
def versionPropsFile = file('version.properties')
def value = 0
Properties versionProps = new Properties()
if (!versionPropsFile.exists()) {
versionProps['VERSION_PATCH'] = "0"
versionProps['VERSION_NUMBER'] = "0"
versionProps['VERSION_BUILD'] = "-1" // I set it to minus one so the first build is 0 which isn't super important.
versionProps.store(versionPropsFile.newWriter(), null)
}
def runTasks = gradle.startParameter.taskNames
if ('assembleRelease' in runTasks) {
value = 1
}
def mVersionName = ""
def mFileName = ""
if (versionPropsFile.canRead()) {
versionProps.load(new FileInputStream(versionPropsFile))
versionProps['VERSION_PATCH'] = (versionProps['VERSION_PATCH'].toInteger() + value).toString()
versionProps['VERSION_NUMBER'] = (versionProps['VERSION_NUMBER'].toInteger() + value).toString()
versionProps['VERSION_BUILD'] = (versionProps['VERSION_BUILD'].toInteger() + 1).toString()
versionProps.store(versionPropsFile.newWriter(), null)
// 1: change major and minor version here
mVersionName = "v1.0.${versionProps['VERSION_PATCH']}"
// 2: change AppName for your app name
mFileName = "AppName-${mVersionName}.apk"
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
applicationId "com.example.appname" // 3: change to your package name
versionCode versionProps['VERSION_NUMBER'].toInteger()
versionName "${mVersionName} Build: ${versionProps['VERSION_BUILD']}"
}
} else {
throw new FileNotFoundException("Could not read version.properties!")
}
if ('assembleRelease' in runTasks) {
applicationVariants.all { variant ->
variant.outputs.all { output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
outputFileName = mFileName
}
}
}
}
task copyApkFiles(type: Copy){
from 'build/outputs/apk/release'
into '../apk'
include mFileName
}
afterEvaluate {
assembleRelease.doLast {
tasks.copyApkFiles.execute()
}
}
signingConfigs {
...
}
buildTypes {
...
}
}
====================================================
INITIAL ANSWER:
I want the versionName to increase automatically as well. So this is just an addition to the answer by CommonsWare which worked perfectly for me. This is what works for me
defaultConfig {
versionCode code
versionName "1.1." + code
minSdkVersion 14
targetSdkVersion 18
}
EDIT:
As I am a bit lazy I want my versioning to work as automatically as possible. What I want is to have a Build Version that increases with each build, while the Version Number and Version Name only increases when I make a release build.
This is what I have been using for the past year, the basics are from CommonsWare's answer and my previous answer, plus some more. This results in the following versioning:
Version Name: 1.0.5 (123) --> Major.Minor.Patch (Build), Major and Minor are changed manually.
In build.gradle:
...
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def value = 0
def runTasks = gradle.startParameter.taskNames
if ('assemble' in runTasks || 'assembleRelease' in runTasks || 'aR' in runTasks) {
value = 1;
}
def versionMajor = 1
def versionMinor = 0
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 {
versionCode versionNumber
versionName "${versionMajor}.${versionMinor}.${versionPatch} (${versionBuild}) Release"
minSdkVersion 14
targetSdkVersion 23
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def fileNaming = "apk/RELEASES"
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
output.outputFile = new File(getProject().getRootDir(), "${fileNaming}-${versionMajor}.${versionMinor}.${versionPatch}-${outputFile.name}")
}
}
}
}
} else {
throw new GradleException("Could not read version.properties!")
}
...
}
...
Patch and versionCode is increased if you assemble your project through the terminal with 'assemble', 'assembleRelease' or 'aR' which creates a new folder in your project root called apk/RELEASE so you don't have to look through build/outputs/more/more/more to find your apk.
Your version properties would need to look like this:
VERSION_NUMBER=1
VERSION_BUILD=645
VERSION_PATCH=1
Obviously start with 0. :)
A slightly tightened-up version of CommonsWare's excellent answer creates the version file if it doesn't exist:
def Properties versionProps = new Properties()
def versionPropsFile = file('version.properties')
if(versionPropsFile.exists())
versionProps.load(new FileInputStream(versionPropsFile))
def code = (versionProps['VERSION_CODE'] ?: "0").toInteger() + 1
versionProps['VERSION_CODE'] = code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
versionCode code
versionName "1.1"
minSdkVersion 14
targetSdkVersion 18
}
I looked at a few options to do this, and ultimately decided it was simpler to just use the current time for the versionCode instead of trying to automatically increment the versionCode and check it into my revision control system.
Add the following to your build.gradle:
/**
* Use the number of seconds/10 since Jan 1 2016 as the versionCode.
* This lets us upload a new build at most every 10 seconds for the
* next 680 years.
*/
def vcode = (int)(((new Date().getTime()/1000) - 1451606400) / 10)
android {
defaultConfig {
...
versionCode vcode
}
}
However, if you expect to upload builds beyond year 2696, you may want to use a different solution.
Another way of getting a versionCode automatically is setting versionCode to the number of commits in the checked out git branch. It accomplishes following objectives:
versionCode is generated automatically and consistently on any machine (including a Continuous Integration and/or Continuous Deployment server).
App with this versionCode is submittable to GooglePlay.
Doesn't rely on any files outside of repo.
Doesn't push anything to the repo
Can be manually overridden, if needed
Using gradle-git library to accomplish the above objectives. Add code below to your build.gradle file the /app directory:
import org.ajoberstar.grgit.Grgit
repositories {
mavenCentral()
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.ajoberstar:grgit:1.5.0'
}
}
android {
/*
if you need a build with a custom version, just add it here, but don't commit to repo,
unless you'd like to disable versionCode to be the number of commits in the current branch.
ex. project.ext.set("versionCodeManualOverride", 123)
*/
project.ext.set("versionCodeManualOverride", null)
defaultConfig {
versionCode getCustomVersionCode()
}
}
def getCustomVersionCode() {
if (project.versionCodeManualOverride != null) {
return project.versionCodeManualOverride
}
// current dir is <your proj>/app, so it's likely that all your git repo files are in the dir
// above.
ext.repo = Grgit.open(project.file('..'))
// should result in the same value as running
// git rev-list <checked out branch name> | wc -l
def numOfCommits = ext.repo.log().size()
return numOfCommits
}
NOTE: For this method to work, it's best to only deploy to Google Play Store from the same branch (ex. master).
Recently I was working on a gradle plugin for Android that makes generating versionCode and versionName automatically. there are lots of customization. here you can find more info about it
https://github.com/moallemi/gradle-advanced-build-version
Create new file inside <yourProjectLocation>/app/version.properties
MAJOR=0
MINOR=0
PATCH=1
VERSION_CODE=1
Add following lines in build.gradle (Module file) :
android {
// other properties....
// add following lines...
def _versionCode=0
def _major=0
def _minor=0
def _patch=0
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
_patch = versionProps['PATCH'].toInteger() + 1
_major = versionProps['MAJOR'].toInteger()
_minor = versionProps['MINOR'].toInteger()
_versionCode= versionProps['VERSION_CODE'].toInteger()+1
if(_patch==100) {
_patch=0
_minor=_minor+1
}
if(_minor == 10){
_minor = 0
_major =_major + 1
}
versionProps['MAJOR']=_major.toString()
versionProps['MINOR']=_minor.toString()
versionProps['PATCH']=_patch.toString()
versionProps['VERSION_CODE']=_versionCode.toString()
versionProps.store(versionPropsFile.newWriter(), null)
}
else {
throw new GradleException("Could not read version.properties!")
}
def _versionName = "${_major}.${_minor}.${_patch}(${_versionCode})"
defaultConfig {
// other properties...
// change only these two lines
versionCode _versionCode
versionName _versionName
}
}
Output : 0.0.1(1)
Another option, for incrementing the versionCode and the versionName, is using a timestamp.
defaultConfig {
versionName "${getVersionNameTimestamp()}"
versionCode getVersionCodeTimestamp()
}
def getVersionNameTimestamp() {
return new Date().format('yy.MM.ddHHmm')
}
def getVersionCodeTimestamp() {
def date = new Date()
def formattedDate = date.format('yyMMddHHmm')
def code = formattedDate.toInteger()
println sprintf("VersionCode: %d", code)
return code
}
Starting on January,1 2022
formattedDate = date.format('yyMMddHHmm')
exceeds the capacity of Integers
To increment versionCode only in release version do it:
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
def versionPropsFile = file('version.properties')
def code = 1;
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
List<String> runTasks = gradle.startParameter.getTaskNames();
def value = 0
for (String item : runTasks)
if ( item.contains("assembleRelease")) {
value = 1;
}
code = Integer.parseInt(versionProps['VERSION_CODE']).intValue() + value
versionProps['VERSION_CODE']=code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
}
else {
throw new GradleException("Could not read version.properties!")
}
defaultConfig {
applicationId "com.pack"
minSdkVersion 14
targetSdkVersion 21
versionName "1.0."+ code
versionCode code
}
expects an existing c://YourProject/app/version.properties file, which you would create by hand before the first build to have VERSION_CODE=8
File
version.properties:
VERSION_CODE=8
Examples shown above don't work for different reasons
Here is my ready-to-use variant based on ideas from this article:
android {
compileSdkVersion 28
// https://stackoverflow.com/questions/21405457
def propsFile = file("version.properties")
// Default values would be used if no file exist or no value defined
def customAlias = "Alpha"
def customMajor = "0"
def customMinor = "1"
def customBuild = "1" // To be incremented on release
Properties props = new Properties()
if (propsFile .exists())
props.load(new FileInputStream(propsFile ))
if (props['ALIAS'] == null) props['ALIAS'] = customAlias else customAlias = props['ALIAS']
if (props['MAJOR'] == null) props['MAJOR'] = customMajor else customMajor = props['MAJOR']
if (props['MINOR'] == null) props['MINOR'] = customMinor else customMinor = props['MINOR']
if (props['BUILD'] == null) props['BUILD'] = customBuild else customBuild = props['BUILD']
if (gradle.startParameter.taskNames.join(",").contains('assembleRelease')) {
customBuild = "${customBuild.toInteger() + 1}"
props['BUILD'] = "" + customBuild
applicationVariants.all { variant ->
variant.outputs.all { output ->
if (output.outputFile != null && (output.outputFile.name == "app-release.apk"))
outputFileName = "app-${customMajor}-${customMinor}-${customBuild}.apk"
}
}
}
props.store(propsFile.newWriter(), "Incremental Build Version")
defaultConfig {
applicationId "org.example.app"
minSdkVersion 21
targetSdkVersion 28
versionCode customBuild.toInteger()
versionName "$customAlias $customMajor.$customMinor ($customBuild)"
...
}
...
}
Define versionName in AndroidManifest.xml
android:versionName="5.1.5"
Inside android{...} block in build.gradle of app level :
defaultConfig {
applicationId "com.example.autoincrement"
minSdkVersion 18
targetSdkVersion 23
multiDexEnabled true
def version = getIncrementationVersionName()
versionName version
}
Outside android{...} block in build.gradle of app level :
def getIncrementedVersionName() {
List<String> runTasks = gradle.startParameter.getTaskNames();
//find version name in manifest
def manifestFile = file('src/main/AndroidManifest.xml')
def matcher = Pattern.compile('versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\"').matcher(manifestFile.getText())
matcher.find()
//extract versionName parts
def firstPart = Integer.parseInt(matcher.group(1))
def secondPart = Integer.parseInt(matcher.group(2))
def thirdPart = Integer.parseInt(matcher.group(3))
//check is runTask release or not
// if release - increment version
for (String item : runTasks) {
if (item.contains("assemble") && item.contains("Release")) {
thirdPart++
if (thirdPart == 10) {
thirdPart = 0;
secondPart++
if (secondPart == 10) {
secondPart = 0;
firstPart++
}
}
}
}
def versionName = firstPart + "." + secondPart + "." + thirdPart
// update manifest
def manifestContent = matcher.replaceAll('versionName=\"' + versionName + '\"')
manifestFile.write(manifestContent)
println "incrementVersionName = " + versionName
return versionName
}
After create singed APK :
android:versionName="5.1.6"
Note : If your versionName different from my, you need change regex and extract parts logic.
Credits to
CommonsWare (Accepted Answer)
Paul Cantrell (Create file if it doesn't exist)
ahmad aghazadeh (Version name and code)
So I mashed all their ideas together and came up with this. This is the drag and drop solution to exactly what the first post asked.
It will automatically update the versionCode and versionName according to release status. Of course you can move the variables around to suite your needs.
def _versionCode=0
def versionPropsFile = file('version.properties')
def Properties versionProps = new Properties()
if(versionPropsFile.exists())
versionProps.load(new FileInputStream(versionPropsFile))
def _patch = (versionProps['PATCH'] ?: "0").toInteger() + 1
def _major = (versionProps['MAJOR'] ?: "0").toInteger()
def _minor = (versionProps['MINOR'] ?: "0").toInteger()
List<String> runTasks = gradle.startParameter.getTaskNames();
def value = 0
for (String item : runTasks)
if ( item.contains("assembleRelease")) {
value = 1;
}
_versionCode = (versionProps['VERSION_CODE'] ?: "0").toInteger() + value
if(_patch==99)
{
_patch=0
_minor=_minor+1
}
if(_major==99){
_major=0
_major=_major+1
}
versionProps['MAJOR']=_major.toString()
versionProps['MINOR']=_minor.toString()
versionProps['PATCH']=_patch.toString()
versionProps['VERSION_CODE']=_versionCode.toString()
versionProps.store(versionPropsFile.newWriter(), null)
def _versionName = "${_major}.${_versionCode}.${_minor}.${_patch}"
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.yourhost.yourapp"
minSdkVersion 16
targetSdkVersion 24
versionCode _versionCode
versionName _versionName
}
There are two solutions I really like. The first depends on the Play Store and the other depends on Git.
Using the Play Store, you can increment the version code by looking at the highest available uploaded version code. The benefit of this solution is that an APK upload will never fail since your version code is always one higher than whatever is on the Play Store. The downside is that distributing your APK outside of the Play Store becomes more difficult. You can set this up using Gradle Play Publisher by following the quickstart guide and telling the plugin to resolve version codes automatically:
plugins {
id 'com.android.application'
id 'com.github.triplet.play' version 'x.x.x'
}
android {
...
}
play {
serviceAccountCredentials = file("your-credentials.json")
resolutionStrategy = "auto"
}
Using Git, you can increment the version code based on how many commits and tags your repository has. The benefit here is that your output is reproducible and doesn't depend on anything outside your repo. The downside is that you have to make a new commit or tag to bump your version code. You can set this up by adding the Version Master Gradle plugin:
plugins {
id 'com.android.application'
id 'com.supercilex.gradle.versions' version 'x.x.x'
}
android {
...
}
Instead of specifying the new version in a properties file, I created a Gradle task that can update the current versionName and versionCode automatically and also can get the new version string from command line (by passing arguments to the task with -P followed by <argName>=<argValue>).
app build.gradle.kts:
project.version = "1.2.3"
tasks.create("incrementVersion") {
group = "versioning"
description = "Increments the version to make the app ready for next release."
doLast {
var (major, minor, patch) = project.version.toString().split(".")
val mode = project.properties["mode"]?.toString()?.toLowerCaseAsciiOnly()
if (mode == "major") {
major = (major.toInt() + 1).toString()
minor = "0"
patch = "0"
} else if (mode == "minor") {
minor = (minor.toInt() + 1).toString()
patch = "0"
} else {
patch = (patch.toInt() + 1).toString()
}
var newVersion = "$major.$minor.$patch"
val overrideVersion = project.properties["overrideVersion"]?.toString()?.toLowerCaseAsciiOnly()
overrideVersion?.let { newVersion = it }
val newBuild = buildFile
.readText()
.replaceFirst(Regex("version = .+"), "version = \"$newVersion\"")
.replaceFirst(Regex("versionName = .+\""), "versionName = \"$newVersion\"")
.replaceFirst(Regex("versionCode = \\d+"), "versionCode = ${(android.defaultConfig.versionCode ?: 0) + 1}")
buildFile.writeText(newBuild)
}
}
Usage:
gradlew incrementVersion [-P[mode=major|minor|patch]|[overrideVersion=x.y.z]]
Examples:
gradlew :app:incrementVersion -Pmode=major
gradlew :app:incrementVersion -PoverrideVersion=4.5.6
The First Commented code will increment the number while each "Rebuild Project" and save the the value in the "Version Property" file.
The Second Commented code will generate new version name of APK file while "Build APKs".
android {
compileSdkVersion 28
buildToolsVersion "29.0.0"
//==========================START==================================
def Properties versionProps = new Properties()
def versionPropsFile = file('version.properties')
if(versionPropsFile.exists())
versionProps.load(new FileInputStream(versionPropsFile))
def code = (versionProps['VERSION_CODE'] ?: "0").toInteger() + 1
versionProps['VERSION_CODE'] = code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
//===========================END===================================
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "0.19"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
//=======================================START===============================================
android.applicationVariants.all { variant ->
variant.outputs.all {
def appName = "MyAppSampleName"
outputFileName = appName+"_v${variant.versionName}.${versionProps['VERSION_CODE']}.apk"
}
}
//=======================================END===============================================
}
}
}
in the Gradle 5.1.1 version on mac ive changed how the task names got retrieved, i althought tried to get build flavour / type from build but was to lazy to split the task name:
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def value = 0
def runTasks = gradle.getStartParameter().getTaskRequests().toString()
if (runTasks.contains('assemble') || runTasks.contains('assembleRelease') || runTasks.contains('aR')) {
value = 1
}
def versionMajor = 1
def versionMinor = 0
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 "de.evomotion.ms10"
minSdkVersion 21
targetSdkVersion 28
versionCode versionNumber
versionName "${versionMajor}.${versionMinor}.${versionPatch} (${versionBuild})"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.debug
}
} else {
throw new GradleException("Could not read version.properties!")
}
code is from #just_user
this one
Using Gradle Task Graph we can check/switch build type.
The basic idea is to increment the versionCode on each build. On Each build a counter stored in the version.properties file. It will be keep updated on every new APK build and replace versionCode string in the build.gradle file with this incremented counter value.
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
def versionPropsFile = file('version.properties')
def versionBuild
/*Setting default value for versionBuild which is the last incremented value stored in the file */
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
versionBuild = versionProps['VERSION_BUILD'].toInteger()
} else {
throw new FileNotFoundException("Could not read version.properties!")
}
/*Wrapping inside a method avoids auto incrementing on every gradle task run. Now it runs only when we build apk*/
ext.autoIncrementBuildNumber = {
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
versionBuild = versionProps['VERSION_BUILD'].toInteger() + 1
versionProps['VERSION_BUILD'] = versionBuild.toString()
versionProps.store(versionPropsFile.nminSdkVersion 14
targetSdkVersion 21
versionCode 1ewWriter(), null)
} else {
throw new FileNotFoundException("Could not read version.properties!")
}
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 21
versionCode 1
versionName "1.0.0." + versionBuild
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// Hook to check if the release/debug task is among the tasks to be executed.
//Let's make use of it
gradle.taskGraph.whenReady {taskGraph ->
if (taskGraph.hasTask(assembleDebug)) { /* when run debug task */
autoIncrementBuildNumber()
} else if (taskGraph.hasTask(assembleRelease)) { /* when run release task */
autoIncrementBuildNumber()
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
}
Place the above script inside your build.gradle file of main module.
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())
....
}