Rake Task Management Essentials

By Andrey Koleshko
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    The Software Task Management Tool – Rake
About this book
Publication date:
April 2014


Chapter 1. The Software Task Management Tool – Rake

In this chapter, we will cover the installation of Rake, the definition of basic terms such as rake task and Rakefile, and how to use them for easy programming issues. The introduction will be given using straightforward examples to explain the terms as clearly as possible. You will see that Rake is a tool that is written in the Ruby programming language, and that's why any Ruby code can be written in a Rake application. Also, you have the choice of using any available Ruby library in a Rake project. This feature makes Rake the winner compared to many other build tools, which use their own limited languages. The chapter will serve as a base for introducing Rake's Domain Specific Language (DSL) and project file structuring.

In this chapter, we will cover the following topics:

  • Installing Rake

  • Introducing rake tasks

  • The command-line arguments

  • Using global Rakefiles to run tasks anywhere

  • Defining custom rake tasks

  • The structure of a Rake project

  • The code conventions of Rake


Installing Rake

As Rake is a Ruby library, you should first install Ruby on the system if you don't have it installed already. The installation process is different for each operating system. However, we will see the installation example only for the Debian operating system family.

Just open the terminal and write the following installation command:

$ sudo apt-get install ruby


If you have an operating system that doesn't contain the apt-get utility and if you have problems with the Ruby installation, please refer to the official instructions at https://www.ruby-lang.org/en/installation. There are a lot of ways to install Ruby, so please choose your operating system from the list on this page and select your desired installation method.

Rake is included in the Ruby core as Ruby 1.9, so you don't have to install it as a separate gem. However, if you still use Ruby 1.8 or an older version, you will have to install Rake as a gem. Use the following command to install the gem:

$ gem install rake


The Ruby release cycle is slower than that of Rake and sometimes, you need to install it as a gem to work around some special issues. So you can still install Rake as a gem and in some cases, this is a requirement even for Ruby Version 1.9 and higher.

To check if you have installed it correctly, open your terminal and type the following command:

$ rake --version

This should return the installed Rake version.

The next sign that Rake is installed and is working correctly is an error that you see after typing the rake command in the terminal:

$ mkdir ~/test-rake
$ cd ~/test-rake
$ rake
rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
(See full trace by running task with --trace)


Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.


Introducing rake tasks

From the previous error message, it's clear that first you need to have Rakefile. As you can see, there are four variants of its name: rakefile, Rakefile, rakefile.rb, and Rakefile.rb. The most popularly used variant is Rakefile. Rails also uses it. However, you can choose any variant for your project. There is no convention that prohibits the user from using any of the four suggested variants.

Rakefile is a file that is required for any Rake-based project. Apart from the fact that its content usually contains DSL, it's also a general Ruby file. Also, you can write any Ruby code in it. Perform the following steps to get started:

  1. Let's create a Rakefile in the current folder, which will just say Hello Rake, using the following commands:

    $ echo "puts 'Hello Rake'" > Rakefile
    $ cat Rakefile
    puts 'Hello Rake'

    Here, the first line creates a Rakefile with the content, puts 'Hello Rake', and the second line just shows us its content to make sure that we've done everything correctly.

  2. Now, run rake as we tried it before, using the following command:

    $ rake
    Hello Rake
    rake aborted!
    Don't know how to build task 'default'
    (See full trace by running task with --trace)

    The message has changed and it says Hello Rake. Then, it gets aborted because of another error message. At this moment, we have made the first step in learning Rake.

  3. Now, we have to define a default rake task that will be executed when you try to start Rake without any arguments. To do so, open your editor and change the created Rakefile with the following content:

    task :default do
      puts 'Hello Rake'
  4. Now, run rake again:

    $ rake
    Hello, Rake

The output that says Hello, Rake demonstrates that the task works correctly.


The command-line arguments

The most commonly used rake command-line argument is -T. It shows us a list of available rake tasks that you have already defined.

We have defined the default rake task, and if we try to show the list of all rake tasks, it should be there. However, take a look at what happens in real life using the following command:

$ rake -T

The list is empty. Why? The answer lies within Rake. Run the rake command with the -h option to get the whole list of arguments. Pay attention to the description of the -T option, as shown in the following command-line output:

-T, --tasks [PATTERN] Display the tasks (matching optional PATTERN) with descriptions, then exit.


You can get more information on Rake in the repository at the following GitHub link at https://github.com/jimweirich/rake.

The word description is the cornerstone here. It's a new term that we should know. Additionally, there is also an optional description to name a rake task. However, it's recommended that you define it because you won't see the list of all the defined rake tasks that we've already seen. It will be inconvenient for you to read your Rakefile every time you try to run some rake task. Just accept it as a rule: always leave a description for the defined rake tasks.

Now, add a description to your rake tasks with the desc method call, as shown in the following lines of code:

desc "Says 'Hello, Rake'"
task :default do
  puts 'Hello, Rake.'

As you see, it's rather easy. Run the rake -T command again and you will see an output as shown:

$ rake -T
rake default  # Says 'Hello, Rake'


If you want to list all the tasks even if they don't have descriptions, you can pass an -A option with the -T option to the rake command. The resulting command will look like this: rake -T -A.


Using global Rakefiles to run tasks anywhere

By default, Rake is looking for tasks that are placed in the current folder (that is, the folder where you run the rake command) in the Rakefile. Assume that we need to have a rake task that can be executed in any folder. For example, say that we have a rake task that cleans the Linux files ending with ~. The following Rakefile defines the rake task to remove them:

desc 'Cleans backup files *~'
task :default do
  files = Dir['*~']

Here, we get temporary files in the current folder and remove them with the rm method. This method is defined in the FileUtils module, which is included in Rake as well. So, we will discuss it in the next chapters.

When you are in the current folder, check this rake task using the Rakefile:

$ rake

Here, we see that the rm command was executed and Rake explicitly said this in the second line. If you don't want to see this verbose message, pass the -q option to the command.

However, what would happen if we go to the folder one level up? When you try to type the rake command, you will have an error message that says that no Rakefile was found. We can get rid of this problem by passing the -f option with the path to the Rakefile as shown in the following lines of code:

$ rake -f ~/my-rake-task/Rakefile

This works well, but you may agree with me that it's too unhandy. Rake produces one useful feature to make this situation work the way we want. It's based on the method of finding the Rakefile. First, Rake tries to find the Rakefile in the current folder. If Rake can't find it there, the search continues till it reaches the user's home folder. If there is no Rakefile there, it finally raises an exception saying that the Rakefile was not found. We can apply this behavior to our issue. Just move the Rakefile to your home folder and mark the rake tasks defined in it as available for the current user everywhere. Open the terminal and type the following commands to achieve the expected output:

$ mv ~/my-rake-task/Rakefile ~/
$ cd ~/my-rake-task
$ rake
(in /Users/andrey)

As you can see, this works as expected, and there is one more new line, as follows:

(in /Users/andrey)

This command says that the Rakefile was found at the user home folder. You can disable showing this information by passing the -s option.

There is another way to define global Rakefiles. You have an option to define them in the ~/.rake folder, and they can be executed from any folder with the help of the -g option. The following is the Rake output of the help command:

-g, --system Using system wide (global) rakefiles (usually '~/.rake/*.rake').

So, let's define a global Rakefile in this way and check it in action. The following is an example of how to do it through the terminal:

$ mkdir ~/.rake
$ touch ~/.rake/hello.rake
$ echo -e 'task "hello" do\n  puts "Hello, Rake"\nend' > ~/.rake/hello.rake
$ rake -g hello
Hello, Rake

Defining custom rake tasks

So far, we defined only one task named default. Rake allows you to define your custom tasks with any name. The common form of the custom rake task definition is passing a task name to the task method and a block as a second argument. The block defines some action and usually contains some Ruby code. The rake task might have an optional description, which is defined with the desc method. This method accepts a text for the description of the task. The following code snippet is an example of defining a custom rake task:

desc 'Restart web server'
task :restart do
  touch '~/restart.txt'

This is an example of a possible rake task to restart Passenger (this is a module for the Nginx web server, which works with the Rails applications). We name the task restart. To run this task, just pass its name as the second argument to the rake command as shown in the following line of code:

$ rake restart

If you have a lot of tasks, it's handy to enclose them to the named spaces, as shown in the following code snippet:

namespace :server do
  desc 'Restart web server'
  task :restart do
    touch './tmp/restart.txt'

You can also run the task in the command line using the following command:

$ rake server:restart

Actually, the task method accepts more arguments. However, they are related to other topics that are explained further along in the book in Chapter 2, Working with Files, and Chapter 3, Working with Rules.


Task dependencies – prerequisites

Sometimes, you have to write tasks that depend on other tasks. For example, when I'm going to seed data in my project, I want to clean all the persisting data that can break my code. In this case, we can say that our seed data task depends on the clean seed data task. The following code example shows us a Rakefile for this case:

task :clean do
  puts 'Cleaning data...'

task :seed => :clean do
  puts 'Seeding data...'

The preceding code executes the clean do task before running the seed task. The result of the execution of this task is shown below the following line of code:

$ rake seed
Cleaning data...
Seeding data...

It works as expected.

If you have to run the task from another namespace, pass its whole name as a string, as shown in the following code snippet:

namespace :db do
  task :clean do
    puts 'Cleaning data...'

task :seed => 'db:clean' do
  puts 'Seeding data...'

However, if the dependent task is in the same namespace, you don't have to pass it as a string, as shown in the following code snippet:

namespace :db do
  task :clean do
    puts 'Cleaning data...'

  task :seed => :clean do
    puts 'Seeding data...'

Earlier in this chapter, we defined the default rake task. To be honest, we did it just to understand what happens on running rake without arguments and to introduce Rake in a few steps giving as less information as possible in an interactive way. However, in the practical word, nobody defines the default rake task with an action. Setting dependencies is a convenient feature. It allows the default task to refer to some other task as many times as you want without regression. For example, today, the default task runs a doc:generate task but tomorrow, we decide to run a test:run task instead. In such a situation, we can just change the prerequisite and that's it. So, always define your default rake task with the following template:

task :default => :some_task

It's also possible to pass many prerequisites for a task. The following line of code is an example of how to do this:

task :task1 => [:task2, :task3]

Multiple tasks definitions

A task might be specified more than once. Each specification adds its dependencies and implementation to the existing definition. This allows one part of a Rakefile to specify the actions and a different Rakefile (perhaps a separately generated one) to specify the dependencies.

For example, take a look a Rakefile that contains the following code:

task :name => [:prereq1, :prereq2] do
  # action

It can be rewritten as the following code:

task :name
task :name => [:prereq1]
task :name => [:prereq2]
task :name do
  # action

Passing arguments to the tasks

Assume that you have a rake task that sets the title for our blog and you want to pass it from the command line; this should be optional. If you don't pass the title of the blog, the default title should be set.

We have two solutions to solve this problem. The first solution is to pass parameters through the environment variable that is passed into the ENV variable in Ruby code (ENV is a hash-like accessor for environment variables, and it is available in any Ruby program). The second solution is using the built-in Rake syntax—you just pass variables to each task through square braces. The first use case doesn't allow you to pass variables for each task in isolation. The variables are shared among all the tasks in the Rakefile. So, the preferable style is the second choice. However, we are shown two alternatives, which will be discussed in the next sections.

The first alternative

The first alternative is a case where we pass variables using environment variables. The following code represents a Rakefile:

task :set_title do
  title = ENV['TITLE'] || 'Blog'
  puts "Setting the title: #{title}"

The following code is a usage example:

$ rake set_title TITLE='My Blog'
Setting the title: My Blog
$ rake set_title # default title should be set in this case
Setting the title: Blog

In the preceding example, the ENV variable approach can be used without any caution. The following code snippet represents the collision in sharing the variable between the tasks. Check the following Rakefile:

task :task1 do
  puts "#{ENV['TITLE']} in task1"

task :task2 do
  puts "#{ENV['TITLE']} in task2"

The following code is an example of usage:

$ rake task1 task2 TITLE='test'
test in task1
test in task2

You can see that the TITLE variable is accessible in both the tasks and is the same. Sometimes, you don't want to get this behavior and you need to pass the variables to each task individually.

A variable declared within a rake command will not persist in the environment. The following terminal output will confirm this statement:

$ export TITLE='Default Title'
$ rake set_title TITLE='My Blog'
Setting the title: My Blog
$ echo $TITLE
Default Title
The second variant

The second variant has a built-in Rake feature. The following is the Rakefile code:

task :set_title, [:title] do |t, args|
  args.with_defaults(:title => 'Blog')
  puts "Setting title: #{args.title}"


Please ignore the t variable at this moment; you will see what it means and what its usages are in Chapter 2, Working with Files.

Look at args, which is a hash-like object of the Rake::TasksArguments class. It has a useful method that is used here, named with_defaults, to merge the given arguments from the command line and the default values. If you don't pass the variables through the command line, the default variable for the title will be set.

The following code depicts how it may be used:

$ rake "set_title[My Blog]"
Setting title: My Blog
$ rake set_title
Setting title: Blog

Here, to pass the argument as a string with space (My Blog), I have enclosed the rake task with the argument within quotes. It's not the only case where I have to enclose the task name within double quotes. There are some terminals that don't understand the squared parentheses in the command line and should escape them with \ at the end of the code line of the rake task that is enclosed within the double quotes.

You are also able to pass multiple arguments to the rake task by separating them with a comma, as shown in the following line of command:

$ rake "name[Andrey,Koleshko]"

The task declaration for the preceding task is as follows:

task :name, [:first_name, :last_name] do |t, args|
  puts "First name is #{args.first_name}"
  puts "Last  name is #{args.last_name}"

Finally, you are able to pass variable-length parameters to the task with a comma, as we did in the previous example. In this case, you may use the extras method on the given args variable:

task :email, [:message] do |t, args|
  puts "Message: #{args.message}"
  puts "Recipients: #{args.extras}"
  puts "All variables: #{args.to_a}"

In the following example, the first argument will be assigned to the message variable on the args variable and the remaining arguments will go to the extras method. If you want to have an array that passes all the variables including the one associated with the message variable, you can call the to_a method on the args variable, as demonstrated in the preceding Rakefile.

$ rake "email[Hello Rake, ka8725@gmail.com, test@example.com]"
Message: Hello Rake
Recipients: ["ka8725@gmail.com", "test@example.com"]
All variables: ["Hello Rake", "ka8725@gmail.com", "test@example.com"]

The structure of a Rake project

Apart from the necessary Rakefile, there is a technique that allows us to form a good structure of a Rake project. Say that you have a very complicated Rake project with a lot of tasks. It's a good idea to split them into separate files and include them in the Rakefile. Fortunately, Rake already has this feature and you shouldn't care about implementing this feature from the scratch. Just place your separated files to the rakelib folder (it can be changed to custom by passing the -R option), give these files a .rake extension, and that's it. You don't have to do anything additional. Files with the *.rake extensions are included in the Rakefile automatically for you. Nonstandard extension such as .rake for the files should not scare you. These are the usual Ruby files. There you can write any Ruby code, define their rake tasks, include the related libraries, and so on. So, take this feature as a good thing to refactor a Rake project.

To approve the things said in this section, please open the terminal and check the following example:

$ mkdir rakelib
$ cat > rakelib/clean.rake
task :clean do
  puts 'Cleaning...'
$ cat > Rakefile
task :default => :clean
$ rake

In this example, ^D is a keyboard shortcut: Ctrl + D. The cat utility writes the standard output to the files here.

Using the import method to load other Rakefiles

It's possible to include other Ruby files or Rakefiles to describe a current Rakefile. It can be achieved by a standard require Ruby statement. However, what do we do when the including files depend on some method or variable defined in the describing Rakefile? To demonstrate the situation, create the following two files in a folder:

  • rakefile

  • dep.rb

The rakefile has some tasks definition, a method definition, and a require statement, as shown in the following code snippet:

require './dep.rb'

def method_from_rakefile
  puts 'it is a rakefile method'

task :a do
  puts 'task a'

task :b do
  puts 'task b'

The dep.rb file just defines a new task that has both the prerequisites tasks, a and b. Also, it calls the defined method, method_from_rakefile(), for some reason, as shown in the following code snippet:


task :c => [:a, :b] do
  puts 'task c'

Trying to run a rake task defined in Rakefile will cause an exception that says that there is no defined method_from_rakefile while the dep.rb file is loading:

$ rake c
rake aborted!
undefined method `method_from_rakefile' for main:Object
~/dep.rb:1:in `<top (required)>'
~/rakefile:1:in `<top (required)>'
(See full trace by running task with --trace)

The exception occurs when the dep.rb file is required by the Rakefile. The problem here is caused because the required file loaded even before the Rakefile could load. One of the possible solutions here is just to move the require statement to the last line of the Rakefile. As a result, the method and tasks required for the dep.rb file will be defined at the time of the dep.rb file being included in the Rakefile. To be honest, the solution seems like a hack; this is the Rake way.

Fortunately, Rake provides us with a tool to resolve this issue—the import method. It does what we really want here; the import statement may be used in any line of the Rakefile, and this doesn't apply to the loading process at all. The imported files will be loaded after the whole Rakefile is loaded. Its usage looks similar to the require statement and is shown in the following line of code:


Here, you are able to pass more than one file.

There is one more feature of the import method. If you pass the filenames to the import task, they are evaluated first, and this allows us to generate the dependent files on the fly. Look at the following Rakefile:

task 'dep.rb' do
  sh %Q{echo "puts 'Hello, from the dep.rb'" > dep.rb}

task :hello => 'dep.rb'

import 'dep.rb'

This example generates the dep.rb file on the file due to the import 'dep.rb' call that evaluates the 'dep.rb' task. The result of the hello task execution is shown as follows:

$ rake hello
echo "puts 'Hello, from the dep.rb'" > dep.rb
Hello, from the dep.rb

It is a really helpful feature that can not only help you in writing the Rake project, but also in a simple Ruby project.

Running rake tasks from other tasks

Sometimes, you will have to execute some defined task from your task manually. For this purpose, you have two methods of the Rake::Task class: execute and invoke. The difference between the two methods is that execute doesn't call dependent tasks, but the invoke method does. Both of these methods also accept arguments that can be passed to the tasks if you need them. Their usage is the same and is shown as follows. The following is the first code:


The following is the second code:


With the Rake::Task['hello'] code, we got the hello rake task. It returns an instance of the Rake::Task class and then, we are able to run any method on this. In the preceding examples, we called invoke and execute.

To get the namespaced task by name, like in the previous example, use a syntax or similar to the following line of code:


One more difference between these methods is that the invoke method can't be executed twice without some trick. If you need to run the task more than once with the invoke method, use the reenable method as shown in the following code snippet:


These capabilities can be used when you need to run some other rake task after a current task has been executed. Look at the following example that depicts how to use it in task actions. It demonstrates the usage of the invoke and reenable methods:

task :clean do
  puts 'cleaning data...'

task :process do
  puts 'processing some data...'

task :process_with_double_clean do
  puts 'processing some data...'

task :process_with_double_clean_and_reenable do
  puts 'processing some data...'


Try to paste this code in a Rakefile and run the process, process_with_double_clean, and process_with_double_clean_and_reenable tasks to find the difference between them. The following code is the output of the executions:

$  rake -f rakefile22 process
processing some data...
cleaning data...
$  rake -f rakefile22 process_with_double_clean
processing some data...
cleaning data...
$  rake -f rakefile22 process_with_double_clean_and_reenable
processing some data...
cleaning data...
cleaning data...

The code conventions of Rake

The words namespace, desc, task, touch, and so on in the Rakefile are general methods and, of course, you are able to pass parentheses when you pass the parameters there, as shown in the following code snippet:

namespace(:server) do
  desc('Restart web server')
  task(:restart) do

However, the code looks quite ugly now, so it's recommended that you avoid using styles such as the one used here. Rake has its own DSL, and if you follow it, the code will be more readable.

The namespace and task methods are the basic methods that accept blocks that make the Rake code very expressive. For the task method, the block in the task definitions is optional, similar to what we saw in the Task dependencies – prerequisites section.

The blocks can be specified with either a do/end pair or with curly braces in Ruby. To specify a Rakefile, it's strongly recommended that you define rake tasks only with do/end. Because the Rakefile idiom tends to leave off parentheses on the tasks definitions, unusual ambiguities can arise when using curly braces. Take a look at the following proposed Rakefile:

def dependent_tasks
  [:task2, :task3]

task :task2 do
  puts 'In task2...'

task :task3 do
  puts 'In task3...'

task :task1 => dependent_tasks {
  puts 'In task1...' # We are expecting this code to be run but it's not

The following is the result of the execution of task1:

$ rake task1
In task2...
In task3...

The defined action in task1 is not evaluated. It leads to unexpected behavior. Because curly braces have a higher precedence than do/end, the block is associated with the dependent_tasks method rather than the task method.

A variant of passing the block after the dependent task name is not valid Ruby code at all, as shown:

require 'rake'
task :task1 => :task2 { }

It might seem strange but unfortunately, this code doesn't work and gives a syntax error as shown:

# => SyntaxError: syntax error, unexpected '{', expecting end-of-input

The conclusion of this is that if you just follow the Rakefile convention, you won't have problems with Rake's unexpected behavior.

Finally, the last tip for Rakefiles description: don't use the new style of a hash definition in the task prerequisites (in other words, don't describe tasks dependencies like this: task1: :task2). Often, only one prerequisite, defined at the first instance, transforms to the list of prerequisites and then you will have to translate the hash definition to the old style (in other words, the task1: :task2 code transforms to :task1 => [:task2, task3]). Usually, all the task definitions contain the hash rocket instead of the colon notation. The conclusion here is simple: use the old style of the creation of Ruby hashes in the rake tasks definitions.



In this chapter, you learned what Rake is, what you have to do to start using Rake, how to use the rake command-line tool, how to write and run rake tasks, how to set their dependencies, and how to structure the code. There was some advice about Rake's DSL and an explanation on why should you follow it. This chapter doesn't demonstrate any real examples of how to use Rake because this knowledge is not enough to work with the files, but please be patient because you will see them in the upcoming chapters.

The next chapter will explain the basics to work with files using Rake. Also, you will see the first real example of put the knowledge of both these chapters to practice.

About the Author
  • Andrey Koleshko

    Andrey Koleshko had his first touch with programming while at school, when he worked on Pascal. He had been solving basic algorithmic tasks. The first programming language he used at the beginning of his career was Java. He worked with this language for a year and then migrated to the Ruby language, which he worked with for the next four years. Of these four years, he worked with Altoros for three. He had an amazing time there, learning the language and technologies deeply. Currently, he works at a local cloud hosting company. The company change provided him with the opportunity to deal with a lot of challenges concerning application architecture, code testing, debugging, and deployment processes. As a result, he has been able to contribute to some famous Ruby libraries. More detailed information about his contributions can be found on GitHub at http://github.com/ka8725. He mostly works with the Rails framework. He openly shares all of his thoughts and his most interesting experiences through his blog at http://railsguides.net. He has recently started to learn the Python programming language. He lives in Minsk, Belarus, and likes to watch and play sports such as soccer, ping-pong, and volleyball. He also likes travelling to tropical countries with his wife. Teaching people gives him immense pleasure.

    Browse publications by this author
Rake Task Management Essentials
Unlock this book and the full library FREE for 7 days
Start now