blog.absurd:li - press play on tape
January 14th 2010
Tagged ruby, rake, capistrano, vlad

Using rake as a component

Rake is a useful beast. As one of the most mature ruby projects it makes my life better by allowing me to forget everything I ever knew about make.

Embedded Rake

I would like to show here how you can use rake as a library, not as a tool. The following piece of code shows how you can declare tasks in your own Ruby classes:


  require 'rake'
  class RocketScience
    include Rake::TaskManager
    def task(*args, &block)
      define_task(Rake::Task, *args, &block)
    end

    def init
      task :build_rocket do
        p :build_rocket # rocket building here
      end

      task :launch_rocket => :build_rocket do
        p :launch_rocket # launching here - only works if you build it first
      end
      self
    end
  end
  

The Rake::TaskManager mixin retrofits task management to any of your classes. It defines a few methods that allow creation, invocation and management of your own tasks. The example uses #define_task to add tasks to the current instance of RocketScience. You can find the default definitions for #task, #file and the whole TaskManager mixin in rake.rb in rake’s source code at the top level.

Now the above example doesn’t do much yet. To show that rake is really useful for your code, here’s how to print something:


  class RocketScience   # reopen the class
    def run
      self[:launch_rocket].invoke
    end

    def options
      options = OpenStruct.new
      options.trace = false
      options.dryrun = false
      options
    end
  end
  
  RocketScience.new.init.run

We need to provide rake with an options method that defines some of the runtime options you can set in the command line tool as well. The above example will print

  :build_rocket
  :launch_rocket

A whole lot, isn’t it?

Now you will say that embedding rake like this is a complicated way of doing the following:


  require 'rake'
  
  task :build_rocket do
    p :build_rocket # rocket building here
  end

  task :launch_rocket => :build_rocket do
    p :launch_rocket # launching here - only works if you build it first
  end
  
  Rake::Task[:launch_rocket].invoke

You would be right. The only thing the first example buys you is that your rake namespace is not a singleton anymore, you can actually have multiple tasks with the same name in different instances of RocketScience.

Remote Rake

One last rake trick before you can go out to play: I recently discovered an heavily underdocumented feature in vlad the deployer: remote rake tasks from within rake. Perhaps this is underdocumented because it is so stupidly obvious to everyone (but me!), but I think it is definitly worth the mention:


  require 'rake'
  require 'rake_remote_task'

  role :server, 'server1'
  role :server, 'server2'

  remote_task :ls, :roles => :server do 
    run 'ls'
  end

  Rake::Task[:ls].invoke

The above example shows how you can execute ‘ls’ on multiple remote servers. You first define your servers as being part of a role, using either the role keyword (as above) or the host keyword:


  host 'multiple-purpose-host', :db, :application

Then you write remote rake tasks against these roles as shown. Note that the example uses the ‘rake-on-a-stick’ technique I’ve shown above and could well be integrated with everything said above. Of course, you could make it a vanilla Rakefile as well.

Since vlad is capistrano built on rake, it should be clear that vlad contains this feature. But I think this is useful for all uses of rake, not just deployments. Using this you can write Rakefiles that execute on multiple servers AND locally at once. Serious coolness. Goodbye Capistrano, welcome to our new russian overlords.