What version numbers should I assign to builds on different branches as part of continuous integration for NET Core-based projects

.net corecontinuous integrationsemantic-versioningversioning

Overview

I'm developing a number of .NET Core applications and I've met my current sprint commitments early. Until my next sprint begins, I'm using the slack to look into setting up a CI pipeline for my projects (this hasn't been hugely necessary as I'm the only person working on these projects, but it would be helpful to automate some of the stuff I'm currently doing manually).

However, I'm confused about the version numbering scheme I should adopt to support this.

Environment

In case it's helpful information, I'm attempting to set up this workflow using the following tools:

  • Visual Studio for code development
  • BitBucket to use as a remote repository
  • BitBucket Pipelines to use for CI builds
  • MyGet to use as the package feed and the location to push packages to during builds

Details

All the technical setup for this has gone pretty smoothly so far but I'm stumped trying to figure out what version numbers I should assign to builds on different branches while still complying with semantic versioning and a GitFlow-style workflow.

Let's say that my previous release of some project X is 1.1.0. If I commit some change on develop and publish it to my repository (triggering a build), what version should be assigned to the NuGet package produced from that code?

Quoting nvie's recommendation here:

It is exactly at the start of a release branch that the upcoming
release gets assigned a version number—not any earlier. Up until that
moment, the develop branch reflected changes for the “next release”,
but it is unclear whether that “next release” will eventually become
0.3 or 1.0, until the release branch is started. That decision is made on the start of the release branch and is carried out by the project’s
rules on version number bumping.

This makes sense to me and would suggest that – until I create a release branch – I should stick with version numbering like e.g. 1.1.0-unstable0023. However, in semantic versioning schemes a version like this is taken to represent a release leading up to v1.1.0 i.e. an earlier build, which is not what I want.

To further complicate things, the dotnet CLI lets you assign the version suffix (e.g. during a CI build) but not any other parts of the version (the major, minor or patch number) – these are determined strictly from the project.json file corresponding to the project that's being built.

For what it's worth, here's what my bitbucket-pipelines.yml looks like for my first adapted project so far:

image: microsoft/dotnet:onbuild

pipelines:
  branches:
    develop:
      - step:
          script:
            -BUILD_CONFIGURATION=Debug

            # Generate build number
            # Note: may adapt this to use GitVersion.exe instead 
            - BUILD_NUMBER=`git log --oneline | wc -l`
            - echo "Build number':' ${BUILD_NUMBER} (will be appended to the generated NuGet package version)"

            # Install NuGet
            - apt-get update && apt-get install -y nuget

            # Add credentials
            - nuget setapikey $MYGET_API_KEY -source https://www.myget.org/F/company/api/v3/index.json -configFile NuGet.Config
            - nuget sources update -name "Company MyGet Feed" -source https://www.myget.org/F/company/api/v3/index.json -user $MYGET_USER -pass $MYGET_PASS -StorePasswordInClearText -configFile NuGet.Config

            # Restore and test projects
            - dotnet restore
            - dotnet test test/<<Company>>.<<Product>>Web.BaseTypes.Tests
            - dotnet test test/<<Company>>.<<Product>>Web.DeviceComponents.Tests
            - dotnet test test/<<Company>>.<<Product>>Web.Utility.Tests

            # Pack projects
            - dotnet pack --configuration $BUILD_CONFIGURATION --version-suffix=build$BUILD_NUMBER project.json src/<<Company>>.<<Product>>Web.BaseTypes
            - dotnet pack --configuration $BUILD_CONFIGURATION --version-suffix=build$BUILD_NUMBER project.json src/<<Company>>.<<Product>>Web.DeviceComponents
            - dotnet pack --configuration $BUILD_CONFIGURATION --version-suffix=build$BUILD_NUMBER project.json src/<<Company>>.<<Product>>Web.Utility
            - dotnet pack --configuration $BUILD_CONFIGURATION --version-suffix=build$BUILD_NUMBER project.json src/<<Company>>.<<Product>>Web.Utility.JsonPathGrammar

            # Push generated packages
            # TODO

Current versioning strategy

At the moment, I assign version numbers following this format for release builds:

<major>.<minor>.<patch>

I bump the version number as soon as I start a release branch, so any pre-release builds from a release branch will be generated just by appending a -betaxxxx suffix:

<major>.<minor>.<patch>-beta0000, <major>.<minor>.<patch>-beta0001, ...

If I need to produce a pre-release version and I haven't started a release branch yet, I will manually bump the version numbers in the relevant project.json files and produce builds like this:

<major>.<minor>.<patch>-unstablexxxx

Where xxxx is the padded number of commits on develop since the previous release.

If anyone has suggestions on a good version numbering scheme that works for .NET Core projects being adapted for a CI/CD workflow, I'd appreciate that a lot.

Best Answer

You need to better understand the specific requirements you have from versioning.

Most of my cases are end user applications. In this scenario a single version number is sufficient, all I require from the version is to match it to a commit ID.

Other, less frequent, cases are libraries. In this case there is an additional requirement to match to an API version. v123.2 and v124.1 refers to API versions 124 and 123, while 1 and 2 refers to a sequential patch number (it might refer to some bug corrected in both API versions).

This a summary of my requirements. You need to understand yours and be aware to not fall to a situation where mantaining version numbers hinders development, that is not the point. Latelly I've been using a build process to inject the actual commit id, but not sure if I like it that much.