Rake vs NAnt - a real-life example

Is it a good idea to replace some or all of your [N]Ant build with Rake? I finally decided to find it out, by actually doing it (*)

The story begins with a fairly normal, run of the mill, Nant build for a .NET project, building a fairly normal, run of the mill, enterprise application. Broadly, the build consists of three parts: database, compile/test and packaging for deployment. It took me several evenings (~20 hours total) to reimplement it with Rake and a couple of weeks to get pretty much everybody in the project to switch over to it.

My conclusion is "Rake rocks", and here is why:

* Our Rake build is a lot smarter about build product dependencies. For example, if I say 'rake test' and nothing has changed within the ./sql directory since the last build, the step for recreating the database is skipped. The same can be done with NAnt, however it would make it a lot more complex than I would personally be comfortable with (been there with a 3 kLOC Ant build once). I think that this feature alone saves about an hour of unproductive time per day across the project. Which makes it a worthwhile thing right away, and is the main reason why build users voluntarily use Rake in their development environments instead of NAnt (I keep both builds alive).

* Even when it has to do everything from scratch, our Rake build is slightly faster than our NAnt build, despite doing more than NAnt (such as checking tkmestamps etc). I half-expected it to be somewhat slower, and was afraid to find it much slower. It turns out that I was wrong.

* Custom tasks are very easy to write. E.g., the source for my csc task (compiles a FileList of C# sources) is mere 40 lines. Because they are so easy to write, it is affordable to have all sorts of small nice touches in the build, such as this method:

# Decides where should the database files be placed
def database_path
  return @database_path if @database_path
  path = PROPS['database_path']
  if path == '%default'
    # try to place database on the RAMDrive
    if File.exists?('B:/')
      path = 'B:\Databases'
    else
      path = 'C:\Databases'
    end
  end
   mkdir_p path unless File.exists? path
  return @database_path = path
end

Doing the same thing in NAnt build is, again, possible, but I would shy away from the extra complexity in this case.

* Custom tasks for Rake are written in the same language as the build file itself, require no compilation, and can be included in the build as easily as "require 'rake_helpers'".

* Ruby syntax is conveniently non-invasive, so it was trivial to give the aforementioned csc task an interface like this:
  csc :out => TEST_DLL,
      :sources => TEST_SOURCES,
      :resources => 'src/test/**/EmbeddedResources/**/*',
      :references => [APP_DLL, WEB_CONTROLS_DLL, 'tools/nunit/nunit.framework.dll', 'tools/nmock/NMock2.dll', 'lib/log4net/log4net.dll']

* I can use breakpoint.rb anywhere in the build file, custom tasks file or even Rake sources, therefore it tends to be much easier to troubleshoot the build problems.

* Since extraction of custom tasks and helper methods into a separate file is so easy, the build file itself is considerably shorter than Nant's and mostly deals with target dependencies. Not to mention the absence of closing XML tags and other such syntactic noise. The end result is something that conveys the big picture (what is built from what) much better.

* Finally, the icing on the cake: sh 'tools\nunit.exe ...' prints out a new dot after every test, rather than writing six lines of dots at once in the end, as Nant does... :slight_smile:

Morale: if you cannot have Ruby in production code, you may still find it useful for project automation and other non-production uses. Speaking of which, one day I may write about using ActiveRecord as a way to talk to the database in Watir tests. I am actually doing it, but not enough to be sure if it really was a good idea yet.

Best regards,
Alexey Verkhovsky

Footnotes:
* My _real_ purpose was adding another .rb file to our CVS, and eventually world domination (of course).