• 14
name

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191

Backtrace:

File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

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!

  • 57
Reply Report
    • While I'm not sure if this is the best answer for the specifics of the question, it answers the title, and the question which brought me here from google, so I'll give you an upvote.
    • This works only partly. The version is read from the file, but never incremented! I logged and saw that incrementVersionNumber() is never called. It seems that the check if (variant.buildType == buildTypes.release) is never true, for whatever reason. Replacing it with if (variant.name == "release") worked for me.
      • 2
    • @Dadou thanks for alerting, I updated the answer to latest versions and fixed it. They introduced read-only buildTypes around 0.14.x and it seems that the two read-only wrappers are created separately.
    • @WOLVERINE You can see the format from the readVersion method source. Running gradle incrementVersion will also create the File for you.
      • 2
    • @meeDamian This code is always incrementing the versionCode because it's always iterating through every build variant. To stop it I used this function boolean isRelease() { def runTasks = gradle.startParameter.taskNames return ('assemble' in runTasks || 'assembleRelease' in runTasks) } in conjunction with variant.buildType.name == buildTypes.release.name

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.

  • 14
Reply Report
    • Nice idea. I'm now using git commit number for versionCode by getting the commit count across all branches and converting to an int: def gitCommitCount = "git rev-list --all --count".execute().text.trim().toInteger()

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"
    }
}
  • 12
Reply Report

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"
            }
        }
    }
}
  • 5
Reply Report

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.

  • 3
Reply Report
      • 1
    • This could be easily combined with the incrementVersionNumber() method in my answer! I use it too, you can even conditionally include modules in settings.gradle.