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.
- It seemed wrong that I had to use an
after_build
oron_success
step to explicitly build my nuget package - I didn't want every build to be prerelease
- 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.
What if your package isn't ready for even pre-release? Do you have a solution for testing a package locally? We've created some tools specific for our code base that work OK but it seems like there should be a better way.
I see pre-release packages as suitable for that world as well, if they're published to a private feed rather than a public one. Usually, people shouldn't be using pre-release packages with an expectation of things working, so I think you're covered. It certainly beats jumping through the hoops of adding an additional local-only workflow. The branch name, being part of the pre-release package name, makes it clear what the package is for.
If your packages are eventually heading to a public feed, I would consider having a private "dev" feed where things go until they're ready for public consumption. That way you can leverage appveyor and avoid maintaining an additional workflow for local development. You can probably extend my solution to redirect packages to the public feed based off the tag name (e.g. `internal-rc1` tag would mean private feed, plain `r1` would mean public feed).