Manage Your Dependencies with Rake and NuGet

On December 14, 2011, in samples, by Josh Bush

Last week I blogged about how to perform some basic build tasks in your .NET project with Rake and Albacore. There was one bit about managing dependencies I left off though because I thought it warranted its own post. For the projects I’ve been working on lately, we’ve managed to keep our source repository light and nimble by not checking in binaries for all of the dependencies.

NuGet 1.6 came out this week and this functionality is baked in. You can check out the NuGet way in the documentation. The bummer of this is that you have to enable “Package Restore” for each project in your solution. You also now have multiple packages.config to maintain per project. Yes, you can manage it all though the GUI or the package manager console for your projects, but I want it all in one place. I also like not having to do anything on a per project basis other than standard references.

After several iterations on what Derek Greer started, I’ve ended up with the solution below. Dependencies are declared in the same packages.config format that nuget uses, so you can take something you’ve already created and centralize it. We have one build step to refresh our dependencies and it looks like this:

require 'rexml/document'
TOOLS_PATH = File.expand_path("tools")
LIB_PATH = File.expand_path("lib")

FEEDS = [
	#Your internal repo can go here
	"http://go.microsoft.com/fwlink/?LinkID=206669"
]

task :dependencies do
	file = File.new("packages.config")
	doc = REXML::Document.new(file)
	doc.elements.each("packages/package") do |elm|
		package=elm.attributes["id"]
		version=elm.attributes["version"]

		packagePath="#{LIB_PATH}/#{package}"
		versionInfo="#{packagePath}/version.info"
		currentVersion=IO.read(versionInfo) if File.exists?(versionInfo)
		packageExists = File.directory?(packagePath)
		
		if(!(version or packageExists) or currentVersion!= version) then
			feedsArg = FEEDS.map{ |x| "-Source " + x }.join (' ')
			versionArg = "-Version #{version}" if version
			sh "\"#{TOOLS_PATH}/nuget/nuget.exe\" Install #{package} #{versionArg} -o \"#{LIB_PATH}\" #{feedsArg} -ExcludeVersion" do |ok,results|
				File.open(versionInfo,'w'){|f| f.write(version)} if ok
			end
		end
	end
end

There’s a little bit of code there, but we’re getting some good benefits from this one task.

Control over where our dependencies go.
I’m not a big fan of the packages/ folder that nuget uses by default. You may be able to change this in the GUI somewhere, but I haven’t seen it yet. Yes, I’m aware that this is trivial, but I got used to storing my dependencies in lib/ and I’m okay with keeping that. :) Every team has their own conventions they like to follow and it’s nice to not have to change those just because you want to adopt a new tool.

No weird version number suffixes on our folders.
The default convention nuget uses is to store packages under a folder named {name}.{version}. That’s cool until you need to update your dependency to a new version. When you do, you (or your tooling) will have to update the reference paths in all of your *.csproj files to accomodate the new path. I would prefer to store it in a folder with just the name of the package. Keep in mind, this removes the ability to run multiple versions of the same library for different projects within a solution. This hasn’t come up on my projects yet though.

No need to keep tabs on what dependencies our dependency has.
I’m hoping this issue will change one day. As it stands right now (NuGet 1.6), if I have a single entry in my packages.config like so: <package id="NHibernate" version="3.2.0.4000"/> then calling $> nuget.exe install packages.config will not get NHibernate’s dependency ‘Iesi.Collections’. It turns out though, calling nuget like this: $> nuget.exe install NHibernate -Version 3.2.0.4000 will get that dependency for us, so that’s exactly how our rake script does it.

I feel like the ruby syntax reads fairly easy even if you aren’t familiar with the language. Still though, I think it would be beneficial to add a little commentary.

Line 5 is where we define our source(s) for nuget packages. At work we’re using a file share to cache packages and then falling back to the default source when needed.

Lines 11 and 12 are where we load up the packages.config xml file using the XML parser that ships with a default Ruby install. From my reading, there are better gems to accomplish this faster, but this is a really tiny XML file we’re dealing with.

Line 13 selects each package node and iterates over it. The next two lines just pick out the id and version attributes into variables. On lines 19 and 20 we read in the version file if it exists and also check if the package directory exists. We use all of that on line 22 to see if we need to restore this package.

If we’re all systems go for NuGet launch, then line 23 turns the array of feeds from line 5 into ‘-Source’ arguments for nuget.exe. Line 24 creates a version argument for nuget.exe if we have one. Finally, line 25 shells out to nuget.exe and assembles all of the command line arguments it needs to do the job. When we get our package, we poke(line 26) a version.info file to track the version we’ve downloaded for future runs.

Wrapping Up
That’s it. I almost didn’t write this post since NuGet 1.6 supports this scenario out of the box. I still feel like it’s worthwhile to have this as part of our rakefile if for no other reason than to manage my packages from a single place. What do you think? Please let me know if you see anywhere I could improve the process.

Tagged with:  
  • Anonymous

    great post, always awesome to see how people use tools from other languages to get things done.

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1003

  • http://mutedsolutions.com Derick Bailey

    that chunk of code is begging to be a custom Albacore task. *nudge nudge* :D