As early as 1997, Eric S. Raymond coined the phrase "The Cathedral and the Bazaar" in his essay, "Release early, release often." With the DevOps paradigm, the concept of Continuous Delivery, which follows this motto, is more relevant than ever. In current software development, it’s crucial to be able to create releases smoothly. To achieve this, the release process must be simple, reproducible, and automated.

To uniquely identify and reference releases, a versioning scheme is required. This is where Semantic Versioning comes into play. Semantic Versioning defines how a version number is structured and incremented.

By using Conventional Commits, developers can specify in commit messages which part of a version should be incremented. This has the advantage of documenting code changes directly and automatically listing them in a project’s changelog. The specification provides guidelines on how commit messages should be structured.

Some key terms of Conventional Commits are:

By using these prefixes in commit messages, you can determine which part of a version number should be incremented. Consequently, within the context of Continuous Deployment strategies, builds can be automatically triggered and published with the resulting version number.

To automate the release process with the aforementioned specifications, the JavaScript community has developed the Semantic Release toolset. Although this toolset is primarily designed for JavaScript, it can also be used in other environments. In this discussion, we will focus on using it in a Java project built with Gradle.

In Gradle, the Ivy Publish Plugin or the Maven Publish Plugin is typically used for publishing artifacts. Both plugins work similarly, mainly differing in the metadata they use for repositories. In the following example, we will use the Maven Plugin, as Maven repositories are commonly encountered.

For a unique identification of artifacts in Gradle, the following properties are required:

The version is generated by the Semantic Release toolset and passed to Gradle via the command line. To enable this, the version property should not be defined in the build.gradle. In our example, this property is not defined in Gradle at all; versioning is exclusively handled by Semantic Release, based on existing tags and commit messages.

The Semantic Release toolset uses the .releaserc file (other files and formats can be viewed here) to control the process. An example configuration might look like this:

releaserc
{
    "branches": ["main"],
    "plugins": [
        "@semantic-release/commit-analyzer",
        "@semantic-release/release-notes-generator",
        ["@semantic-release/github", {
            "assets": [
                {"path": "../../build/distributions/kcd.tar",
                 "label": "Assembly - Tar ${nextRelease.version}"},
                {"path": "../../build/distributions/kcd.zip",
                 "label": "Assembly - Zip ${nextRelease.version}"},
                {"path": "../../build/libs/kcd-*.jar",
                 "label": "Release Jar ${nextRelease.version}"},
                {"path": "../../build/libs/kcd-*-sources.jar",
                 "label": "Release SourceJar ${nextRelease.version}"}
            ]
        }],
        ["@semantic-release/exec", {
          "prepareCmd" : "./release-files.sh ${nextRelease.version}"
        }]
    ]
}

The above configuration includes some plugins from the Semantic Release toolset. Notable plugins are the github-Plugin and the exec-Plugin.

In the configuration, the exec-Plugin calls the release-files.sh script with the generated version number during the "prepare" phase. In this script, Gradle’s publish-Task is invoked with the provided version number, versioning the artifacts specified in build.gradle.kts and then publishing them.

release-files.sh
#!/usr/bin/env sh
cd ../..
./gradlew -Pversion=$1 publish

The github-Plugin attaches artifacts generated by Gradle to the GitHub release. The specified files can then be viewed through the Release tab of the GitHub project.

Releases

To use Semantic Release in a Gradle-based process, npm must first be installed. Additionally, Semantic Release and plugins must be installed with the command npm install conventional-changelog-conventionalcommits @semantic-release/exec. Finally, the release can be generated using npx semantic-release through npx, a runtime environment for Node packages.