Since NuGet 2.7, there is a new approach to Package Restore. In short, it involves executing “nuget.exe restore” before building the solution or project, instead of having each project import the “nuget.targets” file. This new restore workflow solves a number of issues, especially with packages containing MSBuild customizations, but also with parallel builds conflicting when performing the restore in parallel.
Additionally, Team Foundation Server 2013’s Team Build implements this new Package Restore workflow in its default build process templates for both TFVC and Git repositories without any effort. This functionality is implemented care of the new RunMSBuild workflow activity (not to be confused with the original MSBuild workflow activity).
The RunMSBuild activity internally uses another new activity named “NuGetRestore”, which is also conveniently a public type you can use directly in customized build process templates. The NuGetRestore activity simply runs “nuget.exe” via the InvokeProcess activity to perform the real work, so there is no special TFS-only behaviour.
However, by default, the copy of “nuget.exe” that is used for the restore is located in the same folder as the assembly declaring the NuGetRestore activity (Microsoft.TeamFoundation.Build.Activities.dll) typically located in “C:\Program Files\Microsoft Team Foundation Server 12.0\Tools”. The version of this “nuget.exe” that ships with TFS 2013 RTM is version 2.7 but there is a good chance there will regularly be a newer NuGet available than the version shipped with Team Build, and with features you need or want. For example, version 2.8 was recently released and the new Fallback to Local Cache feature would be one handy way to improve build resiliency when the build agent can’t always connect to a NuGet repository.
I’ve done some research and I have found there are basically two options available for using a newer version of NuGet in your Team Builds now:
- Remote to each Team Build Agent with local Administrator privileges, and execute “nuget.exe update -self” on the file located in the TFS Tools folder mentioned above, or …
- Customize your build process XAML file in two places:
- Set the “RestoreNuGetPackages” argument to “false” on the RunMSBuild activity to avoid using the default “nuget.exe”.
- Insert the NuGetRestore activity immediately before RunMSBuild set the “ToolPath” argument to the location of the desired version of “nuget.exe” to use.
With any luck, each future TFS update will ship with the most recent version of NuGet for those builds that can wait.
This year I have been working with a code base that exhibits Visual Studio projects with three characteristics:
- The project references a NuGet package.
- The project is included in more than one Visual Studio solution.
- The solution files are located in different folders.
I’m not sure how common this scenario is. A few different threads on the NuGet CodePlex site suggests at least some other people are wrestling with it. Personally I endeavour to structure a code base to avoid sharing projects between solutions but for old, high-coupled code this can be difficult to achieve.
The problem with this scenario is with the relative paths used to resolve the assemblies within the referenced NuGet package when building the project in clean or constrained working folders – such as on a build agent or when someone first clones a repository. When a NuGet package is installed or updated in a project, the path to the package assemblies are specified relative to the /packages/ folder of the currently open solution. However, when another solution including the same project is built, the assembly won’t be resolved either because the first solution’s /packages/ folder is not present in the workspace and the NuGet Package Restore workflow has put the assemblies in the second solution’s /packages/ folder.
The existing attempts to solve this issue, and the same way I approached the problem originally, tend to be focused on writing reference paths relative to an MSBuild property like $(SolutionDir) or $(PackageDir) which then allows the path to be resolved correctly at build time. If I understand correctly, this approach has been rejected from becoming part of the official NuGet application because it doesn’t handle the scenario where a project is being built directly, not being built as part of a solution – something I also avoid generally.
Last week I had an idea to tackle the problem dynamically at build time instead of when the reference path is written to the project file. My solution is to introduce (yet another) NuGet package to the affected projects as a development-only dependency. I call this the NugetReferenceHintPathRewrite package. This package adds an MSBuild targets file to the project which executes just before the standard ResolveAssemblyReferences MSBuild target. When it executes it looks for references that specify a /packages/ folder as part of their path and then replaces the part of the path up to and including the /packages/ folder with the a new path to the currently building solution’s packages folder. This rewrite is done to the MSBuild Items in-process and does not modify the project file on disk.
The main benefit of this dynamic build-time approach is that I don’t have to worry about new packages being installed or packages being updated (ie re-installed) and the paths in the project file being set to the “wrong” path because someone else forgot to fix it before committing.
You can find the NuGetReferenceHintPathRewrite package on NuGet.org.
In general, an automated build server should be usable to build multiple projects and multiple revisions of a single project. An automated build server should also be disposable, it should not contain any special configuration that isn’t already tracked in source control, and provisioning a new build server should not require hunting for the installers for specific versions of dependencies.
By default however, the Windows Azure SDK for .NET doesn’t work this way. To build an Azure project from a build server you need the full SDK installed and suddenly this means the build server is tied to only building projects targeting a specific version of the Azure SDK. If several projects share a single build server then all projects need to be upgraded to the new SDK when one project is upgraded and performing builds on maintenance branches becomes difficult if not impossible.
Simply copying some Azure SDK files into a source-controlled “Dependencies” folder won’t help much either because the (v1.6) build scripts make some assumptions:
- The Azure tools are located at a fixed path under the Program Files folder
- The Azure SDK is located at a path referred to by a HKEY_LOCAL_MACHINE Registry key
- A common .targets file has been installed in the MSBuild extensions folder so every project system-wide references it automatically
Finding and fixing these assumptions is required so that a source-controlled copy of the SDK will work but these assumptions can change (and have changed) in each new version of the SDK so it is important not to be tempted to edit the original files but instead utilise the various hook points in MSBuild to override these assumptions.
Ideally the Azure SDK would just be a Nuget package but until the product team can be convinced to do this, I have published some scripts which allow you to build your own Nuget package of the Azure SDK with all the afore mentioned assumptions fixed. My scripts require a machine with the SDK already installed because I doubt I have the rights to redistribute original files.
The scripts are available in a Mercurial repository hosted on Bitbucket. After cloning or downloading a copy of the repository, just run MSBuild in the root and it will gather the necessary SDK files from your system and produce a Nuget package in the “output” subfolder. From there you might ideally install the package in your Azure project (the *.ccproj file) using this command from the Package Manager Console:
Install-Package -Id Microsoft.WindowsAzure -Source [path to the "output" subfolder]
Unfortunately, at the time of writing this Nuget (also v1.6) doesn’t support Azure projects so slightly more work is required. First, install the package into the target solution’s “packages” folder using the “nuget.exe” command-line interface:
nuget.exe install Microsoft.WindowsAzure -Source [path to the "output" subfolder] -OutputDirectory [path to the solution "packages" folder]
The open the Azure project’s “.ccproj” file for editing in a text editor and add the following line immediately after the opening <Project … > element:
<Import Project="..\packages\Microsoft.WindowsAzure.1.6\tools\Microsoft.WindowsAzure.Nuget.targets" />
Note that the relative path to the “.Nuget.targets” file may be slightly different depending upon project and solution directory structures. You can vote to get Azure support added to Nuget here.