Versioning NuGet packages in a continuous delivery world: part 1
On the Package Management team, we’re frequently asked how to think about versioning packages. Conceptually, it’s simple: NuGet (like many package managers) prefers semantic versioning (SemVer), which describes a release in terms of its backwards-compatibility with the last release. But for teams that have adopted continuous delivery, there’s tension between this simple concept and the reality of publishing packages.
This series of blog posts will cover strategies for resolving the tension. In this first one, we’ll cover SemVer, immutability of packages, and a really simple versioning strategy. Later posts will talk about some future thinking we’re doing in the versioning space, as well as a tool for Git users that seems to work really well.
The tension between SemVer and CD
With continuous delivery, we recommend that you produce and publish packages as an output of your CI build. Put those CI packages through your validation steps (automated testing, user acceptance testing, or whatever) and, when a particular package is deemed ready, you promote it to release status.
Did you spot the conflict? Each CI package produced needs a unique version number, so you must automatically increment the version number as you produce packages. As Xavier Decoster wrote a few years ago, this is a contradiction in terms: You’re “auto-versioning a yet unknown semantic version”. Put another way, we have a paradox: We need to pick a version number before we’ve had a chance to determine what the version number ought to be based on the contents of the package.
Brief intro to SemVer
Semantic version numbers have 3 numeric components, Major.Minor.Patch. When you fix a bug, increment patch (1.0.0 → 1.0.1). When you release a new backwards-compatible feature, increment minor and reset patch to 0 (1.4.17 → 1.5.0). When you make a backwards-incompatible change, increment major and reset minor and patch to 0 (2.6.5 → 3.0.0).
Immutability and unique version numbers
In NuGet, a particular package is identified by its name and version number. Once you publish a package at a particular version, you can never change its contents. But when you’re producing a CI package, you can’t know whether it will be version 1.2.3 or just a step along the way towards 1.2.3. You don’t want to burn the 1.2.3 version number on a package that still needs a few bug fixes.
SemVer to the rescue! In addition to Major.Minor.Patch, SemVer provides for a prerelease label. Prerelease labels are a “-” followed by whatever letters and numbers you want. Version 1.0.0-alpha, 1.0.0-beta, and 1.0.0-foo12345 are all prerelease versions of 1.0.0. Even better, SemVer specifies that when you sort by version number, those prerelease versions fit exactly where you’d expect: 0.99.999 < 1.0.0-alpha < 1.0.0 < 1.0.1-beta.
Producing CI packages
Xavier’s blog post describes “hijacking” the prerelease tag to use for CI. We don’t think it’s hijacking, though. This is exactly what we do on Visual Studio Team Services to create our CI packages. We’ll overcome the paradox by picking a version number, then producing prereleases of that version. Does it matter that we leave a prerelease tag in the version number? For most use cases, not really. If you’re pinning to a specific version number of a dependent package, there will be no impact, and if you use floating version ranges with project.json, it will mean a small change to the version range you specify.
I’m going to assume you’ve got a small component you want to package up. Make sure the repo’s in your Visual Studio Team Services account. If you don’t, you can create a new class library called “MyComponent” in Visual Studio. Let the IDE add the solution to version control, then push the repo to Visual Studio Team Services. Also, if you haven’t installed Package Management in your account, do that now and create a feed to use.
Create a new Visual Studio build for the repo. You can do that from the Build hub or from the “setup a build now” badge in the repo. Add two new steps to the build: NuGet Packager and NuGet Publisher. Move the Packager step up to right after the Visual Studio Test step, and under Automatic package versioning, choose Use the date and time. Enter the version number you want to build, for example, 1.0.0.
Leave the NuGet Publisher step at the bottom. Change its Feed type to Internal NuGet Feed. Paste in the NuGet URL from the feed you want to use.
The list of steps should look like the screenshot below.
When you save and choose Queue a build, the build system will create a package with a version number like “1.0.0-ci-20160502-100256”. That’s the version number you selected followed by an ever-increasing prerelease section, in this case based on date and time.
Sharing the package with others
Once you put a particular version of a package through its paces and decide it’s ready to release, you need to share it with others. The NuGet Publisher task works in Release Management just as they work in Team Build. While a full walkthrough is out of scope for this post, I recommend that you create a release definition that contains a NuGet Publisher task. You can point it either to another internal feed, or if you’re open-source-oriented, point it at NuGet.org. Queue a release off the build which produced your desired package – there, you’ve just released your first package from a CI stream!
Clarification [added May 5]: NuGet.org is not a good place to point your CI builds directly. It’s only for package versions you’re ready to release to the world. That’s what the previous paragraph intended to say, but it’s a little ambiguous.
See the next post, with more details about future investments that make versioning and releasing easier.