Location: Rake Documents / Tutorial / 03 -- Reducing Duplication with Rules Language: en

Chapter 3—Reducing Duplication with Rules

Dynamically Building Tasks

The command to compile the main.c and greet.c files is identical, except for the name of the files involved. The simpliest and most direct way to address the problem is to define the compile task in a loop. Perhaps something like this …

Rakefile Fragment

1 SRC = FileList['*.c']
2 SRC.each do |fn|
3   obj = fn.sub(/\.[^.]*$/, '.o')
4   file obj  do
5     sh "cc -c -o #{obj} #{fn}"
6   end
7 end

Just a couple things to note about the above code.

The simple reason is that file lists search for file names that exist in the file system. We have no guarantee that the .o files even exist at this point (indeed, the will not after invoking the clean task). The .c are source and will always be there.

Rake Can Automatically Generate Tasks

Defining tasks in a loop is pretty cool, but is really not needed in a number of simple cases. Rake can automatically generate file based tasks according to some simple pattern matching rules.

For example, we can capture the above logic in a single rule … no need to find all the source files and iterate through them.

Rakefile Fragment

1 rule '.o' => '.c' do |t|
2   sh "cc -c -o #{t.name} #{t.source}"
3 end

The above rule says that if you want to generate a file ending in .o, then you if you have a file with the same base name, but ending in .c, then you can generate the .o from the .c.

t.name is the name of the task, and in file based tasks will be the name of the file we are trying to generate. t.source is the name of the source file, i.e. the one that matches the second have of the rule pattern. t.source is only valid in the body of a rule.

Rules are actually much more flexible than you are led to believe here. But that’s an advanced topic that we will save for another day.

Final Rakefile

Here is our final resule. Notice how we use the SRC and OBJ file lists to manage our lists of scource files and object files.

Rakefile

 1 require 'rake/clean'
 2 
 3 CLEAN.include('*.o')
 4 CLOBBER.include('hello')
 5 
 6 task :default => ["hello"]
 7 
 8 SRC = FileList['*.c']
 9 OBJ = SRC.ext('o')
10 
11 rule '.o' => '.c' do |t|
12   sh "cc -c -o #{t.name} #{t.source}"
13 end
14 
15 file "hello" => OBJ do
16   sh "cc -o hello #{OBJ}"
17 end
18   
19 # File dependencies go here ...
20 file 'main.o' => ['main.c', 'greet.h']
21 file 'greet.o' => ['greet.c']