{ |one, step, back| } 1 of 1 article WikiSyndicate: full/short

Custom Rake Applications   21 Apr 07
[ print link all ]

It’s odd how ideas come to you. I was working on a script unrelated to Rake, when a different way of using Rake occurred to me.

Start / Stop Script

Today a coworker passed on a shell script for staring and stopping a server on a local box. The script was pretty typical. You would type “server start” and “server stop” to bring the aforementioned server up and down. It looked something like this:

#!/bin/sh

SERVER_DIR=/path/to/server/directory

case $1 in
    start)
        pushd $SERVER_DIR
        ./startServer.sh >out.log &
        popd
    ;;

    debug)
        pushd $SERVER_DIR_
        ./startServerDEBUG.sh >out.log &
        popd
    ;;

    stop)
        pushd $SERVER_DIR
        sh stopServer.sh
        popd
    ;;

  *) echo "invalid" ;;
esac

There were a few more options, but you get the drift. Anyways, I wanted to to customize the script a bit and realized that my shell scripting abilities were a bit rusty. So I translated the whole thing to Ruby …

The Ruby Version

The translation was pretty straight forward. The guts of the script looked like this:

ARGV.each do |arg|
  case arg
  when 'start'
    Dir.chdir(WLS) do
          system "./startWebLogic.sh >out.log &" 
    end

  when 'debug'
    Dir.chdir(WLS) do
          system "./startWebLogicDEBUG.sh >out.log &" 
    end

# [... code elided ...]

and so on.

After about the second or third “when” clause in the “case” statement, I realized that I was essentially writing tasks. And we have a perfectly good Ruby tool for managing task based software: Rake!

So why wasn’t I writing this as a Rake script? One reason is that Rake depends on the presence of a Rakefile in the current (or parent) directories. I needed to be able to run any of my server commands from anywhere in the file system, not just from the directory containing the Rakefile.

Fair enough. But there’s nothing preventing me from writing a ruby script that includes the Rake library. We saw something similar in the FindInCode script earlier. The only additional thing we need to do is include the task definitions directly in the script, and then explicit invoke the tasks we need.

The Initial Rake Version

So I abandoned my first cut at a Ruby version and went with this:

#!/usr/bin/env ruby

require 'rake'

SERVER_DIR = /path/to/server/directory

task :start do
  Dir.chdir(SERVER_DIR) do
    sh "./startServer.sh >out.log &" 
  end
end

task :stop do
  Dir.chdir(SERVER_DIR) do
    sh "./stopServer.sh >out.log &" 
  end
end

# [... more task defintions go here ...]

ARGV.each do |arg| Rake::Task[arg].invoke end

Now I have a version that can handle all the task dependency stuff that Rakefiles do, but can be run from anywhere in the file system. Assuming I named the script “server”, I can say:

server start

and the right code gets run. I can specify dependencies between the tasks in the script (e.g. “task :bounce => [:stop, :start]“), and do all the other great stuff that building on Rake allows.

The Improved Rake Version

There are a couple of downsides to the above script. Even though I can invoke the server script as if it were a rake-like command, it doesn’t do everything that rake does. For example, there is no way to get a list of all the defined tasks (.e.g rake -T). In fact, no rake command line options are support in the above code. Neither are the environment variable parameters (e.g. TEST=test_file_name.rb). Furthermore, the error messages are not trapped and handled the way that Rake does it.

Now, all of the above could be manually added to the server script, but a better way is to slightly refactor Rake to better support customer rake applications. The init and top_level public methods were added to the Rake Application class, so now we can write the above script as:

#!/usr/bin/env ruby

gem 'rake', '>= 0.7.3'
require 'rake'

Rake.application.init('server')

SERVER_DIR = /path/to/server/directory

task :start do
  Dir.chdir(SERVER_DIR) do
    sh "./startServer.sh >out.log &" 
  end
end

task :stop do
  Dir.chdir(SERVER_DIR) do
    sh "./stopServer.sh >out.log &" 
  end
end

# [... more task defintions go here ...]

Rake.application.top_level

All we did was add a call to Rake.application.init to initialize the command line parameter information in the Rake application. You can specify an optional application name to init to allow the Rake software to correctly report the application name when handling a -T command line option.

The second thing we did was change the explicit loop through all the parameters at the end of the script into a single call to top_level. The top_level method not only handles the invocation of the top level tasks, but also is smart enough about the Rake command line options to properly handle them.

One final note about the “gem” command line near the top. The init and top_level methods were introduced in version 0.7.3 of Rake. By using the gem command we can ensure that are using a compatible version of the Rake software.

Summary

When should you use a normal Rakefile, and when should you write a custom Rake application? A Rakefile works best for per-project type of commands, such as those used to build and test your individual projects. You are (almost) always somewhere in the project directory tree when invoking Rake, and so it just does the right thing.

Custom Rake applications work well when the command are not per-project, and you need to run them anywhere in the file system. A custom application gives you the freedom to run the command anywhere and not need a Rakefile first.

Here’s another quick example where I found a custom Rake application to be useful. Lately I have been doing a lot of small Ruby scripts. I have setup TextMate to autorun the tests via rake, but that means before I can test any of my mini-ruby program, I need to take the time to write a Rakefile to run my tests. Normally I would use rake to automate such a repetitive task, but rake isn’t effective until I have a Rakefile, so we’re in a kind of Catch-22 situation. But with a custom Rake application, I can say something like “proj rakefile” ... and have proj do all the heavy lifting for me without writing an explicit Rakefile first.

Ain’t automation grand?

 

Formatted: 04-Jul-08 14:33
Feedback: jim@weirichhouse.org