
The other day, as I was tracking down a bug in a large J2EE application, I had an idea for a new ant task. Hooray automation! The excitement of the new idea quickly vanished as the reality of dealing with ant set in.
The Problem:Programatically changing text in a file. Oh noes! I will have to create some templates, copy files around, replace words and stuff. An hour or two to figure out what I could do with rake in 5 minutes. But adding a Rakefile to the project for just one task is lame! Dutifully, I popped open our 2k line build.xml and nearly gagged. Rake it is then. But how do I keep our build consistent? One way would be Re-implement our entire ant build in rake. Crazy talk! I needed a compromise. Thinking about it for a second I wondered, "What if rake could seamlessly use all my ant tasks?"
The Solution:Making rake ant aware! Now i'll be able to call all my ant tasks as if they were rake tasks.
rake antTask
I can even mix and match ant tasks with rake tasks.
rake antTask rake_task antTask2
Hoo boy, this is gonna be great! But how do we do it? I need something like method missing for rake tasks. Determined to figure this out I checked out the
rake source code. Nothing like method missing there, but it turns out just a few addidtional lines of code gives rake ant awareness. Here is what I ended up with in the top of my Rakefile.
#Redefine the way rake works so we can have it check our build.xml
module Rake
# open up the TaskManager to make it try ant before pooping out
module TaskManager
# Find a matching task for +task_name+.
def [](task_name, scopes=nil)
task_name = task_name.to_s
self.lookup(task_name, scopes) or
enhance_with_matching_rule(task_name) or
synthesize_file_task(task_name) or
call_ant_task(task_name) or
fail "Cannot find task called '#{task_name}'"
end
def call_ant_task(task)
return nil unless system("ant #{task}")
#return a do nothing task because ant is doing the work here.
Rake::Task.new(self, self)
end
end
end
Commentary on the solution:This solution is a bit of a hack and a better way might be to handle the ant tasks with their own class like the FileTask class does it. But my special behavior is so simple that at this point it might be overkill. Anyway, this works for now.
The Limitations:It is important to note that a major limitation of this is the inability to pass parameters to your ant tasks. We don't have many ant tasks that you have to pass parameters to (thank you build.properties), so I'm not going to worry about this right now. But going forward it would be nice to see that work. The problem is that rake parses the command line options before it initializes your Rakefile, so I can't overwrite the handling of the command line options from a Rakefile. Too bad. I am going to post a question on the Rake-devel mailing list to see if anyone there has some bright ideas on this, and I might whip up a patch for rake that would make this possible, though I haven't really thought through everything yet. Another limitation is that each ant task is a seperate call to ant, so if you run multiple tasks that depend on the same task that prerequisite task will get run multiple times. That could also be overcome by just storing all the "unknown" tasks as a list and making one ant call at the end. I leave that as an exercise for the reader. :)
Now you will go forth with the power and simplicity of rake without losing your investment in ant!