Rake is a build tool written in Ruby, similar to make, Ant and Phing. There is a major difference between Rake and the others, though. Unlike the rest of the tools, Rake does not provide an external DSL (like XML build file in Ant). Instead, all the task are written in pure Ruby. Therefore you gain full flexibility and can take advantage of some nice Ruby features.

What Are The Build Tools?

If you’ve ever tried to install software from source in the Linux or Unix system, there is a high probability that you have already had contact with make. Installation process usually looks the same. First you change current directory to that containing uncompressed source code, then you type in following commands:

./configure
make
make install

Second and third lines are simply make program invocations. When launched, make first looks for the Makefile. The latter contains information about source files and dependencies between them. make sorts the dependencies topologically and tries to resolve them in proper order. So it goes like this: first, software developer specifies dependencies and then the build tool is responsible for handling them.

make also allows to spare time. If the source code hasn’t changed since last compilation, it won’t be processed again as it would be pure waste of time.

Build tools can be used not only for source code compilation, but that was in fact the task they were created for. In general, build tools are being used to automate tiresome and repeating tasks.

Difference Between Rake and Other Build Tools

As I have already mentioned, there is one major difference between Rake and other build tools. Instead of writing Makefile for make or build.xml for Ant and Phing, you just write lines of Ruby code. You don’t have to learn new complicated build tool syntax, inessential if you change one build tool for another.

But that’s enough of theory, let’s get to practice!

Install Rake

Rake installation is easy as pie, provided that you have Ruby gems in your system. To install Rake, type in following command:

gem install rake

In most cases you’re going to need root privileges in order to do that, so you have either to switch to superuser or precede the given command with “sudo “:

sudo gem install rake

Once the Rake gem is installed, we can write our first simple Rake task.

First Rake Task

The most simple way to define a Rake task is to write the following Ruby code:

task :default do
  puts "Hello World!"
end

Rake tasks should always be located in file named rakefile, Rakefile, rakefile.rb or Rakefile.rb. First two forms are most commonly used.

Once the rakefile is saved, you should change current directory to one containing it and run the rake command:

$ rake
(in /home/lukasz/ruby/rake_examples)
Hello World!

Our first Rake task is working!

So, what actually happened? Well, when given rakefile, Rake is looking for tasks which are simply task method invocations. There may be many tasks located in one rakefile. When running Rake from the command line, you can pass name of the task that you want to be executed. If there is no task given, Rake is looking for the default task. That is why our Rake invocation did the job without passing any extra parameters.

Let’s try to put many tasks into one rakefile:

task :ring do
  puts "Bell is ringing."
end

task :enter do
  puts "Entering home!"
end

When we try to simply run rake command without passing any parameters, it will end up with an error:

$ rake
(in /home/lukasz/ruby/rake_examples)
rake aborted!
Don't know how to build task 'default'

(See full trace by running task with --trace)

There is no default task in this rakefile, so we have to explicitly pass the task name:

$ rake enter
(in /home/lukasz/rake_examples)
Entering home!
$ rake ring
(in /home/lukasz/rake_examples)
Bell is ringing.

Although new rakefile seems to work, something went wrong. You cannot step into somebody other’s house without ringing the bell first (at least - you should not)! And that’s where the dependencies come in.

Expressing Dependencies

Let’s introduce a slightly modified rakefile:

task :ring do
  puts "Bell is ringing."
end

task :enter => :ring do
  puts "Entering home!"
end

When we try to enter home now, the bell is being rang first:

$ rake enter
(in /home/lukasz/ruby/rake_examples)
Bell is ringing.
Entering home!

Now Rake is responsible for ringing the bell before walking in. You can define way more complicated dependencies and it is Rake that is responsible for solving them. You tell what your needs are and the build tool does the donkey work.

The good news is that dependencies can be specified not only when defining the task, but also later, depending on the run time conditions. After all, we write tasks in Ruby programming language, not a static XML file, remember?

To achieve the same effect with ringing the bell, we could write:

task :ring do
  puts "Bell is ringing."
end

task :enter do
  puts "Entering home!"
end

task :enter => :ring

Effect will be the same.

Describing Tasks

Each task you write may be described in few simple words. Description will not only serve you as an inline comment, it will also appear on the list of available tasks. Let’s add descriptions first:

desc 'Ring the bell'
task :ring do
  puts "Bell is ringing."
end

desc 'Enter home'
task :enter => :ring do
  puts "Entering home!"
end

To view list of available tasks, run rake with either -T or --tasks parameter:

$ rake -T
(in /home/lukasz/ruby/rake_examples)
rake enter  # Enter home
rake ring   # Ring the bell

I prefer the shorter form (-T) because it saves me keystrokes, but the decision is up to you.

File Tasks

We told that the build tools were made in order to simplify project compiling process. There are many dependencies between specific files and build tools support them, so does Rake. It allows to define a special type of task - a file task:

file 'products.sql' => 'products.xml' do
  # build SQL INSERT clause and save it in products.sql file,
  # basing on products.xml datafile
end

In order to run a file task, simply type in the output file name:

$ rake products.sql

File task is - in general - not different than a regular task. The point is that it won’t run if the input file (products.xml in this case) is not present. It also won’t run if the result file (products.sql) is not older than the input file. If you don’t accept this behaviour, i.e. you need to regenerate the output file every time the task is being run, use a regular task instead.

FileUtils

Rake is including the FileUtils module, which provides a set of Unix-like file operating methods including mkdir, rmdir, cp, mv, chmod and touch.

Because FileUtils module is already included, you can call its methods directly, without using the scope operator:

task :manipulate_files do
  mkdir 'new_dir'
  mv 'new_dir', 'lukasz'
  chmod 0777, 'lukasz'
  touch 'lukasz/wrobel.txt'
  rm_rf 'lukasz'
end

If you are familiar with Linux/Unix command shell, you will learn how to use the FileUtils module in no time.

By the way, do you remember file task behaviour? If the output file is not older than the input file (i.e. it is up to date), the file task will not run. In case you want to perform such a check in a regular task, you can use the FileUtils method named (surprisingly!) uptodate?

task :check do
  # ...

  unless uptodate?(output_file, ['input_file.xml'])
    # regenerate output_file
  end
end

FileList

Imagine a file task, where many input files are combined to give only one output file as a result. The most obvious way to define file dependencies would be:

one_file_to_rule_them_all = 'database.sql'
tables_sql = ['orders.sql', 'payments.sql', 'categories.sql']

file one_file_to_rule_them_all => tables_sql

It will work, but what when a new input SQL file is created? Well, we have to remember to manually add it to the tables_sql variable.

I don’t know how about you, but I feel a little bit confused. We told that build tools were designed to automate boring and repeating tasks. And manually adding files to the list IS a boring task!

Fortunately, Rake’s author knew that, too. There is the FileList class available which can be helpful in such situations. Let’s use it now:

one_file_to_rule_them_all = 'database.sql'

FileList['*.sql'].each {|table| file one_file_to_rule_them_all => table}

Forget about adding every single SQL file to the list; Rake will do it for you.

Since dependencies are already described using the FileList, all you need to do is to add the task body:

file one_file_to_rule_them_all do
  puts 'I require the SQL files'
end

Then you can invoke the task by running:

$ rake database.sql
(in /home/lukasz/ruby/rake_examples)
I require the SQL files

Summary

There are many Rake functions that I didn’t cover in this tutorial, including clean, clobber, rdoc and gem tasks, pathmap, rules and namespaces. You will have a chance to get to know them later.

I hope that this article is a good starting point to get intimate with Rake. Rake has proved its worth and now it is widely used in the Ruby world, so writing Rake tasks is a must-have skill. Good luck in your own experiments with Rake!

If you want to know how to use Rake to run RSpec tests, read the RSpec Rake Task article.

comments powered by Disqus