Dependency Management in .Net: Offline Dependencies with NuGet Command Line Tool


Today I stumbled upon Scott Haselman’s post: How to access NuGet when NuGet.org is down (or you’re on a plane) in which Scott discusses how he recovered from an issue with the nuget.org site being down during his demo at the Dallas Day of .Net.   As it turns out, while NuGet stores packages it downloads in a local Cache folder within your AppData folder, it doesn’t actually use this cache by default.  Scott was able to remedy the situation by adding his local cache as a source through the the Visual Studio Package Manager plugin.

Last year, I wrote about my philosophy for dependency management and  how I use NuGet to facilitate dependency management without using the Visual Studio plugin wherein I discuss using the NuGet.exe command line tool to manage .Net dependencies as part of my rake build.  After reading Scott’s post, I got to wondering whether the NuGet.exe command line tool also had the same caching issue and after a bit of testing I discovered that it does.  Since I, with the help of a former colleague, Josh Bush, have evolved the solution I wrote about previously a bit, I thought I’d provide an update to my approach which includes the caching fix.

As discussed in my previous article, I maintain a packages.rb file which serves as a central manifest of all the dependencies used project wide.  Here’s one from a recent project:

packages = [
[ "Machine.Specifications", "0.5.3.0" ],
[ "ExpectedObjects", "1.0.0.2" ],
[ "Moq", "4.0.10827" ],
[ "RabbitMQ.Client", "2.7.1" ],
[ "log4net", "1.2.11" ]
]

configatron.packages = packages

 

This is sourced by a rakefile which which is used by a task which installs any packages not already installed.

The basic template I use for my rakefile is as follows:

require 'rubygems'
require 'configatron'
 
...

NUGET_CACHE= File.join(ENV['LOCALAPPDATA'], '/NuGet/Cache/') 
FEEDS = ["http://[corporate NuGet Server]:8000", "https://go.microsoft.com/fwlink/?LinkID=206669" ]
require './packages.rb'
 
task :default => ["build:all"]
 
namespace :build do
 
  task :all => [:clean, :dependencies, :compile, :specs, :package] 

  ...

  task :dependencies do
    feeds = FEEDS.map {|x|"-Source " + x }.join(' ')
    configatron.packages.each do | name,version |
      feeds = "-Source #{NUGET_CACHE} " + feeds unless !version
        packageExists = File.directory?("#{LIB_PATH}/#{name}")
        versionInfo="#{LIB_PATH}/#{name}/version.info"
        currentVersion=IO.read(versionInfo) if File.exists?(versionInfo)
        if(!packageExists or !version or !versionInfo or currentVersion != version) then
          versionArg = "-Version #{version}" unless !version
          sh "nuget Install #{name} #{versionArg} -o #{LIB_PATH} #{feeds} -ExcludeVersion" do | ok, results |
            File.open(versionInfo, 'w') {|f| f.write(version) } unless !ok
        end
      end
    end
  end
end

 

This version defines a NUGET_CACHE variable which points to the local cache.  In the dependencies task, I join all the feeds into a list of Sources for NuGet to check.  I leave out the NUGET_CACHE until I know whether or not a particular package specifies a version number. Otherwise, NuGet would simply check for the latest version which exists within the local cache.

To avoid having to change Visual Studio project references every time I update to a later version of a dependency, I use the –ExcludeVersion option.  This means I can’t rely upon the folder name to determine whether the latest version is already installed, so I’ve introduced a version.info file.  I imagine this is quite a bit faster than allowing NuGet to determine whether the latest version is installed, but I actually do this for a different reason.  If you tell NuGet to install a package into a folder without including the version number as part of the folder and you already have the specified version, it uninstalls and reinstalls the package.  Without checking the presence of the correct version beforehand, NuGet would simply reinstall everything every time.

Granted, this rake task is far nastier than it needs to be.  It should really only have to be this:

task :dependencies do
    nuget.exe install depedencyManifest.txt –o lib
  end

 

Where the dependencyManifest file might look a little more like this:

Machine.Specifications 0.5.3.0
ExpectedObjects 1.0.0.2
Moq 4.0.10827
RabbitMQ.Client 2.7.1
log4net 1.2.11

 

Nevertheless, I’ve been able to coerce the tool into doing what I want for the most part and it all works swimmingly once you get it set up.

RabbitMQ for Windows: Building Your First Application