Rake

In the previous chapters, we explored some Ruby tools and concentrated on how those tools fit into the Ruby toolbox: how they interact with Ruby, where they live on your local filesystem, and how to identify issues that arise when using them. In this chapter, we will examine another tool, Rake. This time, we will concentrate on what it does for you and why you need it.

What Is Rake?

Rake is a Rubygem that automates many common functions required to build, test, package, and install programs; it is part of every modern Ruby installation, so you don't need to install it yourself.

Here are some common Rake tasks that you may encounter:

  • Set up required environment by creating directories and files
  • Set up and initialize databases
  • Run tests
  • Package your application and all of its files for distribution
  • Install the application
  • Perform common Git tasks
  • Rebuild certain files and directories (assets) based on changes to other files and directories

In short, you can write Rake tasks to automate anything you may want to do with your application during the development, testing, and release cycles.

How Do You Use Rake?

Rake uses a file named Rakefile that lives in your project directory; this file describes the tasks that Rake can perform for your project, and how to perform those tasks. For instance, here is a very simple Rakefile:

desc 'Say hello'
task :hello do
  puts "Hello there. This is the `hello` task."
end

desc 'Say goodbye'
task :bye do
  puts 'Bye now!'
end

desc 'Do everything'
task :default => [:hello, :bye]

This Rakefile contains three tasks: two that simply display a single message, and one task that has the other tasks as prerequisites or dependencies. The first two tasks are named :hello and :bye, while the final task is the default task; Rake runs the default task if you do not provide a specific task name when you invoke Rake.

Each of the above tasks calls two Rake methods: desc and task. The desc method provides a short description that Rake displays when you run rake -T (see below), and the task method associates a name with either a block of Ruby code (lines 2-4 and 7-9) or a list of dependencies (line 12). Here, the :default task depends on the :hello and :bye tasks: when you run the :default task, Rake will run the :hello and :bye tasks first.

Let's see this in action. The first thing you should do with any Rakefile is find out what tasks it can run. You do this with the rake -T command:

$ bundle exec rake -T
rake bye      # Say goodbye
rake default  # Do everything
rake hello    # Say hello

This shows that there are three tasks defined by the Rakefile: bye, default, and hello. The output shows a short description of each task on the right: this information comes from the desc method calls in Rakefile.

Note that we used bundle exec rake -T, not just rake -T. We discussed bundle exec in the previous chapter. Often, rake -T will work just fine, but many prefer to use bundle exec with rake when possible (i.e., when your project uses Bundler); if you don't, you may see unusual messages like this:

$ rake -T
rake aborted!
Gem::LoadError: You have already activated rake 12.0.0, but your Gemfile requires rake 10.4.2. Prepending `bundle exec` to your command may solve this.

This shows that your Gemfile requires a version of rake that differs from the version you get when you type rake at the command line. bundle exec rake makes sure that you use the correct version of rake that your application depends on.

If you see:

Could not locate Gemfile or .bundle/ directory

when using bundle exec rake, this means that your project isn't using Bundler. Here, you should omit bundle exec and just run rake instead.

One very important thing to notice is that Rakefile is actually a Ruby program. You can put any Ruby code you want in a Rakefile and run it as part of a task. Commands like desc and task are just method calls to parts of Rake; these method calls comprise a Domain Specific Language (DSL) for writing automated Rake tasks.

Once you know what Rake tasks you can run, you just have to run them:

$ bundle exec rake bye
Bye now!

$ bundle exec rake hello
Hello there. This is the `hello` task.

$ bundle exec rake default
Hello there. This is the `hello` task.
Bye now!

$ bundle exec rake                     # we don't need to specify 'default'
Hello there. This is the `hello` task.
Bye now!

Why Do I Need Rake?

One reason why you need Rake is that nearly every Ruby project you can find has a Rakefile, and the presence of that file means you need to use Rake if you want to work on that project. However, just because everybody does something doesn't mean you need to use it too. If you start a brand-new project, you can always say "I don't feel like using Rake, so I won't."

While you can always opt-out of using Rake in your projects, there is little point to doing so. Every project that aims to produce a finished project that either you or other people intend to use in the future has repetitive tasks the developer needs. For instance, to release a new version of an existing program, you may want to:

  • Run all tests associated with the program.
  • Increment the version number.
  • Create your release notes.
  • Make a complete backup of your local repo.

Each step is easy enough to do manually, but you want to make sure you execute them in the proper order (for instance, you want to set the new version number before you commit your changes). You also don't want to be at the mercy of arbitrary typos that may do the wrong thing. It's far better to have a way to perform these tasks automatically with just one command, which is where Rake becomes extremely useful.

Your Rakefile likely has each of these as a separate task, as well as a single overall task (call it release, for instance) that steps through the tasks one at a time. The release task would stop only when it completes all the tasks or one task fails.

A Real World Example

Let's look at a real world Rakefile, the one used by the Pry Gem.

Note that Pry's Rakefile is a bit sensitive in terms of the environment, so, if you follow along, you may see different output than we show. Don't be alarmed or concerned about this right now.

If you want to follow along, go to the Pry repo and clone it to your local filesystem:

$ git clone https://github.com/pry/pry

See the Introduction to Git and GitHub book for more detail about git clone.

Make the cloned directory your current directory:

$ cd pry

Before we look at the Rakefile, let's run Bundler to make sure we have everything installed that needs to be installed. Run the following command from the pry directory:

$ bundle install

This may take several minutes depending on how many and which Gems Bundler needs to install.

Once Bundler has installed everything, you can ask rake to show you what tasks the Rakefile defines:

$ bundle exec rake -T
rake clean                    # Remove any temporary products
rake clobber                  # Remove any generated files
rake default                  # Set up and run tests
rake gems                     # build all platform gems at once
rake profile                  # Profile pry's startup time
rake pry                      # Run pry (you can pass arguments using _ in ...
rake pushgems                 # build and push latest gems
rake reinstall                # reinstall gem
rake rmgems                   # remove all platform gems
rake ruby:clobber_package     # Remove package products
rake ruby:gem                 # Build the gem file pry-0.10.4.gem
rake ruby:package             # Build all the packages
rake ruby:repackage           # Force a rebuild of the package files
rake test                     # Run tests
rake version                  # Show pry version

This abridged listing eliminates several groups of commands for brevity.

Let's say we want to run the tests that the developer uses to test Pry. As we scan the list of commands and their descriptions from above, we see that rake default sets up and runs the tests, while rake test just runs the tests. Given that this is the first time we're running the lists, let's run the default action first; there may be some setup that Rake needs to do:

$ bundle exec rake default
  ...output from tests is here...
Finished in 6.73 seconds (files took 0.61458 seconds to load)
1166 examples, 0 failures

Your results may be different at this point! You may even have seen some errors and the tests may not have run. As mentioned above, don't be concerned about this right now.

You can also run:

$ bundle exec rake

Rake invokes the "default" task when no task is provided on the command line.

So, how did Rake know what to do when you ran bundle exec rake default? Let's look at Pry's Rakefile (again, what you see here may differ from what you see with your copy of Pry). If you scan through this file, you will soon find these two lines (or something very similar):

desc "Set up and run tests"
task :default => [:test]

As before, desc provides the description for the next task call in the file: the :default task. The array to the right of the => in the task call identifies a list of dependencies for the named task. Here, the :default task depends on the :test task, so Rake also runs the :test task.

Scanning down a little further, we find something like this:

desc "Run tests"
task :test do
  paths =
    if explicit_list = ENV['run']
      explicit_list.split(',')
    else
      Dir['spec/**/*_spec.rb'].shuffle!
    end
  run_specs paths
end

This call to task includes a block of Ruby code; the block tells Rake how to run the tests for Pry. Specifically, it first determines the paths of the test files it needs to run (these names come from the run environment variable if present; otherwise, the task reads the names from the spec directory). Once Rake has the path names, it runs the tests with the run_specs method, which Rake provides.

If you haven't encountered specs yet, think of them as equivalent to Minitest tests. There are a variety of different testing frameworks for Ruby, and they sometimes use differing terminology. The specs term comes from a testing framework named RSpec, a very popular tool among Rubyists.

Let's look at the reinstall task now. Before we try running it, find the :reinstall task in the Rakefile. It looks something like this:

desc "reinstall gem"
task :reinstall => :gems do
  sh "gem uninstall pry" rescue nil
  sh "gem install #{File.dirname(__FILE__)}/pkg/pry-#{Pry::VERSION}.gem"
end

Here, we see another call to desc and task. The :reinstall task has a dependency named :gems. Note that we don't use array syntax here; that means that :gems is an array defined elsewhere in Rakefile:

desc "build all platform gems at once"
task :gems => [:clean, :rmgems, 'ruby:gem', 'jruby:gem']

:gems depends on four other tasks, each of which will be run by Rake. We won't worry much about what they do. Briefly, they perform some cleanup and setup processing required by Rubygems.

Once the :gems task finishes, Rake returns to the :reinstall task, and runs the block we passed to task. This block uninstalls the existing version of pry, then reinstalls it based on the current state of the local repo (your pry directory).

If this sounds as though it might break something, don't worry. As it happens, this code installs the updated Pry package in the pkg subdirectory of the current directory. This won't interfere with other programs that use Pry, but you can still use and inspect what got installed in the pkg directory. So, feel free to go ahead and run:

$ bundle exec rake reinstall
$ ls -l pkg  # this will show you the newly "installed" package.

Again, don't be alarmed if this command fails on your system.

When Things Go Wrong

As a Rubygem, it is subject to the same problems as other Gems. It is also subject to problems that occur with Ruby version managers. Thus, you can use many of the commands discussed in previous chapters to help diagnose issues with Rake.

The main issue you will have with Rake is when to use bundle exec: sometimes you need to use bundle exec, sometimes you don't, and sometimes you can't use it. Generally, if your project uses Bundler (i.e., you have a Gemfile), use bundle exec; if your project doesn't use Bundler, you can omit bundle exec and just use rake.

Summary

In this chapter, we've taken a brief look at Rake and shown you how to use it to automate tasks while working on a Ruby project.