Caius Theory

Now with even more cowbell…

Programatically invoke rake task with --trace enabled

Rake is a Make-like program implemented in Ruby (to quote the website.) It contains tasks written in ruby code you can invoke from the command line. These can run prequisite tasks first too, in this case we configure the setup task to be invoked before the work task is ever invoked.

task :setup do
  puts "Setting up"
end

task work: :setup do
  puts "Doing work"
end
$ rake work
Setting up
Doing work

Rake also supports --trace as an argument which outputs a bunch of debugging information, allowing you to see which tasks have been executed in which order and how often they were invoked.

$ rake --trace work
** Invoke work (first_time)
** Invoke setup (first_time)
** Execute setup
Setting up
** Execute work
Doing work

From the --trace output we can see calling the same task multiple times in the same rake process doesn’t execute the task multiple times, but it is invoked the correct number of times.

$ rake --trace work setup work
** Invoke work (first_time)
** Invoke setup (first_time)
** Execute setup
Setting up
** Execute work
Doing work
** Invoke setup
** Invoke work

For performance reasons (Rails apps can take multiple seconds to start a new process), we might want to run multiple tasks within the same rake process.

task :create_assets do
  puts "Created some assets"
end

We’re now stuck in a catch-22 from the shell in terms of tracing however, if we run both tasks in the same process we can only trace both, or trace neither.

$ rake --trace create_assets work
** Invoke create_assets (first_time)
** Execute create_assets
Created some assets
** Invoke work (first_time)
** Invoke setup (first_time)
** Execute setup
Setting up
** Execute work
Doing work

$ rake create_assets work
Created some assets
Setting up
Doing work

If we’ve previously been calling multiple tasks in different commands and only tracing some of them, we want to maintain the same output in our CI system logs but also reap the performance benefit of not booting the app multiple times over. This means having tracing enabled for some tasks, but not others which we can’t do from the shell.

Luckily there’s another mechanism to invoke multiple rake tasks, we define a new rake task that can then call the other rake tasks and setup tracing before calling the last one by mimicking the internals of rake --trace.

(Rake::Task.[] lets you find rake tasks by their labels, and then Rake::Task#invoke calls the code defined for the task as if you’d run rake whatever on the cli.)

task :ci_perform do
  Rake::Task["create_assets"].invoke

  # Enable `--trace` programatically for remaining tasks invoked
  Rake.application.options.trace = true
  Rake.application.options.backtrace = true
  Rake.application.options.trace_output = $stderr
  Rake.verbose(true)

  Rake::Task["work"].invoke
end

Et voila, we get our trace output for the work task but not until that’s invoked.

$ rake ci_perform
Created some assets
** Invoke work (first_time)
** Invoke setup (first_time)
** Execute setup
Setting up
** Execute work
Doing work