Skip to content

JavierSegoviaCordoba/semver-gradle-plugin

Repository files navigation

Kotlin version MavenCentral Snapshot

Build Coverage Quality Tech debt

Semver Gradle Plugin

Set projects versions based on git tags and following semantic versioning.

Inspired on Reckon but centered on supporting multi-project versions and combine normal stages with snapshot stage.

Apply the plugin

The plugin must be applied individually to each project in order to support configuration cache and project isolation.

plugins {
  id("com.javiersc.semver.gradle.plugin")
}

semver {
  tagPrefix.set("v") // optional, default is empty
}

Default values:

tagPrefix
default value (empty)
Optional Yes

Examples

Check documented examples or test examples to understand easily how it works.

Usage

There are three project properties which the plugin uses to detect automatically the current version based on the last tag in the current branch: semver.stage, semver.scope and semver.tagPrefix.

They can be set via CLI, for example:

./gradlew "-Psemver.stage=final" "-Psemver.scope=major" "-Psemver.tagPrefix=v"
  • semver.stage indicates the stage to be changed, for example, alpha
  • semver.scope indicates the scope to be changed, for example, patch
  • semver.tagPrefix is used to know which version is going to be changed based on the tag prefix, for example v. If the projects have different tag prefix, it is necessary to disambiguate which version is going to be bumped.

Default values:

stage scope tagPrefix
default value auto auto (empty)
Optional Yes* Yes* Yes*

Depends on the use case*

Plugin extension

semver {
  tagPrefix.set("")
}

It is used to asociate a project version with a tag prefix, and it allows having different versions in multi-project builds.

An example can be setting the extension prefix to v in a specific project and the last tags in the last commit are: 1.0.0 and v3.0.1. The project version is v3.0.1.

semver.tagPrefix project property via CLI or gradle.properties file

"-Psemver.tagPrefix=v"
semver.tagPrefix=v

If it is necessary to bump the project version, for example to v3.0.2 from v3.0.1, but at same time there are more project with different prefixes, the plugin needs to know which tag prefix is going to be bumped, so semver.tagPrefix property is the solution to that problem.

To get it working:

./gradlew "-Psemver.scope=patch" "-Psemver.tagPrefix=v"
Additional notes
Empty tag prefix for all projects

If all projects are not using a tag prefix, or in other words, the tag prefix is empty, both the property in the extension and the project property via CLI or gradle.properties file are irrelevant.

Same tag prefix for all projects

If all projects share a tag prefix, it is easier to set it in the root project gradle.properties file instead of passing it constantly via CLI.

Version types

Final

  • Format: <major>.<minor>.<patch>
  • Example: 1.0.0

Significant

  • Format: <major>.<minor>.<patch>-<stage>.<num>
  • Example: 1.0.0-alpha.1

Insignificant

  • Format: <major>.<minor>.<patch>-<stage>.<num>.<commits>+<hash or timestamp>
  • Examples:
    • 1.0.0.4+2021-11-11T14-22-03-207850300Z
    • 1.0.0.4+26f0484

Snapshot

  • Format: <major>.<minor>.<patch>-SNAPSHOT
  • Example: 1.0.0-SNAPSHOT

Stages

To change between stages, use the Gradle project property -Psemver.stage=<stage>

There are reserved stages that can be used to create certain versions:

  • final: It creates a version without a suffix stage, for example, 1.0.1.
  • auto: It calculates automatically the next stage based on the previous stage.
  • snapshot: It generate the next snapshot version, for example, 1.0.1-SNAPSHOT.
# gradle.properties
semver.tagPrefix=v
# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.stage=alpha" # v1.0.0-alpha.2
./gradlew "-Psemver.stage=beta" # v1.0.0-beta.1
./gradlew "-Psemver.stage=rc" # v1.0.0-rc.1
./gradlew "-Psemver.stage=snapshot" # v1.0.1-SNAPSHOT (uses the next patch version)
./gradlew "-Psemver.stage=final" # v1.0.0
./gradlew "-Psemver.stage=auto" # v1.0.0-alpha.2

# Last tag = v1.0.0
./gradlew "-Psemver.stage=alpha" # v1.0.1-alpha.1
./gradlew "-Psemver.stage=beta" # v1.0.1-beta.1
./gradlew "-Psemver.stage=rc" # v1.0.1-rc.1
./gradlew "-Psemver.stage=snapshot" # v1.0.1-SNAPSHOT (still uses the same patch version)
./gradlew "-Psemver.stage=final" # v1.0.1
./gradlew "-Psemver.stage=auto" # v1.0.1

Scopes

To change between scopes, use the Gradle property -Psemver.scope=<scope>

The scope has to be one of major, minor, patch or auto.

# gradle.properties
semver.tagPrefix=v
# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.scope=major" # v2.0.0
./gradlew "-Psemver.scope=minor" # v1.1.0
./gradlew "-Psemver.scope=patch" # v1.0.1
./gradlew "-Psemver.scope=auto" # v1.0.0-alpha.2 (uses the next num version)

# Last tag = v1.0.0
./gradlew "-Psemver.scope=major" # v2.0.0
./gradlew "-Psemver.scope=minor" # v1.1.0
./gradlew "-Psemver.scope=patch" # v1.0.1
./gradlew "-Psemver.scope=auto" # v1.0.1 (uses the next patch version)

Combine stages and scopes

# gradle.properties
semver.tagPrefix=v
# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=major" # v2.0.0-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=major" # v2.0.0-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=major" # v2.0.0-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=major" # v2.0.0
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=major" # v2.0.0-SNAPSHOT
./gradlew "-Psemver.stage=auto" "-Psemver.scope=auto" # v1.0.0-alpha.2

# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=minor" # v1.1.0-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=minor" # v1.1.0-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=minor" # v1.1.0-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=minor" # v1.1.0
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=minor" # v1.1.0-SNAPSHOT

# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=patch" # v1.0.1-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=patch" # v1.0.1-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=patch" # v1.0.1-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=patch" # v1.0.1
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=patch" # v1.0.1-SNAPSHOT

# Last tag = v1.0.0-alpha.1
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=auto" # v1.0.0-alpha.2
./gradlew "-Psemver.stage=beta" "-Psemver.scope=auto" # v1.0.1-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=auto" # v1.0.1-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=auto" # v1.0.1
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=auto" # v1.0.1-SNAPSHOT

# Last tag = v1.0.0
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=major" # v2.0.0-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=major" # v2.0.0-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=major" # v2.0.0-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=major" # v2.0.0
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=major" # v2.0.0-SNAPSHOT
./gradlew "-Psemver.stage=auto" "-Psemver.scope=auto" # v1.0.1

# Last tag = v1.0.0
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=minor" # v1.1.0-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=minor" # v1.1.0-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=minor" # v1.1.0-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=minor" # v1.1.0
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=minor" # v1.1.0-SNAPSHOT

# Last tag = v1.0.0
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=patch" # v1.0.1-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=patch" # v1.0.1-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=patch" # v1.0.1-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=patch" # v1.0.1
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=patch" # v1.0.1-SNAPSHOT

# Last tag = v1.0.0
./gradlew "-Psemver.stage=alpha" "-Psemver.scope=auto" # v1.0.1-alpha.1
./gradlew "-Psemver.stage=beta" "-Psemver.scope=auto" # v1.0.1-beta.1
./gradlew "-Psemver.stage=rc" "-Psemver.scope=auto" # v1.0.1-rc.1
./gradlew "-Psemver.stage=final" "-Psemver.scope=auto" # v1.0.1
./gradlew "-Psemver.stage=snapshot" "-Psemver.scope=auto" # v1.0.1-SNAPSHOT

Tasks

There are three tasks:

  • printSemver: Prints the tag in CLI and create a file in build/semver/version.txt which has two lines; the version without the tag and the version including the tag.
  • createSemverTag. Creates a git tag.
  • pushSemverTag. Creates and pushes a git tag to the remote.

You can combine them with any semver project properties to ensure the correct tag version is printed, created or pushed.

pushSemverTag can use a specific remote if the Gradle property semver.remote is set. If it is not set, origin is used if it exists, if not, the first remote by name is used. If there is no remote, the task fails.

Samples:

./gradlew createSemverTag

./gradlew createSemverTag "-Psemver.stage=alpha"

./gradlew pushSemverTag

./gradlew pushSemverTag "-Psemver.stage=alpha"

Set versions without timestamp on dirty repositories

By default, if the repository status is not clean, the version shows the timestamp but that can be avoided by setting the Gradle property semver.checkClean.

For example, if the last tag is 1.0.0, there are 23 commits between that tag and the last commit and the repo is not clean:

./gradlew "-Psemver.stage=final" "-Psemver.scope=patch"

semver: 1.0.0.23+2021-12-09T23-46-33-217289300Z
./gradlew "-Psemver.stage=final" "-Psemver.scope=patch" "-Psemver.checkClean=false"
semver: 1.0.1
./gradlew "-Psemver.checkClean=false"
semver: 1.0.0.23+1a2cd5b2 # 1a2cd5b2 is the last commit hash