[Gradle] Automate application version management with Gradle

Theekshana Wijesinghe
5 min readMar 17, 2021

Gradle is a popular build tool for Java that makes life easy for developers. It is a flexible tool and since it is powered by Groovy, developers are free to develop their own solutions.

In this blog post, I will walk you through how to automate the application version management using Gradle.

Let’s ask the obvious, Why?

There are many reasons for this, one simple reason would be if you are generating an artifact like .jar file and publishing it in an Artifactory like JFrog you want to maintain the version correctly.

Let’s dive in.

First I need to create a Gradle project. You can leverage gradle init and generate a project of your liking. I used Spring Initializr to generate a Spring Boot project.

In the project I’ve created, I have the following version in the build.gradle file.

# build.gradle...version = '0.0.1-SNAPSHOT'...

Now if I run the command ./gradlew build and a new folder namedbuild will be created and inside it under the libs folder you will find versioning-0.0.1-SNAPSHOT.jar file.

If ./gradlew seems a bit weird to you, I suggest you read about the Gradle wrapper. 0.0.1-SNAPSHOT part of the jar file comes directly from the value of version in build.gradle file. versioning part comes from the artifact name I have given.

Notes:
We can change rootProject.name to alter the artifactname.
If you want to change the folder where the .jar is generated, change libsDirName

When you start developing this application and want to publish new versions the way would be changing the version manually. This is the part we are going to automate.

Semantic versioning (SemVer)

When we define a version as 0.0.1-SNAPSHOT it is called a semantic version. Read more on it here. In simple terms, it specifies a version of the application that a dependency manager can download to any project (Gradle does this for us). When we develop a new version we increment this version according to the type of change.

If we break the semantic versioning, you get

0.0.1-SNAPSHOT = <MAJOR>.<MINOR>.<PATCH><PRE-RELEASE>

Note: SNAPSHOT has a special meaning that the version has not been released yet.

So when we are going to automate, we need a way to keep track of the SemVer. For this, I will define a class in Groovy.

# build.gradleclass ApplicationVersion {
Integer major
Integer minor
Integer patch
Boolean release

ApplicationVersion(Integer major, Integer minor, Integer patch, Boolean release) {
this.major = major
this.minor = minor
this.patch = patch
this.release = release
}

private String getRelease() {
return this.release ? '' : '-SNAPSHOT'
}

String getVersion() {
return "$major.$minor.$patch" + this.getRelease()
}
}

Now we have a way to model the version. With this our end goal is,

# build.gradleversion = <...>.getVersion()// We will see what comes to <...> later

version.properties

Since version has been removed from the build.gradle file we need to keep the current version somewhere else. Let’s define a new file version.properties that captures all the aspects of the version of the application.

# version.propertiesmajor=0
minor=0
patch=1
release=false

Next, we need to load the version in version.properties to build.gradle , for this let’s define a method loadVersion.

# build.gradleext.loadVersion = { ->

def versionPropertiesFile = file('version.properties')

if(!versionPropertiesFile.exists()) {
throw new Exception('No version.properties file found')
}

Properties versionProperties = new Properties()

versionPropertiesFile.withInputStream { stream ->
versionProperties.load(stream)
}
return new ApplicationVersion(versionProperties.major.toInteger(),
versionProperties.minor.toInteger(),
versionProperties.patch.toInteger(),
versionProperties.release.toBoolean())

}

It is straightforward, we read the version properties file and get the properties defined to a Properties object and create a new ApplicationVersion object.

Now right after the method loadVersion have the following version definition

# build.gradleext.loadVersion = { -> 
...
}
version = loadVersion().getVersion()

Now if you run ./gradlew clean build and go to the libs folder inside build folder, You’ll see the .jar file with the same previous name. Now change the values in version.properties file and re-run the above command to see if this really works. (It should!)

Now we have a way to load the application version from a file. But how are we going to automate versioning?

Gradle tasks

Gradle Tasks to the rescue.

We are going to introduce five new tasks.

  • majorVersionUpdate

This will increment the major version of the current version by 1.
If the current version is v0.0.1 then it will move to v1.0.0

  • minorVersionUpdate

This will increment the minor version of the current version by 1.
If the current version is v0.0.1 then it will move to v0.1.1

  • patchVersionUpdate

This will increment the patch version of the current version by 1.
If the current version is v0.0.1 then it will move to v0.0.2

  • releaseVerion

This is will set the current to a release version. (SNAPSHOT will be removed)

  • preReleaseVersio

This will set the current release to a non-release version ( SNAPSHOT release)

Let’s define tasks as below

task majorVersionUpdate(group: 'versioning', description: 'Bump to next major version') {
doFirst {
def versionFile = file('version.properties')
ant.propertyfile(file: versionFile) {
entry(key: 'major', type: 'int', operation: '+', value: 1)
entry(key: 'minor', type: 'int', operation: '=', value: 0)
entry(key: 'patch', type: 'int', operation: '=', value: 0)
}
}
}

task minorVersionUpdate(group: 'versioning', description: 'Bump to next minor version') {
doFirst {
def versionFile = file('version.properties')
ant.propertyfile(file: versionFile) {
entry(key: 'minor', type: 'int', operation: '+', value: 1)
entry(key: 'patch', type: 'int', operation: '=', value: 0)
}
}
}

task patchVersionUpdate(group: 'versioning', description: 'Bump to next patch version') {
doFirst {
def versionFile = file('version.properties')
ant.propertyfile(file: versionFile) {
entry(key: 'patch', type: 'int', operation: '+', value: 1)
}
}
}

task releaseVersion(group: 'versioning', description: 'Make the version a release') {
doFirst {
def versionFile = file('version.properties')
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}

task preReleaseVersion(group: 'versioning', description: 'Make the version a pre release') {
doFirst {
def versionFile = file('version.properties')
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'false')
}
}
}

There is nothing magical with these tasks,

  • We first read the version file ( version.properties )
  • Then depending on the task, we change the properties of the version file by using an ant task ant.propertyfile
entry(key: 'patch', type: 'int', operation: '+', value: 1)

The above statement simply says, in the property file provided, for the property patch which is an int use the operation add to increment the value by 1.

Pretty simple right.

Note: For the tasks, I have defined group = 'versioning' this will group all those 5 tasks under the group Versoning Taskswhen you run ./gradlew tasks

Now let’s test this. In your favorite terminal run the below commands

$ ./gradlew patchVersionUpdate # Increase the patch by 1$ ./gradlew releaseVersion # Remove SNAPSHOT $ ./gradlew clean build

I had the version 0.0.2-SNAPSHOT as the current version before I ran the above commands. Now the jar that has been created is versioning-0.0.3.jar which is exactly what I wanted. (You will also note that a timestamp is added to version.properties file)

You can combine tasks together similar to,

./gradlew patchVersionUpdate releaseVersion

Cleaning up

We added versioning-related stuff to build.gradle file and it clutters the file. As the final step, we can move this logic to a separate file. Let’s call it version.gradle

Let’s cut and paste things related to versioning to the few file and in the gradle.build

apply from: 'version.gradle'

...
version = loadVersion().getVersion()
...

And that’s how you can automate the version management using Gradle.

See the full code here.

References

[1] Gradle in Action: Benjamin Muschko

--

--