What is Semantic Versioning?
Semantic Versioning (SemVer) is a versioning scheme that uses a three-part version number: MAJOR.MINOR.PATCH. It helps to convey meaning about the underlying changes in a new release:
- MAJOR version changes indicate incompatible API changes.
- MINOR version changes add functionality in a backward-compatible manner.
- PATCH version changes are for backward-compatible bug fixes.
What is GitVersion?
GitVersion is a tool that automatically generates a semantic version number based on your Git history. It analyzes your repository's commit history and branch structure to determine the appropriate version number.
All you need to do is:
- Prepare your desired
GitVersion.yml
file. - Add it to the root path of your project/Module directory, where your
.git
directory exists. - Prepare the build:
- If you are using .NET:
- Add
GitVersion.MsBuild
NuGet package to your every single project in the solution.
- Add
- If you are using Java/Kotlin:
- Add the necessary tasks to create the
gitversion.json
file byGitVersion CLI
to parse the right version for your projects.
- Add the necessary tasks to create the
- If you are using .NET:
- Boom! Build your solution (.NET) / main module (Java/Kotlin)
Preparing GitVersion.yml
You can use a detailed explanation of configuring your GitVersion.yml
file on gitversion.net to generate it. However, you can also use the sample prepared based on GitFlow (GitFlow/v1)
in my GitHub gist:
# This configuration uses GitFlow branching model which always has a main and a develop branch. see: https://nvie.com/posts/a-successful-git-branching-model/
# This configuration follows Semantic Versioning. see: https://semver.org/
# A good explanation on semantic versioning: https://semantic-versioning.org/
workflow: GitFlow/v1
assembly-versioning-scheme: MajorMinorPatchTag
assembly-file-versioning-scheme: MajorMinorPatchTag
assembly-informational-format: "{FullSemVer}"
tag-prefix: "[vV]?"
version-in-branch-pattern: (?<version>[vV]?\d+(\.\d+)?(\.\d+)?).*
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
no-bump-message: '\+semver:\s?(none|skip)'
tag-pre-release-weight: 60000
commit-date-format: "yyyy-MM-dd"
merge-message-formats: {}
update-build-number: true
semantic-version-format: Strict #ensure that versions are consistently formatted and that the versioning process remains predictable and reliable. This can be particularly important for projects with strict dependency management and release policies.
strategies:
- Fallback #This strategy is used when no other versioning strategy applies. It ensures that a version is always generated, even if no tags or commits indicate a version change.
#ConfiguredNextVersion: #This strategy allows you to manually specify the next version number. It's useful for scenarios where you want to control the versioning process directly.
- MergeMessage #This strategy increments the version based on the merge commit message. If the message contains specific keywords (e.g., "version bump"), the version number is incremented accordingly.
#- TaggedCommit #This strategy uses the commit tagged with a version number to determine the next version. It's useful for projects that follow a strict versioning policy based on tags.
- TrackReleaseBranches #This strategy tracks branches that are used for releases. It ensures that the version number is incremented based on the commits made to these branches.
- VersionInBranchName #This strategy extracts the version number from the branch name itself. It's useful for projects that use branch names to indicate version information.
branches:
......
Add GitVersion.yml
to the root path of your project
In .NET:
In Java/Kotlin:
Preparing the build
In .NET:
Add GitVersion.MsBuild
NuGet package to every single project in the solution:
In Java/Kotlin:
Note: This guid is for build.gradle.kts
:
- Open
build.gradle.kts
file for every single project and inject this code block:
val gitVersionJsonFilePath = "../gitversion.json"
tasks.register<Exec>("gitVersionOutputJSon") {
//If you are in in linux os
commandLine("sh", "-c", "gitversion /output json > $gitVersionJsonFilePath")
//If you are in Windows os
//commandLine("CMD", "/c", "gitversion /output json > $gitVersionJsonFilePath")
}
tasks.register("parseGitVersion") {
dependsOn("gitVersionOutputJSon")
doLast {
val jsonSlurper = JsonSlurper()
val gitVersionFile = file(gitVersionJsonFilePath)
val gitVersion = jsonSlurper.parse(gitVersionFile) as Map<*, *>
project.version = gitVersion["SemVer"].toString()
println("---> Project version set to: ${project.version}")
}
}
tasks.withType<Jar>().configureEach {
dependsOn("parseGitVersion")
}
tasks.named<Jar>("jar") {
from(sourceSets["main"].output)
manifest {
attributes(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
}
tasks.named("assemble") {
dependsOn("parseGitVersion")
}
tasks.named("build") {
dependsOn("parseGitVersion")
}
version = project.version
Note: To ensure the gitVersionOutputJSon
task is runnable, you need to install GitVersion CLI
on your operating system and set it as an environment variable
.
This block of code should be placed between
plugins {
java
`java-library`
`jvm-test-suite`
}
repositories {
mavenCentral()
}
group = "demo.java"
version = "0.1.0-SNAPSHOT"
and
dependencies {
testImplementation(platform("org.junit:junit-bom:5.11.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test {
useJUnitPlatform()
}
Finally we have something like this:
import groovy.json.JsonSlurper
plugins {
java
`java-library`
`jvm-test-suite`
}
repositories {
mavenCentral()
}
group = "demo.java"
version = "0.1.0-SNAPSHOT"
//-------------------------Semantic Versioning-------------------------------------
val gitVersionJsonFilePath = "../gitversion.json"
tasks.register<Exec>("gitVersionOutputJSon") {
commandLine("sh", "-c", "gitversion /output json > $gitVersionJsonFilePath")
}
tasks.register("parseGitVersion") {
dependsOn("gitVersionOutputJSon")
doLast {
val jsonSlurper = JsonSlurper()
val gitVersionFile = file(gitVersionJsonFilePath)
val gitVersion = jsonSlurper.parse(gitVersionFile) as Map<*, *>
project.version = gitVersion["SemVer"].toString()
println("---> Project version set to: ${project.version}")
}
}
tasks.withType<Jar>().configureEach {
dependsOn("parseGitVersion")
}
tasks.named<Jar>("jar") {
from(sourceSets["main"].output)
manifest {
attributes(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
}
tasks.named("assemble") {
dependsOn("parseGitVersion")
}
tasks.named("build") {
dependsOn("parseGitVersion")
}
version = project.version
//-------------------------------------------------------------------------------
dependencies {
testImplementation(platform("org.junit:junit-bom:5.11.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test {
useJUnitPlatform()
}
The Semantic Versioning block generates the gitversion.json
file in the root path of your project:
If you look at the file, you should have something like this:
{
"AssemblySemFileVer": "0.1.0.74",
"AssemblySemVer": "0.1.0.74",
"BranchName": "develop",
"BuildMetaData": null,
"CommitDate": "2024-12-03",
"CommitsSinceVersionSource": 74,
"EscapedBranchName": "develop",
"FullBuildMetaData": "Branch.develop.Sha.18f202d192ba86da3e6a3e7c8e197af5d512ded8",
"FullSemVer": "0.1.0-alpha.74",
"InformationalVersion": "0.1.0-alpha.74",
"Major": 0,
"MajorMinorPatch": "0.1.0",
"Minor": 1,
"Patch": 0,
"PreReleaseLabel": "alpha",
"PreReleaseLabelWithDash": "-alpha",
"PreReleaseNumber": 74,
"PreReleaseTag": "alpha.74",
"PreReleaseTagWithDash": "-alpha.74",
"SemVer": "0.1.0-alpha.74",
"Sha": "18f202d192ba86da3e6a3e7c8e197af5d512ded8",
"ShortSha": "18f202d",
"UncommittedChanges": 1,
"VersionSourceSha": "",
"WeightedPreReleaseNumber": 74
}
Let's take a look at what happens when you run the build
task:
-
gitVersionOutputJSon
task tries to make thegitversion.json
file based on your project changes.. -
parseGitVersion
task tries to parse the generated JSON file and take the 'SemVer' and assign it to theproject.version
- Now gradle uses this property to generate the
jar
file.
Congrats! you finish the local project side.
Let's explore the build server setup on GitHub to ensure the CI process runs smoothly
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
name: .NET Build and Test
on: [push, pull_request, workflow_dispatch]
jobs:
build_and_Test:
runs-on: ubuntu-latest
steps:
- name: Checkout #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/setup.md
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install GitVersion #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/setup.md
uses: gittools/actions/gitversion/setup@v3.0.0
with:
versionSpec: '6.x'
preferLatestVersion: true
- name: Determine Version #https://github.com/GitTools/actions/blob/main/docs/examples/github/gitversion/execute.md
uses: gittools/actions/gitversion/execute@v3.0.0
with:
useConfigFile: true
updateAssemblyInfo: true
- name: Setup .NET #https://github.com/actions/setup-dotnet
uses: actions/setup-dotnet@v4
- name: Available projects
run: dotnet sln list
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
name: Java Build and Test With Gradle
on: [push, pull_request, workflow_dispatch]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Set up JDK 23
uses: actions/setup-java@v4.5.0 #https://github.com/actions/setup-java
with:
java-version: '23'
distribution: 'oracle'
- name: Install GitVersion 6.0.5 for Gradle
run: |
wget -q -O gitversion.tar.gz https://github.com/GitTools/GitVersion/releases/download/6.0.5/gitversion-linux-x64-6.0.5.tar.gz
mkdir gitversion_extracted
tar -xzf gitversion.tar.gz -C gitversion_extracted
ls -R gitversion_extracted
sudo mv gitversion_extracted/gitversion /usr/local/bin/gitversion
sudo chmod +x /usr/local/bin/gitversion
- name: Setup Gradle 8.11.1
uses: gradle/actions/setup-gradle@v4 #https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#build-with-a-specific-gradle-version
with:
gradle-version: '8.11.1'
- name: Build with Gradle 8.11.1
run: gradle build --scan --warning-mode all
dependency-submission: # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
- name: Set up JDK 23
uses: actions/setup-java@v4.5.0
with:
java-version: '23'
distribution: 'oracle'
- name: Setup Gradle 8.11.1
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '8.11.1'
- name: Setup Gradle Wrapper
run: gradle wrapper
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v4
Top comments (0)