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.