How to: NuGet Package Restore when sharing projects between solutions

Although it is a situation I try to avoid, it is not uncommon to share project files between solutions. If you are using NuGet package restore on these solutions, and one or more of these shared projects is consuming NuGet packages, you'll likely hit issues. This StackOverflow question is an illustration of exactly this problem.

Symptoms

Developer A:

"I can't compile Solution2 anymore after pulling the latest version!"

Followed by:

"That's strange… it works on my machine." – Developer B

Instantly followed by handing out a Pink Sombrero™ to Developer B: (yes, you broke the build! Hopefully…)

Many teams have lost time debugging this issue, and although I already have quite an exhaustive checklist, this particular issue is not covered as I never really had to deal with this one. I prefer to consume the NuGet package instead of sharing the project…

Example

Behold the following sample folder structure containing 2 solutions. Solution2.sln also references the existing Library1.csproj file (note that it is not inside the solution's root directory):

Both solutions have package restore enabled. Solution1.sln has no issues at all.

Solution2.sln cannot build, because the referenced Library1.csproj project cannot reference its packages. This is tricky: package restore did work! But by convention, the packages consumed by Library1.csproj got installed into the solution's Packages folder, which in this case is Solution2\Packages. Package restore (behind the scenes nuget.exe install – horrible command name mismatch) does not install any packages: it downloads and extracts them. None of the package scripts are run, no content is injected, no references added.

If you locally have both solutions and you restored the packages consumed by Solution1.sln, then you'll be in the situation of Developer B where it works on your machine. However, Developer A who only opened Solution2 and never built Solution1 will get build failures (as will the build server).

Root Cause

  1. NuGet Package Restore is MSBuild-based at the moment. It is being redesigned so I expect great improvements in the foreseeable future.
  2. You're sharing projects and code between solutions. Why not package them instead? Wait, here comes the "debugging experience complex"… It is so convenient to just reference the damn code. I agree within the scope of a single solution.

Sharing between multiple solutions? Here's the deal: set up continuous (package) delivery and use symbols packages (or a symbols server as the one built-in to TFS). You can use SymbolSource.org or set up your own SymbolSource Server. If you need more in order to debug your solution, then frankly you might want to rethink what you're sharing here… This smells like tight coupling and a missed opportunity to share a package.

Patching up the open wound

Although I'd strongly recommend taking a closer look at these projects you're sharing, a short term solution to your problem is also MSBuild-based. You'll need to tweak the package restore command per project. The easiest way to do this is by introducing a new MSBuild property and only deviate from the default conventions when absolutely required. The projects that will require a deviation are those that are being shared obviously.

If you disregard the concerns I raised earlier in this post, then here's what I would do to fix this mess (quick-n-dirty style):

  • In Solution1\.nuget\nuget.targets, add the following MSBuild property before the <RestoreCommand> element:

<PackageRestoreDir Condition="$(PackageRestoreDir) == ''">$(SolutionDir)\Packages</PackageRestoreDir>

  • Within the same file, update the <RestoreCommand> by appending the following:

<RestoreCommand>$(NuGetCommand) install … -o "$(PackageRestoreDir) "</RestoreCommand>

  • In the Library1.csproj file, add the following MSBuild property (mind the order of precedence with <SolutionDir>):

<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<PackageRestoreDir>$(SolutionDir)\..\Libraries\Packages</PackageRestoreDir>

Repeat the last step for each existing shared project you referenced in your second solution. The <PackageRestoreDir> should match the project reference and the default package restore directory of the main solution of that project.

You can download a sample solution here.

Happy packaging!

Sharing is caring

Posted by Xavier Decoster on
Last revised: 23 Jul, 2013 11:26 PM
blog comments powered by Disqus