Build Number Plug-in

Automate the generation of build numbers on the CI/CD with a custom Gradle Plug-in

Introduction

In today's article we will learn how to create a custom gradle plug-in that automates the build number on our apps on the CI/CD (Continuous Integration / Continuous Deployment), and how to update the GitHub workflows to leverage it.

The GitHub action

We won't be writing everything from scratch, we will leverage this GitHub action to keep track of the current build number. The docs on the action are pretty good so you can reference them for the specific details on how it works, but the short version is that this action uses a git tag of the form build-number-123 to track the current build number (123 in this example), and when it runs, it reads the number, deletes the tag, and creates a new tag with the new number, build-number-124 in our example. The build number to use is then made available as an environment variable to other workflows on the CI/CD.

Local builds

While we want the CI/CD to use the action to generate new build numbers, on local builds we simply want to be able to set any number as build number, so we will provide a couple of ways to specify the number, either by passing a command line argument to the gradle build command, or by defining a local file in the root of the project that will contain the build number to use. So, the way we want this to work is as follows:

Creating the gradle plug-in

Now that we have our requirements set, we can write the plug-in. We will start by defining the plug-in extension.

Build number extension

An extension is just a plain class that provides configuration options for the plug-in and is accessible from within the gradle build scripts. In our case, we only need to expose a single property, the build number, which we will initialize from the plug-in block. Our extension will be fairly simple, let's see it:

That's all we need for the extension, we can now move to the plug-in itself.

The build number plug-in

The plug-in needs to do the following tasks:

Let's see the code of our plug-in:

With that our plug-in is now complete, we just need to register it and apply it.

Applying the plug-in

Using the build logic convention, we will register our plug-in by using the includeBuild directive. All we have to do for this is create a folder inside the build-logic folder, add our plug-in code and then a build.gradle.kts file to register it. This is common to all custom plug-ins, so I won't delve into the details here as it is well covered in the gradle documentation, I'll just show the build.gradle.kts file:

Once we have this, we can then apply it to our project and then reference the build number fromthe extension, as shown here:

That's pretty much it for the plug-in and local builds, we can specify the build number either with an argument in the command line,

./gradlew assembleDebug -PbuildNumber=123

or using a build_number.properties file with the following content

buildNumber = 123

Next we'll see how we can leverage this on the CI/CD to increment build numbers for each build.

Updating the GitHub workflows

So we have created and applied the plug-in to our build script, now we need to expose the build number we want to use as a system environment variable on the CI/CD. For this, we need to add a new job that will use the action mentioned earlier on this article to expose the build number, from the tag, as an environment variable. This is already covered in the action's documentation, but let's see it here anyways:

Once we have this, we simply need to update the other jobs to use the build number. Note that, because the Setup job needs to run first, we will set a dependency on Setup for the jobs that need the build number:

And that's it, every time we push to the main branch we will trigger these actions to generate a build number and our plug-in will parse it and apply it to the generated artifact.

A full implementation of this solution is available on this GitHub repo.