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.