In Progress
Unit 1, Lesson 1
In Progress

Rake: Finding Tasks

Video transcript & code

When we encounter a new Ruby project, one of the first points of contact we have with that project is often the Rakefile. Today I thought we might talk about some useful tools for understanding and finding the tasks that a project's Rakefile defines.

Here we are in the project directory for one of my gems. Let's dump the Rakefile to get a feel for what kinds of tasks are defined.

$ cat Rakefile
require "bundler/gem_tasks"

begin
  require 'rspec/core/rake_task'
  RSpec::Core::RakeTask.new(:spec)
rescue LoadError
end

task :default => :spec

require "yard"

task :build => :readme

desc "Build the README"
task :readme => "README.markdown"

file "README.markdown" => "README.erb" do
  puts "Generating README.markdown"
  require "erb"
  template = IO.read("README.erb")
  IO.write("README.markdown", ERB.new(template).result)
end

YARD::Rake::YardocTask.new do |t|
  # t.files   = ['lib/**/*.rb', OTHER_PATHS]   # optional
  # t.options = ['--any', '--extra', '--opts'] # optional
  # t.stats_options = ['--list-undoc']         # optional
end
task :yard => :readme

We can see some explicitly defined tasks in this file. But we can also see that it requires some other libraries which might well define tasks of their own. How can we know the complete list of tasks that are available to us?

Chances are, if you are familiar with any Rake command-line options at all, you know about the -T option. -T tells Rake to dump a list of tasks and then immediately exit.

$ rake -T
rake build    # Build brainguy-0.0.1.gem into the pkg directory
rake install  # Build and install brainguy-0.0.1.gem into system gems
rake readme   # Build the README
rake release  # Create tag v0.0.1 and build and push brainguy-0.0.1.gem to Rubygems
rake spec     # Run RSpec code examples
rake yard     # Generate YARD Documentation

Now we can see some of the gem-defined tasks, like build, install, and release, that were implicitly set up by requiring the bundler/gem_tasks library. We also have some documentation on each, which is nice.

But is this a complete list? We'll return to that question. For now, let's run the rake build task, which is supposed to build a gem file.

$ rake build
Generating README.markdown
brainguy 0.0.1 built to pkg/brainguy-0.0.1.gem.

This is a little unusual. Before the command builds a gem file, it first says something about generating a README.markdown file. How did that become part of building a gem?

We could explore the Rakefile, but that's not always an ideal option. In older, more established projects, the Rakefile may be hundreds of lines long, and/or split up into multiple files.

Another option is to ask Rake to list prerequisites, using the -P flag. Let's try that out.

$ rake -P 
rake README.markdown
    README.erb 
rake build
    readme 
rake default
    spec 
rake install
    build 
rake readme
    README.markdown 
rake release
    build 
    release:guard_clean
    release:source_control_push
    release:rubygem_push
rake release:guard_clean
rake release:rubygem_push
rake release:source_control_push
rake spec 
rake yard
    readme

This listing is similar in some ways to the -T output. But it also differs in several important ways. First, obviously, there is no documentation this time around. Second, each task is followed by the tasks it depends on, otherwise known as its prerequisites. If we go down the list to the entry for build, we can see that it depends on the readme task. We can then look for the readme task, and see that it in turn depends on a file called README.markdown being generated. In other words, before Rake is allowed to proceed with any other actions associated with the build task, it must first run a task to build the README.markdown file.

Let's go on a quick digression here. A third difference you might have noticed between this output and the output from -T is that it is listing tasks which simply didn't appear in the -T output. Where did these new tasks appear from?

$ rake -T
rake build    # Build brainguy-0.0.1.gem into the pkg directory
rake install  # Build and install brainguy-0.0.1.gem into system gems
rake readme   # Build the README
rake release  # Create tag v0.0.1 and build and push brainguy-0.0.1.gem to Rubygems
rake spec     # Run RSpec code examples
rake yard     # Generate YARD Documentation

Well, if we look in the Rakefile, we can see that the README.markdown file task doesn't have a description line above it. And there's our answer: by default, -T only lists documented tasks. It effectively assumes that if we didn't document a task, it must be for internal use only.

file "README.markdown" => "README.erb" do
  puts "Generating README.markdown"
  require "erb"
  template = IO.read("README.erb")
  IO.write("README.markdown", ERB.new(template).result)
end

If we want, we can force -T to list all defined tasks, whether they are documented or not. We do this by adding a -A option.

 $ rake -T -A
rake README.markdown  #
rake build            # Build brainguy-0.0.1.gem into the pkg directory
rake default          #
rake install          # Build and install brainguy-0.0.1.gem into system gems
rake readme           # Build the README
rake release          # Create tag v0.0.1 and build and push brainguy-0.0.1.gem to Rubygems
rake spec             # Run RSpec code examples
rake yard             # Generate YARD Documentation

We now know that the build task depends on a task called readme. The question now becomes, where is this dependency defined? After all, build is a task that's defined inside the Bundler gem, right?

To answer this question, we can use another Rake flag, -W, to ask where a given rule task is defined.

$ rake -W build 
rake build home/avdi.gem/ruby/2.2.0/gems/bundler-1.7.12/lib/bundler/gem_helper.rb:38:in `install' 
rake build /home/avdi/sync/dev/brainguy/Rakefile:13:in `<top (required)>'

The interesting thing about this output is that there are two definitions listed. This is because, in Rake, it's possible to declare a task any number of times. Each declaration enhances the task with more dependencies or actions.

The first definition is deep in the Bundler gem, as we expected. But the second definition is in our local project Rakefile. And if we check it out, we can see an un-documented task line for the build task that adds a dependency on the readme task.

task :build => :readme

And now we have all of our answers: we know that this project adds extra dependencies to the standard build task, and we know exactly where those dependencies are tacked on.

Part of the power of Rake comes from this ability to accumulate a task definition across multiple files in different projects. But this can also be quite confusing, because there is no obvious visual linkage between related definitions. For this reason, if we want to get the most out of Rake, it's good to know how to ask it where things are defined. Happy hacking!

Responses