2019-04-15

automate your .NET versioning with GitVersioning

Nerdbank.GitVersioning makes versioning easy for .NET projects. It gives you deterministic versions for each git commit. The calculated version will be same locally as it is on the CI server. A demo is the easiest way to explain:

PS C:\Users\taggac\github\Bolero> dotnet tool install -g nbgv
You can invoke the tool using the following command: nbgv
Tool 'nbgv' (version '2.3.138') was successfully installed.

PS C:\Users\taggac\github\Bolero> git checkout master
PS C:\Users\taggac\github\Bolero> nbgv get-version -v SemVer2
0.0.219-73246e6f61

By default, the semantic version created has major and minor identifiers of 0, the patch identifier is the git height, and the pre-release identifier is the first 10 characters of the last git commit checksum. Each time you commit, the git height increases and the git commit checksum changes. When you update the version.json, the git height resets to 1. GitVersioning is highly configurable and I'll show you the setting that I like.

PS C:\Users\taggac\github\Bolero> git checkout nbgv
PS C:\Users\taggac\github\Bolero> nbgv get-version -v SemVer2
0.4.6-cabc4b6

PS C:\Users\taggac\github\Bolero> cat .\version.json
{
    "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
    "version": "0.4",
    "nugetPackageVersion": {
        "semVer": 2
    },
    "gitCommitIdShortAutoMinimum": 7,
    "publicReleaseRefSpec": [
        "^refs/heads/master$",
        "^refs/heads/\\d+\\.\\d+$"
    ]
}

The $schema is optional and just helps Visual Studio Code and other editors with IntelliSense for the file. The version is the major.minor. NuGet finally supports Semantic Versioning 2.0.0. For GitVersioning, this avoids a 'g' prefix before the checksum. gitCommitIdShortAutoMinimum is a feature that I added to GitVersioning. Instead of 10 characters of the last git commit checksum, the length matches the short according to git. You can see it with `git rev-parse --short HEAD`. GitHub and lots of other tools also just show the git commit short. The publicReleaseRefSpec is a list of regular expressions for branches that you wish to produce normal versions for instead of pre-release versions. In this case, it would produce normal versions for `master`and versioned branches such as `0.4`and `0.5`.

PS C:\Users\taggac\github\Bolero> git checkout master
PS C:\Users\taggac\github\Bolero> git merge --squash nbgv
PS C:\Users\taggac\github\Bolero> git commit -m "use GitVersioning"
PS C:\Users\taggac\github\Bolero> nbgv get-version -v SemVer2
0.4.1

It was the first commit containing the version of  0.4 in version.json, which is why the git height was reset to 1. A normal version was produced instead of a pre-release version, because it was on `master` branch.

Release Process

Above, I showed how to create a deterministic version with GitVersioning. Later on, I'll show how to add the version to your .NET assemblies, .NET NuGet packages, and CI build labels.

Prior, I've often created normal versions from git tags. A git tag would trigger a CI build, and the build script would know that it was a tag and produce a normal version instead of a pre-release version. With GitVersioning, each commit to master will create a new normal version. If you have a CD process triggered from normal versions, you may wish to create a `dev` branch or similar and push to `master` when you want to trigger the CD process.

For most OSS projects I contribute to, we don't have a CD process like that. One or two may push master builds directly to NuGet.org, but most don't. They publish builds to a NuGet feed, such as AppVeyor or MyGet.org and promote builds to NuGet.org manually. Either process works, depending on the project. I like it when all NuGet packages from all builds are published to an intermediate NuGet feed. It makes it easier for contributors to test out their contributions.

Versioning .NET Assemblies

Just adding a reference in each project file or in a Directory.Build.props will allow GitVersioning to generate the AssemblyInfo attributes for you. All the version attributes are set by GitVersioning. I recently made it work for F# projects too:


Versioning NuGet Packages

The above PackageReference will also add a version for `dotnet pack` as well. If you are using a nuspec or a paket.template instead, then you must pass in the version in your build script.

Versioning CI Build Labels

GitVersioning can help with setting the CI build labels by adding this to version.json:

    "cloudBuild": {
        "buildNumber": {
            "enabled": true
        }
    }

Usually, I prefer to set the CI build label myself at the beginning of a build script. For example with AppVeyor, you can add this to appveyor.yml:

before_build:
- ps: |
    dotnet tool install -g nbgv
    $env:SEMVER = cmd /c $HOME\.dotnet\tools\nbgv get-version -v SemVer2
    Update-AppveyorBuild -Version "$env:SEMVER ($env:APPVEYOR_BUILD_ID)"

AppVeyor also requires the build label to be unique, so I added its unique build ID in parenthesis. The build ID is a part of the URL. Here, you can see these two commits:



produced these two AppVeyor builds:



and these NuGet artifacts that are available in the AppVeyor project NuGet feed from the most recent build:

Compatability with Source Link

Before I wrap up this blog post, it is worth mentioning coreclr bug 21606. It is fixed in dotnet 3.x, but has not yet been backported to dotnet 2.x. Both GitVersioning and Source Link use native library libgit2. The native library dependencies need to be compatible in dotnet 2.x or dotnet barfs. The latest beta2 of Source Link is not compatible. The previous beta is:

<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-63127-02" PrivateAssets="All" />

Gratitude

A big thank you to Andrew Arnott who created and maintains GitVersioning. He made it easy for me to contribute a couple of pull requests. We've worked together before. He helped contribute to Source Link. It is a great project and I encourage adoption.