Android Gradle: Dynamically change versionName at build time

android, android-gradle-plugin, gradle, groovy

Solution

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!

Problem

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?

Original source