Reckon...is this week's muse

(A weekly featured project/product that will make your short life better.)

We’ve all been through the drill: Create a release branch. Bump the version number. Merge and delete the release branch. Tag the release. What a waste of time. If you’re building a project with Gradle and using Git, the Reckon plugin can infer your projects version number without having to hard-code it in your build files by taking advantage of all the information that is already available in Git.

Reckon has pretty great documentation, but here’s a couple of use cases you can run through yourself to get a feel for using it.

If you don’t have Gradle installed, I’d suggest using the excellent SDKMAN to manage your installations.

Use Case: A Java Library Using Maven Snapshot Versioning

Create a gradle project

We are going to build a Java library, working towards version 1.0.0.

$ $ mkdir reckon-snapshot-example
$ cd reckon-snapshot-example
$ gradle init
Starting a Gradle Daemon (subsequent builds will be faster)

Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 3
...

Accept the defaults for the rest of the options. You should be able to build the project with:

$ ./gradlew clean assemble
...
BUILD SUCCESSFUL

If we look at the results of the build, there is no version number on the build artifact:

$ ls build/libs/
reckon-snapshot-example.jar
Initialize a Git Repo
$ git init .
$ git add .
$ git commit -am "Initial import of project"
Apply the Reckon Plugin

You can find the latest version of the reckon plugin by searching for it on https://plugins.gradle.org.

Open up build.gradle (A good time format it and clean up whitespace), add the plugin, and the snapshot configuration:

plugins {
  ...
  id 'org.ajoberstar.reckon' version '0.12.0'
}

reckon {
  scopeFromProp()
  snapshotFromProp()
}

Run the build again and notice that Reckon is now inferring a build number and applying it to the project:

$ ./gradlew clean assemble
Reckoned version: 0.1.0-SNAPSHOT
$ ls build/libs/
reckon-snapshot-example-0.1.0-SNAPSHOT.jar

Go ahead and commit the changes in your build.gradle file.

$ git commit -am "Update build file with reckon config"

That’s pretty cool, but we had decided that we want to be working towards version 1.0.0, not 0.1.0. Let’s tell Reckon that we are working towards a major version release:

$ ./gradlew clean assemble -Preckon.scope=major
Reckoned version: 1.0.0-SNAPSHOT
$ ls build/libs/
reckon-snapshot-example-1.0.0-SNAPSHOT.jar

Boom! Our build now produces the correct snapshot version number, without any hard-coding.

Release a Final Version

If the work on this release is done, we can release a final (no snapshot) version of our library. (You’ll get an error if your git repo isn’t clean.)

The Reckon snapshot configuration has two stages: snapshot (the default) and final. So, we can tell our build that we want to be in the final stage.

$ ./gradlew clean assemble -Preckon.scope=major -Preckon.stage=final
Reckoned version: 1.0.0

Reckon’s reckonTagCreate task can tag the release for us. Since we haven’t set up a remote repository, we’ll tag locally:

$ ./gradlew reckonTagCreate -Preckon.scope=major -Preckon.stage=final 
> Task :reckonTagCreate
Reckoned version: 1.0.0

$ git tag
1.0.0
Working Towards the Next Patch Release

Until we make changes to the project, reckon will infer the version from the Git tag as 1.0.0. If we do some work, we can have reckon infer the next version for us.

$ touch foo
$ ./gradlew clean assemble
Reckoned version: 1.1.0-SNAPSHOT

Reckon defaults to using the reckon.scope of minor. If we are working on a patch release, we can set the scope patch to patch:

$ ./gradlew clean assemble -Preckon.scope=patch
Reckoned version: 1.0.1-SNAPSHOT
$ git commit -am "Add foo file"

Use Case: An Application Using beta –> rc –> final Versioning

In this use case, we are developing an application using a 3 stage versioning scheme.

You can start a new example by following the gradle init example above and choosing application as the project type.

The build configuration looks like:

reckon {
  scopeFromProp()
  stageFromProp('beta', 'rc', 'final')
}

From a clean Git repo, you can see the inferred version:

$ ./gradlew clean assemble
Reckoned version: 0.1.0-beta.0.1+719d620

What’s this? Reckon is using the default scope of minor, the first stage alphabetically (beta), and an abbreviated git commit hash. As before, we are working towards a final version of 1.0.0. We need to set the scope to major:

$ ./gradlew clean assemble -Preckon.scope=major
Reckoned version: 1.0.0-beta.0.1+719d620

If we do some work and build with the changes in an uncommitted state, reckon will use a timestamp instead of a commit hash:

$ touch foo
$ ./gradlew clean assemble -Preckon.scope=major
Reckoned version: 1.0.0-beta.0.1+20200412T220104Z

Assuming we’re done developing, we can commit our work and do a beta release:

$ git add foo && git commit -m "Complete beta development for 1.0.0-beta"
$ ./gradlew reckonTagCreate -Preckon.scope=major -Preckon.stage=beta

> Task :reckonTagCreate
Reckoned version: 1.0.0-beta.1

Now we have build 1 of the 1.0.0 beta. Reckon will keep track of not only the beta versions, but the number of builds as well. We can continue working through beta an rc builds until we are ready to do a final release.

$ ./gradlew reckonTagCreate -Preckon.scope=major -Preckon.stage=final
> Task :reckonTagCreate
Reckoned version: 1.0.0

The How Reckon Works documentation has a ton of examples explaining how the version and build numbers are inferred.

Hope this helps simplify your release process. Enjoy!

References