DEV Community

Cover image for Semantic Versioning using GitVersion YAML file for your .NET, Java, and Kotlin projects' CI/CD
Sam Park
Sam Park

Posted on

Semantic Versioning using GitVersion YAML file for your .NET, Java, and Kotlin projects' CI/CD

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:

  1. Prepare your desired GitVersion.yml file.
  2. Add it to the root path of your project/Module directory, where your .git directory exists.
  3. Prepare the build:
    • If you are using .NET:
    • If you are using Java/Kotlin:
      • Add the necessary tasks to create the gitversion.json file by GitVersion CLI to parse the right version for your projects.
  4. 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:
......
Enter fullscreen mode Exit fullscreen mode

Add GitVersion.yml to the root path of your project

In .NET:

.NET GitVersion

In Java/Kotlin:

Java/Kotlin GitVersion

Preparing the build

In .NET:
Add GitVersion.MsBuild NuGet package to every single project in the solution:

GitVersion.MSBuild Nuget Package

In Java/Kotlin:

Note: This guid is for build.gradle.kts:
  1. 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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

The Semantic Versioning block generates the gitversion.json file in the root path of your project:

GitVersion Json File

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
}
Enter fullscreen mode Exit fullscreen mode

Let's take a look at what happens when you run the build task:

  • gitVersionOutputJSon task tries to make the gitversion.json file based on your project changes..
  • parseGitVersion task tries to parse the generated JSON file and take the 'SemVer' and assign it to the project.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
Enter fullscreen mode Exit fullscreen mode
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 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)