How To Build Custom Rake Tasks; The Right Way


A long time ago (last year) I wrote a few blog posts that talked about how I was going about creating some custom rake tasks. This turned out to be the beginning of my albacore project and I’ve been working on that project for nearly a year now. It’s been a lot of fun and I’ve learned a lot in the process of creating and maintaining it. One of the things I learned recently is that the way I am currently handling task creation – the way that I talked about in those posts – is not the best way to do it. There is, in fact, a much easier way to create a custom rake task: the Rake::Task.define_task method.

 

A Simple Rake Task

The following code creates a rake task called “foo”, then defines a task named :bar and sets it up to run as the default task.

   1: def foo(*args, &block)

   2:   Rake::Task.define_task(*args, &block)

   3: end

   4:  

   5: foo :bar do

   6:   puts "this is coming from the task called :bar, which is a foo task"

   7: end

   8:  

   9: task :default => [:bar] 

</div> </div>

Lines 1 through 3 show how simple it actually is to define a rake task. Obviously I haven’t done anything useful with this task, but it illustrates the point.  The call to Rake::Task.define_task on line 2 is where the real magic happens. Well, ok… there’s nothing magical about this… at all. I’ve defined a method on line 1 and I’ve called that method on line 5. the name :bar is passed into the *args parameter and the do … end block is passed into the &block parameter. Both of those are then passed into the define_task method. Running rake on this rakefile produces the simple message that you see in the puts statements on line 6.

 

A Useful Rake Task

At this point, any valid ruby code you want can be used to define a rake task. You only need to provide a minimum of a name to the define_task method and you’re good to go. The code block to define the method body is optional and can be provided by any code that you want. You can also take different parameters than the define_task needs, as long as you provide what define_task needs when it needs it.

For example, you could easily create a very simple task to copy files, like this:

   1: def copy(name, src, dest, *args)

   2:   args || args = []

   3:   args.insert 0, name

   4:   

   5:   body = proc {

   6:     FileList[src].each do |f|

   7:       puts "Copying #{f} To #{dest}"

   8:       FileUtils.cp f, dest

   9:     end

  10:   }

  11:   Rake::Task.define_task(*args, &body)

  12: end

  13:  

  14: copy :copysomefiles, "*.rb", "/temp/"

  15:  

  16: task :default => [:copysomefiles]

</div> </div>

Line 11 will copy all of the .rb files from the current folder into the temp folder on the root of your drive… more importantly, though, it is a first class citizen in the rake task list, giving you all the capabilities of task depdenencies, parameterized tasks, etc.

   1: copy :copyoutput => [:initialize, :build, :test], "**/*.rb", "/myproject/"

   2:  

   3: task :default => :copyoutput

</div> </div>

This simple snippet sets up a :copyoutput task with 3 dependencies and then sets up the default task to run it.

 

Cleaning Up

I’m glad I found this… it is going to greatly simplify the things I have been doing to create rake tasks – especially when it comes to Albacore. The code that I have in Albacore’s create_task method is pretty ugly, involves inheritance and a few other hacks that I’m not so fond of. The worst of it, though, is that it’s very difficult to change because it’s hard to see what’s really happening. Simplifying things down into small methods that call Rake::Task.define_task should greatly improve the infrastructure of Albacore, though.

I hope this information is useful for others, as well. If even one person has that “aha!” moment and realizes how easy it is to create your own rake tasks, than this blog post has done it’s job. 🙂

Why Is Git Trying To Delete My Project Folder?!