Prerelease packages with branches, Appveyor, and MyGet

We use a workflow where Appveyor is setup for CI and then it automatically pushes newly built nuget packages to a MyGet feed for consumption. Perhaps in a future post, I will go over how to set all this up, but for now, let's assume you already have this working; you push changes to a branch in your GitHub repo, which then gets built and tested on Appveyor, before being pushed to MyGet. Everything is nice and smooth.

Unfortunately, the magic ended there. Since there is no differentiation between pushing prerelease changes and release changes, I found that I would either have to limit what branches built in on Appveyor or spend a lot of time curating MyGet to remove intermediate builds I did not want used. I knew that MyGet supported prerelease packages but no matter what I tried, I could not get Appveyor to build them. Unsurprisingly, I found this frustrating. Then I stumbled on this particular answer on StackOverflow:

However, there were some issues I had with this.

  1. It seemed wrong that I had to use an after_build or on_success step to explicitly build my nuget package
  2. I didn't want every build to be prerelease
  3. It didn't work

The first point smelled enough that I wanted to see if I could not have to do that, and that second point seemed really important.

So, I delved a little deeper and discovered that the nuspec file, which has a handy $version$ substitution for the version takes that information from the value of the AssemblyInformationalVersion attribute, which I did not have declared in my AssemblyInfo.cs. Since it was not in there, the Appveyor step declared to patch it did not do anything. This was easy to fix, so I edited my AssemblyInfo.cs to include the attribute and tried again. This time the version updated as I wanted, even without the after_build or on_success shenanigans.

However, it still was not quite right since now, every build being performed was marked as prerelease. While this is a potential workflow, where the appveyor.yml is updated when finally reaching release, what I wanted was for releases to occur when I tagged a branch. For that, I looked at tweaking how the Appveyor build version updated and what environment variables Appveyor defined that I could leverage.

It turns out that Appveyor defines APPVEYOR_REPO_TAG, which is set to true if the build was started by a tag being pushed. It also defines APPVEYOR_REPO_BRANCH containing the name of the branch being built. Armed with these two variables, I updated my appveyor.yml to have two init scripts.

init:
- ps: $env:customnugetversion = if ($env:APPVEYOR_REPO_TAG -eq $True) { "$env:APPVEYOR_BUILD_VERSION" } else { "$env:APPVEYOR_BUILD_VERSION-$env:APPVEYOR_REPO_BRANCH" }
- ps: Update-AppveyorBuild -Version $env:customnugetversion

The first script creates a new environment variable. If the APPVEYOR_REPO_TAG is set to true, the new variable gets set to the value of APPVEYOR_BUILD_VERSION; if not, it is set to APPVEYOR_BUILD_VERSION-APPVEYOR_REPO_BRANCH. So, for example, if the build was going to be version 2.4.0, it was not a tag, and the branch was master, then the new variable would be set to 2.4.0-master; however, if it was a tag, it would just be 2.4.0.

The second script calls the Update-AppveyorBuild cmdlet provided by Appveyor, passing the value of the new environment variable as the -Version parameter value.

These two init scripts, plus the AssemblyInformationalVersion attribute in the AssemblyInfo.cs (and corresponding assembly_information_version field under the assembly_info section of the appveyor.yml) were all I needed. Now, whenever I push to a branch, I get a new prerelease nuget package that I can use in my development coding, and whenever I create a new tag, I get a release package instead. Not only does this reduce my need to manually manage my nuget packages on MyGet, but it also means I can take advantage of the different retention policy settings between prerelease and release packages.

All in all, I find this workflow much nicer than what I had before. Hopefully some of you do too. Examples of the appveyor.yml file and associated AssemblyInfo.cs change can be seen in the following Gist.