[Gradle] Automate application version management with Gradle
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 changerootProject.name
to alter the artifactname.
If you want to change the folder where the.jar
is generated, changelibsDirName
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
taskant.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 groupVersoning Tasks
when 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