78

I'm experimenting with new Android build system based on Gradle and I'm thinking, what is the best way to autoincrease versionCode with it. I am thinking about two options

  1. create versionCode file, read number from it, increase it and write it back to the file
  2. parse AndroidManifest.xml, read versionCode from it, increase it and write it back to the AndroidManifest.xml

Is there any more simple or suitable solution?

Has anyone used one of mentiod options and could share it with me?

sealskej
  • 7,281
  • 12
  • 53
  • 64

13 Answers13

58

I have decided for second option - to parse AndroidManifest.xml. Here is working snippet.

task('increaseVersionCode') << {
    def manifestFile = file("AndroidManifest.xml")
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcher = pattern.matcher(manifestText)
    matcher.find()
    def versionCode = Integer.parseInt(matcher.group(1))
    def manifestContent = matcher.replaceAll("versionCode=\"" + ++versionCode + "\"")
    manifestFile.write(manifestContent)
}

tasks.whenTaskAdded { task ->
    if (task.name == 'generateReleaseBuildConfig') {
        task.dependsOn 'increaseVersionCode'
    }
}

versionCode is released for release builds in this case. To increase it for debug builds change task.name equation in task.whenTaskAdded callback.

sealskej
  • 7,281
  • 12
  • 53
  • 64
  • 5
    one question, how to integrate those code into Cradle build system? – LiangWang Jan 07 '14 at 01:47
  • Also, somewhat cleaner answer here doesn't modify XML in place, but keeps version number in a props file: http://stackoverflow.com/questions/21405457/autoincrement-versioncode-with-gradle-extra-properties – Paul Cantrell Aug 06 '14 at 17:02
  • 1
    Just change file("AndroidManifest.xml") to file("src/main/AndroidManifest.xml") if you are using the new project structure. – Elhanan Mishraky Feb 16 '15 at 09:48
  • 3
    What if I don't set versionCode in my AndroidManifest.xml? – IgorGanapolsky Apr 02 '15 at 18:31
  • @Jacky If you guys still not got a workaround, see my answer: http://stackoverflow.com/a/39619297/1150251 – iStar Sep 21 '16 at 14:26
  • @Yazon2006 you should add the following line to your `app/build.gradle` file: `import java.util.regex.Pattern` Insert this line after `apply plugin ...`section – shimatai Sep 21 '17 at 20:17
40

I'm using this code to update both versionCode and versionName, using a "major.minor.patch.build" scheme.

import java.util.regex.Pattern

task('increaseVersionCode') << {
    def manifestFile = file("src/main/AndroidManifest.xml")
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcher = pattern.matcher(manifestText)
    matcher.find()
    def versionCode = Integer.parseInt(matcher.group(1))
    def manifestContent = matcher.replaceAll("versionCode=\"" + ++versionCode + "\"")
    manifestFile.write(manifestContent)
}

task('incrementVersionName') << {
    def manifestFile = file("src/main/AndroidManifest.xml")
    def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
    matcherVersionNumber.find()
    def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
    def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
    def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
    def buildVersion = Integer.parseInt(matcherVersionNumber.group(4))
    def mNextVersionName = majorVersion + "." + minorVersion + "." + pointVersion + "." + (buildVersion + 1)
    def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"")
    manifestFile.write(manifestContent)
}

tasks.whenTaskAdded { task ->
    if (task.name == 'generateReleaseBuildConfig' || task.name == 'generateDebugBuildConfig') {
        task.dependsOn 'increaseVersionCode'
        task.dependsOn 'incrementVersionName'
    }
}
36

it doesn't seem to be the exact setup you're using, but in my case the builds are being run by jenkins and i wanted to use its $BUILD_NUMBER as the app's versionCode. the following did the trick for me there.

defaultConfig {
    ...
    versionCode System.getenv("BUILD_NUMBER") as Integer ?: 9999
    ...
}
user2399268
  • 436
  • 4
  • 3
  • What is System.genenv? – IgorGanapolsky Apr 02 '15 at 18:08
  • This actually helped me a lot when using Jenkins. You can use this [this plugin](https://wiki.jenkins-ci.org/display/JENKINS/Version+Number+Plugin) to create environmt variable with build number and read it with `System.getenv()` in the gradle. – Lamorak May 12 '15 at 12:06
20

UPDATE

As google play warning:

The greatest value Google Play allows for versionCode is 2100000000.

We might change the format as below to reduce the risk of reaching limit:

def formattedDate = date.format('yyMMddHH')

ORIGINAL

I am using time stamp for the version code:

def date = new Date()
def formattedDate = date.format('yyMMddHHmm')
def code = formattedDate.toInteger()

defaultConfig {
    minSdkVersion 10
    targetSdkVersion 21
    versionCode code
}
thanhbinh84
  • 17,876
  • 6
  • 62
  • 69
7

If you are holding the version code in the build.gradle file use the next snippet:

import java.util.regex.Pattern    
task('increaseVersionCode') << {
    def buildFile = file("build.gradle")
    def pattern = Pattern.compile("versionCode\\s+(\\d+)")
    def manifestText = buildFile.getText()
    def matcher = pattern.matcher(manifestText)
    matcher.find()
    def versionCode = Integer.parseInt(matcher.group(1))
    def manifestContent = matcher.replaceAll("versionCode " + ++versionCode)
    buildFile.write(manifestContent)
}
Elhanan Mishraky
  • 2,736
  • 24
  • 26
  • That pattern is not matching with the versionCode. – lagos May 05 '15 at 13:38
  • Can you post your build.gradle? – Elhanan Mishraky May 05 '15 at 15:02
  • My build.gradle is a normal build-file. VersionCode line looks like `versionCode 18 `. Even if I ran your regex on a tester (`"versionCode \d+"`) it will not match. – lagos May 06 '15 at 07:18
  • The error was that one more whitespace. Android Studio use two whitespaces on `versionCode` and the digit, since it will be align together with the text in `versionName`. – lagos May 06 '15 at 07:41
5

Gradle Advanced Build Version is a 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

moallemi
  • 2,448
  • 2
  • 25
  • 28
4

To take both product flavors and build types into account and using @sealskej's logic for parsing manifest:

android.applicationVariants.all { variant ->
    /* Generate task to increment version code for release */
    if (variant.name.contains("Release")) {
        def incrementVersionCodeTaskName = "increment${variant.name}VersionCode"
        task(incrementVersionCodeTaskName) << {
            if (android.defaultConfig.versionCode == -1) {
                def manifestFile = file(android.sourceSets.main.manifest.srcFile)
                def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
                def manifestText = manifestFile.getText()
                def matcher = pattern.matcher(manifestText)
                matcher.find()
                def versionCode = Integer.parseInt(matcher.group(1))
                android.defaultConfig.versionCode = versionCode + 1
                def manifestContent = matcher.replaceAll("versionCode=\"" + android.defaultConfig.versionCode + "\"")
                manifestFile.write(manifestContent)
            }
        }
        def hookTask = variant.generateBuildConfig
        hookTask.dependsOn(incrementVersionCodeTaskName)
    }
}
Max Ch
  • 1,247
  • 10
  • 13
3

Increment VersionCode Task(Integer):

This works by incrementing the Version Code by 1, for example:

  android:versionCode="1"
1 + 1 = 2
import java.util.regex.Pattern

task incrementVersionCode << {
    def manifestFile = file('AndroidManifest.xml')
    def matcher = Pattern.compile('versionCode=\"(\\d+)\"')
    .matcher(manifestFile.getText())
    matcher.find()
    def manifestContent = matcher.replaceAll('versionCode=\"' +
        ++Integer.parseInt(matcher.group(1)) + '\"')
    manifestFile.write(manifestContent)
}

Increment VersionName Task(String):

Warning: Must contain 1 period for Regex

This works by incrementing the Version Name by 0.01, for example: You can easily modify and change your increment or add more digits.

android:versionName="1.0"
1.00 + 0.01 -> 1.01
1.01 + 0.01 -> 1.02
1.10 + 0.01 -> 1.11
1.99 + 0.01 -> 2.0
1.90 + 0.01 -> 1.91
import java.util.regex.Pattern

task incrementVersionName << {
    def manifestFile = file('AndroidManifest.xml')
    def matcher = Pattern.compile('versionName=\"(\\d+)\\.(\\d+)\"')
    .matcher(manifestFile.getText())
    matcher.find()
    def versionName = String.format("%.2f", Integer
        .parseInt(matcher.group(1)) + Double.parseDouble("." + matcher
            .group(2)) + 0.01)
    def manifestContent = matcher.replaceAll('versionName=\"' +
        versionName + '\"')
    manifestFile.write(manifestContent)
}

Before:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exmaple.test"
    android:installLocation="auto"
    android:versionCode="1"
    android:versionName="1.0" >

After:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exmaple.test"
    android:installLocation="auto"
    android:versionCode="2"
    android:versionName="1.01" >
Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
3

If you write your versionCode in gradle.build file(most case currently), here is a workaround. A little bit stupid(update "self"), but it works!

import java.util.regex.Pattern

task('increaseVersionCode') << {
    def buildFile = file("build.gradle")
    def pattern = Pattern.compile("versionCode(\\s+\\d+)")
    def buildText = buildFile.getText()
    def matcher = pattern.matcher(buildText)
    matcher.find()
    def versionCode = android.defaultConfig.versionCode
    def buildContent = matcher.replaceAll("versionCode " + ++versionCode)
    buildFile.write(buildContent)

    System.out.println("Incrementing Version Code ===> " + versionCode)
}

tasks.whenTaskAdded { task ->
    if (task.name == 'generateReleaseBuildConfig') {
        task.dependsOn 'increaseVersionCode'
    }
}
iStar
  • 1,112
  • 12
  • 20
2

To add on to @sealskej's post, this is how you can update both your version code and version name (Here I'm assuming your major and minor version are both 0):

task('increaseVersion') << {
    def manifestFile = file("AndroidManifest.xml")
    def patternVersionCode = Pattern.compile("versionCode=\"(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcherVersionCode = patternVersionCode.matcher(manifestText)
    matcherVersionCode.find()
    def versionCode = Integer.parseInt(matcherVersionCode.group(1))
    def manifestContent = matcherVersionCode.replaceAll("versionCode=\"" + ++versionCode + "\"")

    manifestFile.write(manifestContent)

    def patternVersionNumber = Pattern.compile("versionName=\"0.0.(\\d+)\"")
    manifestText = manifestFile.getText()
    def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
    matcherVersionNumber.find()
    def versionNumber = Integer.parseInt(matcherVersionNumber.group(1))
    manifestContent = matcherVersionNumber.replaceAll("versionName=\"0.0." + ++versionNumber + "\"")
    manifestFile.write(manifestContent)
}
Karim Varela
  • 7,562
  • 10
  • 53
  • 78
1

what about this ? add to build.gradle (app module)

def getBuildVersionCode() {
    def date = new Date()
    def formattedDate = date.format('yyyyMMdd')
    def formattedSeconds = date.format('HHmmssSSS')
    def formatInt = formattedDate as int;
    def SecondsInt = formattedSeconds as int;
    return (formatInt + SecondsInt) as int
}

   defaultConfig {
    applicationId "com.app"
    minSdkVersion 17
    targetSdkVersion 22
    versionCode getBuildVersionCode()
    versionName "1.0"
}
narancs
  • 5,234
  • 4
  • 41
  • 60
1

So as I was looking into most of the solution, they were nice but not enough so I wrote this, one increment per multi-deploy:

This will increment the build when compiling debug versions, and increment the point and version code when deploying.

import java.util.regex.Pattern

def incrementVersionName(int length, int index) {
    def gradleFile = file("build.gradle")
    def versionNamePattern = Pattern.compile("versionName\\s*\"(.*?)\"")
    def gradleText = gradleFile.getText()
    def matcher = versionNamePattern.matcher(gradleText)
    matcher.find()

    def originalVersion = matcher.group(1)
    def originalVersionArray = originalVersion.split("\\.")
    def versionKeys = [0, 0, 0, 0]
    for (int i = 0; i < originalVersionArray.length; i++) {
        versionKeys[i] = Integer.parseInt(originalVersionArray[i])
    }
    def finalVersion = ""
    versionKeys[index]++;
    for (int i = 0; i < length; i++) {
        finalVersion += "" + versionKeys[i]
        if (i < length - 1)
            finalVersion += "."
    }

    System.out.println("Incrementing Version Name: " + originalVersion + " ==> " + finalVersion)

    def newGradleContent = gradleText.replaceAll("versionName\\s*\"(.*?)\"", "versionName \"" + finalVersion + "\"")
    gradleFile.write(newGradleContent)
}

def incrementVersionCode() {
    def gradleFile = file("build.gradle")
    def versionCodePattern = Pattern.compile("versionCode\\s*(\\d+)")
    def gradleText = gradleFile.getText()
    def matcher = versionCodePattern.matcher(gradleText)
    matcher.find()

    def originalVersionCode = Integer.parseInt(matcher.group(1) + "")
    def finalVersionCode = originalVersionCode + 1;
    System.out.println("Incrementing Version Code: " + originalVersionCode + " ==> " + finalVersionCode)

    def newGradleContent = gradleText.replaceAll("versionCode\\s*(\\d+)", "versionCode " + finalVersionCode)
    gradleFile.write(newGradleContent)
}

task('incrementVersionNameBuild') << {
    incrementVersionName(4, 3)
}

task('incrementVersionNamePoint') << {
    incrementVersionName(3, 2)
}

task('incrementVersionCode') << {
    incrementVersionCode()
}


def incrementedBuild = false
def incrementedRelease = false

tasks.whenTaskAdded { task ->
    System.out.println("incrementedRelease: " + incrementedRelease)
    System.out.println("incrementedBuild: " + incrementedBuild)
    System.out.println("task.name: " + task.name)

    if (!incrementedBuild && task.name.matches('generate.*?DebugBuildConfig')) {
        task.dependsOn 'incrementVersionNameBuild'
        incrementedBuild = true
        return
    }

    if (!incrementedRelease && task.name.matches('generate.*?ReleaseBuildConfig')) {
        task.dependsOn 'incrementVersionCode'
        task.dependsOn 'incrementVersionNamePoint'
        incrementedRelease = true
        return
    }
}
TacB0sS
  • 10,106
  • 12
  • 75
  • 118
-1

My approach is to read manifest file from build folder and get buildVersion out of there, than I delete a folder. When task creates new manifest, my incremented buildVersion variable is already there.

def versionPattern = "Implementation-Version=(\\d+.\\d+.\\d+.\\d+\\w+)"

task generateVersion (dependsOn : 'start') {
    // read build version from previous manifest
    def file = file("build/libs/MANIFEST.MF")
    if (file.exists()) {
        def pattern = Pattern.compile(versionPattern)
        def text = file.getText()
        def matcher = pattern.matcher(text)
        matcher.find()
        buildNumber = Integer.parseInt(matcher.group(1))
        // increment build version
        version = "${majorVer}.${minorVer}.${patchVer}.${++buildNumber}${classifier}_${access}"
    } 
    else 
        version = "${majorVer}.${minorVer}.${patchVer}.1${classifier}_${access}"
}

task specifyOutputDir (dependsOn : 'generateVersion', type : JavaCompile) {
    // create a folder for new build
    destinationDir = file("build/${version}/")
}

task clean (dependsOn : 'generateVersion', type : Delete) {
    doLast {
        delete "build/${version}"
        println 'Build directory is deleted'
    }
}

task configureJar (dependsOn : 'generateVersion', type : Jar) {
    baseName = applicationName
    version = project.version
    archiveName = "${applicationName}_ver${version}.${extension}"
    manifest {[
            "Main-Class" : mainClassName,
            "Implementation-Title" : name,
            "Implementation-Version" : version,
            "Access" : access,
            "Developer" : developer
    ]}
}
Katoteshi Fuku
  • 123
  • 1
  • 1
  • 11
  • This does not provide an answer to the question. Once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](http://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/low-quality-posts/12960913) – MLavoie Jul 10 '16 at 19:58
  • @MLavoie check it out now my W.I.P. snippet – Katoteshi Fuku Jul 10 '16 at 22:10