Azure DevOps - Always Be Shipping!

Fun with Azure DevOps NuGet package versioning!

This post was most recently updated on April 11th, 2020.

Reading Time: 6 minutes.

This article will briefly explain the different NuGet package versioning schemes – both automatic and manual – available. Then we’ll take a look at how to implement a nifty, and quite frankly, downright elegant automatic versioning scheme for your NuGet packages.


Okay – returning from quite a trip down another rabbit hole, I think it’s a good time to document some of my findings in regards to Azure DevOps NuGet package versioning!

This seems to be another area, where the documentation certainly does exist, but most of it just didn’t answer our questions. A task that should have been easy and straightforward to solve, really wasn’t. At all.

So either our use case was unique, we were asking the wrong questions, or the documentation wasn’t comprehensive enough – I’ll leave that distinction to someone else!

So, what were we doing, then?

Description

We were configuring Azure DevOps build pipelines with the intention of publishing new package versions to our organization’s internal NuGet stream automatically with each successful Release build of the package. This would enable us to easily share different versions of our package by promoting each (somewhat) stable build to the PreRelease stream, from which different teams can effortlessly access them for testing purposes before picking the stable versions for their production builds.

My configuration and requirements were as follows:

  1. .NET Framework Project
  2. Azure DevOps pipeline configured with YAML
  3. An internal NuGet feed to publish the packages to
  4. Unique, incremental semver-compatible version numbers for each update of the package
  5. No need to bump the number manually

That said, onwards with the configuration! First, let’s take a look at our options.

Which versioning options do we have and how do they function?

There is a few different options to choose from, when you’re configuring your build pipeline’s NuGet packaging -step’s versioning scheme. The next chapter is meant to help you choose the one that suits you the best!

Available versioning schemes

  • byPrereleaseNumber
    • Configure the package version with a number of other switches, namely:
      • majorVersion
      • minorVersion
      • patchVersion
      • packTimezone (documentation says this is required, but it just seemed to default to UTC without)
  • byEnvVar
    • Reference a variable to set the package version
  • byBuildNumber
    • Build NAME is used as the number – it needs to be something that can be parsed as a build number. Note that this’ll also literally change the names of your builds in the build history and all emails.
  • off
    • Configure the package version using either the project file or a separate nuspec file.

How to choose which versioning scheme to use for your Azure DevOps NuGet packaging?

I don’t know about you, but trying to figure out, based on the documentation, how the versions are going to look like with these options, turned out to be kind of complicated.

Quite truthfully, none of these options appeared to suit our needs. “ByBuildNumber” caused the side effect of the builds being renamed and limited us into using a few predefined formats. It’s also kind of stupid – instead of having a “date.revision – comments” as the build / pipeline execution name, you’ll have a number configured in the build “name” as the execution name, too.

What do I mean? I mean, you need to set this (or similar):

 name: $(BuildDefinitionName)_$(Year:yyyy).$(Month).$(DayOfMonth)$(Rev:.r)

// much later...

- task: [email protected]
  inputs:
    command: pack
    packagesToPack: '**/[projectname].csproj.nuspec'
    versioningScheme: byBuildNumber
    packDestination: '$(Build.ArtifactStagingDirectory)\nugetpkg'
 

That’ll define your NuGet package name. But it’ll also rename the build. And hell, I tried multiple different versions, but it just generates annoying results.

Modifying the name of the release definition (with the side effects it causes) to change the name of the generate NuGet package sounds… Wrong.

Besides, $(Rev:.r) will only increment for each build of the day. So your NuGet package versioning is going to be date + build number (or similar).

$(Rev:r)2 (The third run on this day will be 3, and so on.)

Use $(Rev:r) to ensure that every completed build has a unique name. When a build is completed, if nothing else in the build number has changed, the Rev integer value is incremented by one.

If you want to show prefix zeros in the number, you can add additional ‘r’ characters. For example, specify $(Rev:rr) if you want the Rev number to begin with 01, 02, and so on.

Source

On the other hand, “ByPrereleaseNumber” would’ve been great, but you actually can’t get rid of the date&time in the version – which was FRUSTRATING. “ByEnvVar” wasn’t too bad since you could configure a pipeline variable to do whatever you want, but making that into a semver-compatible incremental number… Well, let’s just say there’s no apparent way to do that. You’d have to always change the value of the variable per build, or write custom PowerShell logic in your build to change the value during the execution.

In the end, “Off” seemed like it was the closest one – with that, I could’ve moved the responsibility from Azure DevOps to myself, and just updated a nuspec file to always contain the desired version number. Sometimes, manual work is preferable to crappy automation, right?

Well, truthfully, that didn’t look very desirable to me either, as it would’ve required me to increment the version number manually, or trust an add-in to do it – something, that I know would eventually fail. Either I’d forget to update it, or someone without the add-in would commit a duplicate version number in.

Blah.

Instead, we wanted to always have each build from master (essentially, only after a successful merge) generate a new version of the package with a new semver-compatible version number. How do we do this?

This brings us to the actual question:

How to configure Azure DevOps to automatically increment NuGet package version using semver?

After quite a lot of googling, we encountered this masterpiece of an article:

https://kasunkodagoda.com/2019/04/03/hidden-gems-in-azure-pipelines-creating-your-own-rev-variable-using-counter-expression-in-azure-pipelines/

BOOM! This article describes exactly what we needed. Finding it took a while, but now this is a great opportunity to expand on the material, and document what we did and how it’s working for us!

The article outlines a simple way to build a semver-like versioning scheme based on a few pipeline variables.

Source of this picture: https://kasunkodagoda.com/2019/04/03/hidden-gems-in-azure-pipelines-creating-your-own-rev-variable-using-counter-expression-in-azure-pipelines/

Using this,

Below is an example of our configuration in YAML:

- task: [email protected]
  inputs:
    command: pack
    packagesToPack: '**/[projectname].csproj.nuspec'
    versioningScheme: byEnvVar
    versionEnvVar: PackageVersion
    packDestination: '$(Build.ArtifactStagingDirectory)\nugetpkg'

And how it’s in the pipeline variables:

With this, I have an incremental, automatic Patch-version of my PackageVersion variable, with Major and Minor being updated manually by yours truly. I also have the optional PackageVersionType, in case I want to label a package explicitly as being “preview” or anything else.

The original article didn’t have this as copypasteable text, so I suppose I’m doing us all a service by spelling it out down below:

$[counter(format('{0}.{1}', variables['Major'], variables['Minor']), 0)]

That’s it for now!

References

This blog post was an interesting investigation and a deep-dive into Microsoft’s documentation. Below are some of the more useful sources we went through:

mm
0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments