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


   5: foo :bar do

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

   7: end


   9: task :default => [:bar] 

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


   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


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


  16: task :default => [:copysomefiles]

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/"


   3: task :default => :copyoutput

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. :)

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs - the amazingly awesome podcast audio hosting service that everyone should be using, and where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Albacore, Rake. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Fernando Z

    Thanks for sharing… keep the posts on rake coming… it explains a lot of what is going in Albacore

  • Troy Goode

    Hey Derek – which version of Ruby are you running? Your example code doesn’t appear to work in either Ruby 1.9.1 or IronRuby 1.0 (.Net 4.0).

  • Troy Goode

    More specifically, the “copy” task example is what doesn’t work. The example that takes a &block works fine.

    Also a colleague tested this on 1.8.7 and couldn’t get it to work either…

  • Jim Deville

    The copy example should be calling define_task(*args, &body). The & turns body into a block instead of a proc. I would also suggest using lambda instead of proc to avoid local jump errors.

    This is nice for quick tasks, but I still like having classes for more advanced tasks

  • derick.bailey

    @Troy – D’oh! sorry about that. that’s what i get for using code examples that i didn’t actually run. :(

    i keep forgetting about the rules on when / where you can do *args… i corrected the code to actually work now. i’m sure there is a cleaner way to get it to work, but this got the job done. :)

  • Troy Goode

    No problem Derick (sorry for the misspelling above, btw). Also, the very friendly folks on the ironruby-core mailing list suggested the use of lambda instead of proc, for reasons explained here:

  • derick.bailey

    i’ve never really understood when and where to use proc vs. lamba, even though i have seen those differences pointed out to me multiple times. i’ll have to read further and see if i can actually make sense out of it. thanks for the link!