Versioning an Android project with semantic-release

The semantic-release is a very powerful tool for automating the versioning of your project based on the commit messages (fix, feat, chore, etc.). The main issue with using it is that it produces a standard SemVer string but does not have a proper output to increment the Android versionCode.

In Android, versionCode is a positive integer used by developers to identify an app’s version internally. This number is not shown to users but is crucial for the Android system and Google Play to manage updates, as it determines if a new version is more recent than an installed one. Each new release must have a higher versionCode than the previous one to be considered an update; otherwise, it will be rejected.

We can define both versionCode and the user-visible versionName in the defaultConfig section. The originally recommended method was to increment the counter value by one for the each new version. For example this was the config file before:

android {
    defaultConfig {
        versionCode 47
        versionName 2.7.3
        [...]
    }
    [...]
}

And this would become:

android {
    defaultConfig {
        versionCode 48
        versionName 2.7.4
        [...]
    }
    [...]
}

It may seem that there is not too much correlation between the versionCode and the versionName, but at some point someone realized that we could generate the versionCode value from the versionName value, if the numbers are well defined. In the case of SemVer they are! We can simply multiply the major component by 100*100, the minor component by 100 and then add the bugfix/patch component:

android {
    defaultConfig {
        versionCode 20704
        versionName 2.7.4
        [...]
    }
    [...]
}

With this method, we always get a strictly increasing versionCode value as long as none of the components is greater than 99. Great!

How can we use it with semantic-release?

The semantic-release usually has a .releaserc file and we can simply add an @semantic-release/exec plugin to create or modify two files during the versioning process:

["@semantic-release/exec", {
  "prepareCmd": "echo -n \"${nextRelease.version}\" >.versionName; awk -F. {'print $1*10000+$2*100+$3'} .versionName >.versionCode"
}],

As you can see, we simply write ${nextRelease.version} to the .versionName file, and with awk we split it into three parts (separated by .), perform the calculation, and write the result to the .versionCode file. For example, the versionName will contain 2.7.4 and the .versionCode will contain 20704.

Okay, but how can we use them in the Gradle process? It’s easy, just read them from the files:

android {
    defaultConfig {
        versionCode = rootProject.file(".versionCode").text.trim().toInteger()
        versionName = rootProject.file(".versionName").text.trim()
        [...]
    }
    [...]
}

Leave a Comment

Scroll to Top