In Progress
Unit 1, Lesson 1
In Progress

Rake Invoke

Video transcript & code

I received a ton of great feedback from the series of episodes I did on Rake. I also got some questions. Today I want to quickly address one of them.

The question was this: assuming we have a Rakefile that automates useful tasks, how can we invoke those tasks from within an application rather than from the command line?

For the sake of demonstration, we'll use this simple Rakefile. It contains two tasks, one at the top level, and one within a namespace. It also contains a rule for converting .md files into .html files.

task "hello" do
  puts "Hello from Rakefile"
end

namespace "tapas" do
  task "hello" do
    puts "Hello from namespace"
  end
end

rule ".html" => ".md" do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

Let's execute the hello task from within another Ruby program. First, we'll require the rake library. Then we initialize Rake by invoking Rake.application.init. Next we call Rake.application.load_rakefile to find and load the Rakefile, based on the current working directory.

Finally, we find the task we want using the subscript operator on the Rake::Task class. Once we have the task, we invoke it by sending it the—you guessed it— #invoke message.

require "rake"
Rake.application.init
Rake.application.load_rakefile
Rake::Task["hello"].invoke
# >> Hello from Rakefile

And that's all there is to it: the task is executed and we can see the output.

Now as you may recall from previous episodes on Rake, Rake is all about doing the least possible amount of work. We can see that when we try to invoke the same task twice. We only see one line of output. This is because Rake keeps track of the tasks it has executed and considers the hello task to be already done, so it doesn't bother running it again when we ask it to.

require "rake"
Rake.application.init
Rake.application.load_rakefile
Rake::Task["hello"].invoke
Rake::Task["hello"].invoke
# >> Hello from Rakefile

If we are sure we want to execute the code associated with a task, even if that task has already been run, we can use #execute instead of #invoke.

require "rake"
Rake.application.init
Rake.application.load_rakefile
Rake::Task["hello"].invoke
Rake::Task["hello"].execute
# >> Hello from Rakefile
# >> Hello from Rakefile

Note however that #execute will only run the code associated directly with a task; it will not run any prerequisites of that task.

In some cases we may want to be specific about what Rakefile or files to load, rather than relying on Rake's autodiscovery. In that case we need even less code: we can use Rake.load_rakefile and immediately start running tasks.

require "rake"
Rake.load_rakefile("./Rakefile")
Rake::Task["hello"].invoke
# >> Hello from Rakefile

If we want to invoke a task within a namespace we just need to be sure to use its fully-qualified name:

require "rake"
Rake.load_rakefile("./Rakefile")
Rake::Task["tapas:hello"].invoke
# >> Hello from namespace

Running tasks programmatically is not limited to tasks with explicit names. We can also trigger rules the same way. Let's say we have a Markdown file called hello.md. In our Rakefile there is a rule for turning .md files into .html files using pandoc. To cause the HTML file to be built, we can specify the file we want to end up with, hello.html, as if it were a task name.

cat hello.md
require "rake"
Rake.load_rakefile("./Rakefile")
Rake::Task["hello.html"].invoke

When we run this, the rule is triggered and the file is built.

cat hello.html

So there you go: now you know how to invoke Rake tasks from within any program. With all that said, however, it's probably a better idea to move any code that you want to invoke from both the command line and an application into a file of its own. Then you can require it in both the Rakefile and your application.

def hello
  puts "Hello, world"
end
require_relative "lib/hello"

task "hello" do
  hello
end

namespace "tapas" do
  task "hello" do
    puts "Hello from namespace"
  end
end

rule ".html" => ".md" do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

That's all for today. Happy hacking!

Responses