Set Android versionCode from inside a task

I would like to have a task that increments versionCode and proceeds with the build.

I can’t get this to work, because the android {} closure is apparently evaluated before any tasks are run, and I can’t seem to change the values properly after that point.

Build script

I tried to keep this as minimal as possible, but it’s still pretty big.

// Boilerplate
buildscript {
    repositories { jcenter() }
    dependencies { classpath 'com.android.tools.build:gradle:1.3.0' }
}
allprojects { repositories { jcenter() } }

// Version management

def showCurrentCustomVersion() {
    println("    currentDynamicVersionCode = ${project.ext.loadCurrentDynamicVersionCode()} (${project.ext.currentDynamicVersionCode})")
    println("    android.defaultConfig.versionCode = ${android.defaultConfig.versionCode}")
}

project.ext.currentDynamicVersionCode = 10

project.ext.loadCurrentDynamicVersionCode = {
    // In my full script, this loads the version code from a file
    return project.ext.currentDynamicVersionCode
}

task changeCustomVersionCode << {
    println("*** Before changeCustomVersionCode:")
    showCurrentCustomVersion()

    // In my full script, this loads the version code from a file, increments it, and saves it back to the file for future use
    project.ext.currentDynamicVersionCode = 99000
    android.defaultConfig.versionCode project.ext.loadCurrentDynamicVersionCode()

    // Also tried this, but had the same result:
    //android.defaultConfig.versionCode = project.ext.loadCurrentDynamicVersionCode()


    println("*** After changeCustomVersionCode:")
    showCurrentCustomVersion()

    println("=> android.defaultConfig:\n    ${android.defaultConfig}")
    println("=> android.buildType:\n    ${android.buildTypes}")
    println("=> android.productFlavors:\n    ${android.productFlavors}")
}

// Android

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.snobwall.buildtest"
        minSdkVersion 21
        targetSdkVersion 22
        versionCode project.ext.loadCurrentDynamicVersionCode()
        versionName "1.0"
    }
}

dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) }

task changeCustomVersionAndBuild(dependsOn: changeCustomVersionCode)

gradle.projectsEvaluated {
    changeCustomVersionAndBuild.dependsOn assembleRelease
    assembleRelease.mustRunAfter(changeCustomVersionCode)
}

Relevant output

The versionCode appears to be changed correctly here.

:changeCustomVersionCode
*** Before changeCustomVersionCode:
    currentDynamicVersionCode = 10 (10)
    android.defaultConfig.versionCode = 10
*** After changeCustomVersionCode:
    currentDynamicVersionCode = 99000 (99000)
    android.defaultConfig.versionCode = 99000
=> android.defaultConfig:
    ProductFlavor_Decorated{name=main, dimension=null, minSdkVersion=ApiVersionImpl{mApiLevel=21, mCodename='null'}, targetSdkVersion=ApiVersionImpl{mApiLevel=22, mCodename='null'}, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptNdkModeEnabled=null, versionCode=99000, versionName=1.0, applicationId=com.snobwall.buildtest, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=[], mBuildConfigFields={}, mResValues={}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}}
=> android.buildType:
    [BuildType_Decorated{name=debug, debuggable=true, testCoverageEnabled=false, jniDebuggable=false, pseudoLocalesEnabled=false, renderscriptDebuggable=false, renderscriptOptimLevel=3, applicationIdSuffix=null, versionNameSuffix=null, minifyEnabled=false, zipAlignEnabled=true, signingConfig=SigningConfig_Decorated{name=debug, storeFile=/Users/mrb/.android/debug.keystore, storePassword=android, keyAlias=AndroidDebugKey, keyPassword=android, storeType=/Users/mrb/.android/debug.keystore}, embedMicroApp=false, mBuildConfigFields={}, mResValues={}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}}, BuildType_Decorated{name=release, debuggable=false, testCoverageEnabled=false, jniDebuggable=false, pseudoLocalesEnabled=false, renderscriptDebuggable=false, renderscriptOptimLevel=3, applicationIdSuffix=null, versionNameSuffix=null, minifyEnabled=false, zipAlignEnabled=true, signingConfig=null, embedMicroApp=true, mBuildConfigFields={}, mResValues={}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}}]
=> android.productFlavors:
    []
:preBuild UP-TO-DATE
:preReleaseBuild UP-TO-DATE
--- snip ---
:assembleRelease
:changeCustomVersionAndBuild

BUILD SUCCESSFUL

Resulting APK

No luck here; uses version 10 instead of 99000.

$ aapt d badging build/outputs/apk/app-release-unsigned.apk | fgrep versionCode
package: name='com.snobwall.buildtest' versionCode='10' versionName='1.0' platformBuildVersionName='5.1.1-1819727'

What I tried

  • Use a lazy GString for the versionCode, but Android expects an int there (not a string).

  • Call apply plugin: and android {} from within the task (instead of at project scope). This caused something to crash internally (in the Android project, I believe). I also don’t know what effect this would have on the project evaluation process: Would gradle.projectsEvaluated be invoked twice?

  • Use a separate prebuild script to perform versioning tasks, then chain to the app build script. This works, but is awkward and feels like a hack.

  • Poke at the android object to see if there’s anything else I can change. For example, maybe other configs are created using defaultConfig as a template. I didn’t see anything relevant though. (Some of these are visible in the output above)

  • Browse this Android DSL reference to see if there’s anything else I might be able to poke at.

My knowledge of Gradle script debugging is too weak for me to get any deeper. I suspect that the Android plugin may be creating some tasks with closures that have captured the versionCode value anonymously; if that’s true, I may be forever unable to change it once the android {} closure has been evaluated.

Hoping for any suggestions!

Did you ever figure this out? Struggling with the exact same issue at the moment.

Unfortunately, no. I settled with changing the versionCode from a shell script that I run before my Gradle build.