Tuesday, October 9, 2012

Bending NuGet to Your Will by Manipulating the AppData Cache

NuGet decides which versions of dependencies to pull in based a combination of the way dependencies are specified in the dependent packages and the packages that are available on your feeds.  NuGet drills down pulling all of a components direct dependencies and its indirect dependencies based on the nspec specifications of each pulled in component. This lets you pull in some  OSS component and then its dependencies down to bottom level which might be something like a Microsoft component. NuGet compares the versions specifications when resolving those to NuGet packages on a feed. Here are some example version specifications for the Microsoft MVC

  • "4.0.20505.0"  MVC 4.0 RC or later
  • "[4.0.20505.0]" MVC 4.0 RC only
  • "[4.0.20505.0, 4.1] MVC 4.0 RC through any version of 4.1
  • "[4.0.20505.0, 4.0.20710.0) MVC 4.0 RC and all up to but not including MVC 4 RTM
You can run into problem when new versions of dependencies appear on feeds and dependencies are inccorectly bounded.

Example Problem

Lets say you have library based on MVC 4.0 RC, 4.0.20505.0, that hasn't isn't yet ready to update to MVC 4.0 RTM, 4.0.20710,0.  Now Microsoft updates MVC on the official NuGet feed from 4.0.20505.0 to 4.0.20710.0 making it available to you.  You then update a consuming application pull in this version of your library.

If the library specified its' NuGet dependency as "4.0.20505.0" then NuGet will automatically delete 4.0.20505.0 from your packages folder and add version 4.0.20710.  You now have a component that was built against RC (4.0.20505) but which is sitting in an app with MVC RTM (4.0.20710.) One option is to fix any compile errors resulting from inclusion of 4.0.20710.0 In large projects that is complicated because you might have a multi-solution rol lup to get everything updated and builds rolled up. "Web Component C" in the diagram would require 3 consuming updates to carry this 3rd party version upgrade to the top.



The right way to fix this is to fix this problem is to change the libraries description of it's MVC dependency from "[4.0.20505.0]" which says ignore any other versions on the feed.  This works well also but in a big project may mean that you have to update dozens of nuspec definitions to include the square brackets. This can get complicated in a larger project. I saw one 50 solution project where it took almost a week to roll this type of upgrade from bottom to top.  

Microsoft themselves have some trouble with this.  The MVC/WebApi 4.0.20505.0 RC NuGet internal dependencies are specified as "4.0.20505" for things like Razor, etc.  This means you can potentially end up in a mixed RC / RTM environment where the later dependencies were all pulled in as RTM even though the previous dependencies were RC.

Hacking NuGet to "Use the Cache, Luke"

Another option is to hack NuGet to provide the versions you want.

NuGet caches all of its downloaded dependencies in %HOME%\AppData\NuGet\cache.  This includes any packages that have been updated by the NuGet Package Manager. We make use of this and the fact that this cache looks like a "local" feed.  
  1. Check the cache to see if your packages are already ther.  If not , create a temporary project, and use the package manager to create dependencies that are automatically downloaded to your NuGet cache directory.  Sometimes you need seeding and sometimes you don't.
  2. Turn off your normal feeds including NuGet.org and any local/hosted feeds.. 
  3. Create a new NuGet feed pointing a the AppData\NuGet\cache.  
  4. Purge any conflicting NuGet packages from the cache directory.
  5. Run the NuGet package manager. It will assume that your cache contains the correct versions and it will install from the cache instead of from the actual feeds.

NuGet Package Restore

I'm not sure how this works with Package Restore.  You may be able to use a similar technique

No comments:

Post a Comment