NuGet & TFS Preview: a challenging combination
I've recently spent some time trying to come up with an end-to-end demo of using NuGet on TFS Preview, and I wanted to share you my findings.
My scenario looks as follows:
- NuGet packages are restored from an HTTPS endpoint on MyGet.org
- The build agent auto-increments the build number and check-in the changes to the AssemblyInfo.cs file
- NuGet packages are created using this incremented version
- NuGet packages are pushed to an HTTPS feed on MyGet.org
This is quite a basic Continuous Integration set-up which has worked for me before on TeamCity. However, on TFS Preview I would soon find out it's not as straightforward as one would think it is...
If we try to visualize the above scenario, you'd end up with the following flow:
Now how to implement this? I didn't want to customize any build definition templates for a multitude of reasons, but let's stick to the fact I didn't want to :) That leaves me with few other alternatives: MSBuild, PowerShell and NuGet.exe. I've been in this situation before and was hoping to be able to reuse some of the stuff I used on TeamCity. So even if you don't use TFS or TFS Preview at all, I hope you'll find some useful information in this post.
Expect some challenges
First things first, let's start with the HTTPS endpoint from which the build agent should restore NuGet packages in a pre-build step. When enabling the NuGet package restore feature on your solution, the NuGet-based Microsoft Package Manager (aka NuGet VSIX, aka NuGet Visual Studio Extension) is creating a .nuget folder in your solution directory, injects some files and modifies some if not all of your project files. The files that are being fetched are: nuget.config, nuget.exe and the nuget.targets MSBuild file. Enabling package restore is an optimal experience with little hassle (disabling however isn't). Now where is the catch?
Package restore actually is a call to nuget.exe install with some parameters. One of them is the -source option, allowing you to define which package source should be used to fetch packages from. If the -source option points to an HTTPS endpoint, guess what, you'll be prompted for credentials. So we need to feed the tool with the required credentials in a non-interactive way. Luckily enough, there's an option for this as well: simply add the -NonInteractive switch. This will instruct the NuGet command line tool to silently look for credentials in the nuget.config file. Perfect, right?
NuGet v2.1 is now supporting hierarchical configurations, so adding these credentials to our solution-level nuget.config should work. At least, that's what I thought... Turns out it doesn't take into account credentials yet (I logged an issue). However, I know that nuget.exe does check the nuget.config in the build account's roaming user profile... IF you use the nuget.exe from that location, which doesn't exist, so I have to copy it first. You can see things can quickly get complicated here: a NuGet bug (?) requires me to first copy over nuget.exe to the %AppData%\NuGet folder (which I also must create), after which I also need to run a nuget.exe command to register the package source and its credentials... And all this must happen before package restore even kicks in. Obviously, that would also mean you need to do some cleanup at the end of the build process as well, unless you run on TFS Preview (because builds run in a sandboxed environment).
All of this can easily be done using a few MSBuild instructions and nuget.exe commands, and this is how I found out that the build agent seems to be running some exotic language pack on the OS :)
Edit: Actually, it's not. As Chris Patterson explains in a comment to this post, this was bug for which a fix will be deployed soon.
Here's the path to the build account's roaming profile directory: C:\Users\畢汩杤敵瑳\AppData\Roaming. I remember the build user is named buildguest, but because of these exotic characters I occasionally had to switch between %appdata% and $(APPDATA) in my MSBuild file.
Restore works, next up: versioning
I've been doing auto-versioning before using the MSBuild Extension Pack which contains an AssemblyVersionTask. However, I also noticed that this task's changes to the AssemblyInfo.cs file are being undone! This happens inside the DLL that is containing this MSBuild Task, so I created my own NuGet.MSBuildExtensions.dll (currently very basic and only fixing this scenario). Undoing changes is not desirable, as we need to keep track of the version change. This means that these changes need to be committed into source control. To do that in a non-interactive way, you need to use tf.exe and know the credentials of the account to be used to check-in these changes. Another challenge: what are my TFS Preview credentials? I use a Windows Live ID for authentication, so should I simply use my email and password? Nope, turns out there's a TFS Service Credential Viewer which allows you to authenticate using your Windows Live ID and fetch your actual username and encrypted password. This is what you should be using when checking in those changes using tf.exe.
Easy, let's create packages
Here's another breaking change coming into play: those familiar with TFS know that all TFS output by default ends up into a Binaries folder, which is a sibling folder of the Sources folder where your Build Definition workspace gets checked out. Big news: this folder got renamed to bin! Again, I found out by trial and error... This also means you need to ensure that your project output ends up in this bin folder for the Release configuration (assuming you are building in Release mode). Simply change this in your project's properties: replace "bin\Release" by "..\..\bin\", and do this for each project that should produce a NuGet package. You can optionally create a NuSpec file and make it available next to your project file to add some additional package metadata.
All of the above makes it very cumbersome to support this scenario on TFS Preview at the moment. I'm sure if you'd go the custom build definition template way you'd end up with something very similar but less portable. I prefer this MSBuild + command line approach because it allows me to partially reuse some scripts on another environment such as TeamCity, which definitely has the lead at the moment in supporting NuGet in Continuous Integration scenarios. Adding the above "hacks" to the visualization gives us the following image:
I've uploaded the relevant sources to this post for those interested. I'd rather see first-class support for NuGet rather soon (meaning full support for this scenario as a bare minimum)...
Leave a Comment