Skip to main content

Automating Semantic Versioning Releases - DotNet

This process was developed after the React/NodeJS and Python/Streamlit processes.

Use the Automation Scripts when Making a Release

You are "making a release" when you are at a good point to merge code from the dev branch into master/main.

These merges should always be done via Pull Requests

When you're ready to make a release, follow these steps:

  1. Commit and push all of the relevant code to the dev branch.

  2. Decide what kind of version change this is: Major (large changes, like language version upgrades or version-breaking package upgrades), Minor (new features, non-breaking upgrades), Patch (changes, bug fixes, etc.)

  3. Run the version bump script with the following command (specifying major/minor/patch):

    ./scripts/bump_version.cmd patch

    This will:

    • Update the version in all changed .csproj files
    • Create a commit with the version bump
    • Create a git tag for the new version
    • Push the changes and tag to the remote repository
  4. Go to GitHub and create a new Pull Request:

    • Set the base branch to master
    • Set the compare branch to dev
    • Use a title like "Release vX.Y.Z" (where X.Y.Z is the new version)
    • In the description, list the major changes in this release
    • Request a review from a team member
  5. After the PR is accepted and merged, you (or the reviewer) can run the post-merge script with the command:

    ./scripts/post_merge.cmd

    This will:

    • Switch to the master branch
    • Pull the latest changes
    • Push the version tag to the remote repository

Add Automation to a new Dotnet Project

These instructions assume your project is starting at version 0.0.1. If you're starting at a different version number, adapt these instructions appropriately.

  1. Ensure your solution's main project .csproj file has a version property. For example:

    <PropertyGroup>
    <Version>0.0.1</Version>
    </PropertyGroup>
  2. Under the repository's root directory, add a scripts folder if one doesn't exist.

  3. Create a new file under the scripts directory called bump_version.cmd with the following content:

    @echo off
    setlocal enabledelayedexpansion

    REM =============================================
    REM Input Validation
    REM =============================================
    REM Check if a version bump type is provided
    if "%1"=="" (
    echo Error: Please specify the version bump type: major, minor, or patch.
    exit /b 1
    )

    REM Validate the version bump type
    if not "%1"=="major" if not "%1"=="minor" if not "%1"=="patch" (
    echo Error: Invalid version bump type. Use 'major', 'minor', or 'patch'.
    exit /b 1
    )

    REM =============================================
    REM Get Current Version
    REM =============================================
    REM Read the current version from the main project's .csproj file
    for /f "tokens=*" %%a in ('powershell -Command "$csproj = Get-ChildItem -Path . -Filter *.csproj -Recurse -File | Select-Object -First 1; if ($csproj) { (Get-Content $csproj.FullName) -match '<Version>.*</Version>' | ForEach-Object { $_ -replace '<Version>|</Version>', '' } } else { Write-Error 'No .csproj file found in current directory or subdirectories' }"') do set CURRENT_VERSION=%%a

    REM Verify we found a version
    if "%CURRENT_VERSION%"=="" (
    echo Error: Could not find version in .csproj file.
    echo Please ensure you have a Version property in your .csproj file like this:
    echo ^<PropertyGroup^>
    echo ^<Version^>0.1.0^</Version^>
    echo ^</PropertyGroup^>
    exit /b 1
    )

    REM =============================================
    REM Version Increment Logic
    REM =============================================
    REM Split the current version into its components (major.minor.patch)
    for /f "tokens=1,2,3 delims=." %%a in ("%CURRENT_VERSION%") do (
    set MAJOR=%%a
    set MINOR=%%b
    set PATCH=%%c
    )

    REM Increment the appropriate version component based on the bump type
    if "%1"=="major" (
    REM Major version bump: increment major, reset minor and patch
    set /a MAJOR+=1
    set MINOR=0
    set PATCH=0
    ) else if "%1"=="minor" (
    REM Minor version bump: increment minor, reset patch
    set /a MINOR+=1
    set PATCH=0
    ) else if "%1"=="patch" (
    REM Patch version bump: increment patch only
    set /a PATCH+=1
    )

    REM Construct the new version string
    set NEW_VERSION=%MAJOR%.%MINOR%.%PATCH%
    echo Bumping version from %CURRENT_VERSION% to %NEW_VERSION%

    REM =============================================
    REM Version Update Process
    REM =============================================
    REM Get the last version tag to check for changes
    for /f "tokens=*" %%a in ('git describe --tags --abbrev=0 2^>nul') do set LAST_TAG=%%a
    if "%LAST_TAG%"=="" (
    REM If no previous tag exists, update all projects
    echo No previous version tag found. Updating all project versions.
    for /r %%f in (*.csproj) do (
    echo Updating version in %%~nxf
    powershell -Command "$file = '%%f'; Write-Host 'Processing file: ' $file; $content = Get-Content $file -Raw; Write-Host 'Current content contains version: ' ($content -match '<Version>.*?</Version>'); $newContent = $content -replace '<Version>.*?</Version>', '<Version>%NEW_VERSION%</Version>'; Write-Host 'New content contains version: ' ($newContent -match '<Version>.*?</Version>'); Set-Content -Path $file -Value $newContent -NoNewline"
    )
    ) else (
    REM Process each .csproj file in the solution
    for /r %%f in (*.csproj) do (
    REM Get the project's directory path
    for %%d in ("%%~dpf.") do set PROJECT_DIR=%%~fd

    REM Check if any files in this project have changed since the last tag
    git diff --name-only %LAST_TAG% %PROJECT_DIR% > nul 2>&1
    if !ERRORLEVEL! equ 0 (
    REM Project has changes - update its version
    echo Updating version in %%~nxf
    powershell -Command "$file = '%%f'; Write-Host 'Processing file: ' $file; $content = Get-Content $file -Raw; Write-Host 'Current content contains version: ' ($content -match '<Version>.*?</Version>'); $newContent = $content -replace '<Version>.*?</Version>', '<Version>%NEW_VERSION%</Version>'; Write-Host 'New content contains version: ' ($newContent -match '<Version>.*?</Version>'); Set-Content -Path $file -Value $newContent -NoNewline"
    ) else (
    REM Project unchanged - skip version update
    echo Skipping unchanged project: %%~nxf
    )
    )
    )

    REM =============================================
    REM Git Operations
    REM =============================================
    REM Stage all modified files
    git add .

    REM Create a commit with the version bump
    git commit -m "chore(release): %NEW_VERSION%"

    REM Create a new version tag
    git tag v%NEW_VERSION%

    REM Push changes and tags to remote
    git push --follow-tags

    echo Version bumped to %NEW_VERSION%.
  4. Create a new file under the scripts directory called post_merge.cmd with the following content:

    @echo off
    setlocal enabledelayedexpansion

    REM =============================================
    REM Git Operations - Current Branch
    REM =============================================
    REM Pull the latest changes from the current branch
    git pull
    if %ERRORLEVEL% neq 0 (
    echo Error pulling the latest changes.
    goto :cleanup
    )

    REM =============================================
    REM Switch to Master Branch
    REM =============================================
    REM Switch to the master branch
    git checkout master
    if %ERRORLEVEL% neq 0 (
    echo Error switching to master branch.
    goto :cleanup
    )

    REM =============================================
    REM Git Operations - Master Branch
    REM =============================================
    REM Pull the latest changes from master
    git pull
    if %ERRORLEVEL% neq 0 (
    echo Error pulling the latest changes from master.
    goto :cleanup
    )

    REM =============================================
    REM Version Tag Operations
    REM =============================================
    REM Get the current version from the master project's .csproj file
    for /f "tokens=*" %%a in ('powershell -Command "$csproj = Get-ChildItem -Path .\EpanetConsoleCore -Filter *.csproj -File | Select-Object -First 1; if ($csproj) { (Get-Content $csproj.FullName) -match '<Version>.*</Version>' | ForEach-Object { $_ -replace '<Version>|</Version>', '' } } else { Write-Error 'No .csproj file found in EpanetConsoleCore directory' }"') do set NEW_VERSION=%%a

    REM Verify we found a version
    if "%NEW_VERSION%"=="" (
    echo Error reading version from .csproj file.
    echo Please ensure you have a Version property in your .csproj file like this:
    echo ^<PropertyGroup^>
    echo ^<Version^>0.1.0^</Version^>
    echo ^</PropertyGroup^>
    goto :cleanup
    )

    REM Push the version tag to the remote repository
    git push origin v%NEW_VERSION%
    if %ERRORLEVEL% neq 0 (
    echo Error pushing the tag v%NEW_VERSION%.
    goto :cleanup
    )

    echo Successfully pushed tag v%NEW_VERSION% to origin.

    :cleanup
    REM Always switch back to dev branch
    git checkout dev
    if %ERRORLEVEL% neq 0 (
    echo Warning: Failed to switch back to dev branch.
    exit /b 1
    )

    exit /b 0