Fancy Android Version Numbers from Git

I recently started working on several Android projects, including a custom automatic updater (the target devices won’t have internet access, let alone the Play Store). While the system is working wonderfully, it relies on detecting changes in the app’s version number. Which I keep forgetting to change. As with most of my projects I’m using Git for version control. So let’s do something fancy, and generate the android version number from git’s commit and tag information.

Jump to the finished result

Gradle

Android uses Gradle to manage it’s builds, so this would seem like the obvious place to make the change. By default, each module has a build.gradle file which contains an android  section, which in turn contains the android-specific configuration. The entries we’re interested in are versionCode and versionName.

VersionCode  must be an integer, and is used by the Play Store for it’s version control. This is important, as the play store cares about upgrade Vs downgrades, and will only move users to a newer version. I’m not fussed about that, so automatically incrementing the versionCode  is left as an exercise for the reader (don’t forget about multiple build machines and decentralized version control). For reference, mine’s set like this:

versionCode System.getenv("APP_VERSION_CODE") as Integer ?: 0

This will use the environment variable APP_VERSION_CODE, or just 0 if the variable is missing or not an integer.

Git

The obvious answer for accessing Git information in Java is JGit, however it can be difficult to work with from Gradle. As such, Grgit makes life much easier.

Grgit can be added to a an Android Gradle project by adding this line to the project’s build.gradle:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath 'com.google.gms:google-services:3.0.0'
        classpath 'org.ajoberstar:grgit:1.4.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

NB This is not the module’s build.gradle, and will be one lever higher than your src and build folders. The warning in the file is there to remind you that these are build dependencies, IE libraries needed for the build itself, and not your app.

This will allow us to use data from Git in the build process. Now let’s go back to the module’s build.gradle and make use of it.

Grgit

As with any library, Grgit needs to be imported before use. This can be done with import org.ajoberstar.grgit.Grgit at the top of the file.

By default, Gradle will run the build from the project’s directory, which means we don’t have to worry about finding the Git repo. Grgit knows this, and will look in the current directory if we don’t specify a folder. Using this, we can tell Grgit to get the repo info by using git = Grgit.open().

Git already has a great way of tracking version numbers, they’re called tags. The exact details of tags are covered in the Git manual pages, but they can be assigned to a specific commit and later referred to by name. Git also has a handy way of getting a reference to the current version, including the last tag and any extra commits made since then. This is git describe. The describe command has two possible outputs:

PS C:\Projects\android-stuff> git describe
v1.2.3

This means we are on the commit with tag “v1.2.3”, and no extra commits have been made since the tag.

PS C:\Projects\android-stuff> git describe
v1.2.3-7-g6628da2

This means the last tag was “v1.2.3”, but 7 other commits have been made since the tag, the last of which was g6628da2.

Note that neither of these mention untracked or uncommitted files, as Git doesn’t keep an eye on them until we tell it to (by adding and committing them.). We can make use of the describe command in Grgit by calling .describe() on our git object.

Putting it all together

Combining all of the above gives us these Gradle files:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
import org.ajoberstar.grgit.Grgit

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath 'com.google.gms:google-services:3.0.0'
        classpath 'org.ajoberstar:grgit:1.4.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
import org.ajoberstar.grgit.Grgit
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.1"

    defaultConfig {
        applicationId "com.jacobmansfield.android.stuff"
        minSdkVersion 25
        targetSdkVersion 25
        versionCode System.getenv("APP_VERSION_CODE") as Integer ?: 0

        git = Grgit.open()
        versionName "${git.describe()}"

        multiDexEnabled true
    }
    buildTypes {
        debug {
            versionNameSuffix "-SNAPSHOT"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        disable 'GoogleAppIndexingWarning'
    }
}

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

Eagle-eyed readers may notice the versionNameSuffix inside the module build.gradle, this will add “-SNAPSHOT” to the version number of debug builds, which lets us tell if they’re from a dev’s PC or from the proper build system (which produces signed release builds)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.