{ |one, step, back| } 1 of 1 article Syndicate: full/short

Rake Tutorial -- Another C Example   28 Apr 05
[ print link all ]

Mark Probert on the Ruby-Talk mailing lists asks: “I am not sure how to create a set of Rake rules to do the following. Can anyone prove assistance?”

I had planned for the next Rake tutorial to go into using prepackaged task libraries, but Mark’s question highlights an interesting problem (and resolution) with rules. I posted an answer to Mark on the list, but why waste a perfectly good explaination when I can recycle it here.

The Problem

Mark has two separate source directories (he calls them src_a and src_b, but I suspect they are more creatively named in real life. Both directories contain .c files. However, the kicker is that all the object files are to be placed in a single directory (named obj) no matter which source directory contained the original C code.

The rule we introduced in the last tutorial isn’t powerful enough to move the .o file into the obj directory. We need to tweek it just a bit.

The .o File Rule

Here is the rule in question.

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

To recap, the rule specifies how to create a .o file from a similarly named .c file. But as noted above, the .c are in a different location. We can fix this by giving the rule a general purpose function that transforms the object file name into the correct source file name.

Finding the Source File.

But how do we find the source file? Assuming we have a constant named SRC that contains a list of all our source files, this simple find command will do the trick (assume objfile is the name of the object file):

SRC.find { |s| File.basename(s, '.c') == File.basename(objfile, '.o') }

Wrapping this in a method (named find_source) gives us a nice way to find the source file.

Tweeking the Rule

We can now write the rule like this…

rule '.o' => lambda { |objfile| find_source(objfile) } do |t|
  sh "cc -c -o #{t.name} #{t.source}" 
end

The Whole Rakefile

Just a couple notes about the Rakefile

  1. Note that we invoke the OBJDIR task directly in the rule. Because it is a rule, there is no opportunity to list OBJDIR as an explicit dependency. By invoking it directly inside the rule, we will build that directory if it is needed (but only if it is needed).
  2. If searching the SRC list has performance problems (because SRC is very long), then an alternative is to create a mapping of object names to source names at the top of the file. Then finding the source name is a simple hash lookup.

Rakefile

require 'rake/clean'

PROG = "foo" 
LIBNAME = PROG
LIBFILE = "lib#{LIBNAME}.a" 

SRC = FileList['**/*.c']

OBJDIR = 'obj'
OBJ = SRC.collect { |fn| File.join(OBJDIR, File.basename(fn).ext('o')) }

CLEAN.include(OBJ, OBJDIR, LIBFILE)
CLOBBER.include(PROG)

task :default => [:build, :run]

task :build => [PROG]

task :run => [PROG] do
  sh "./#{PROG}" 
end

file PROG => [LIBFILE] do
  sh "cc -o #{PROG} -L . -l#{LIBNAME}" 
end

file LIBFILE => OBJ do
  sh "ar cr #{LIBFILE} #{OBJ}" 
  sh "ranlib #{LIBFILE}" 
end

directory OBJDIR

rule '.o' => lambda{ |objfile| find_source(objfile) } do |t|
  Task[OBJDIR].invoke
  sh "cc -c -o #{t.name} #{t.source}" 
end

def find_source(objfile)
  base = File.basename(objfile, '.o')
  SRC.find { |s| File.basename(s, '.c') == base }
end

Alternatives

On possible alternative is to replace the rule with a loop that explicitly creates tasks to compile each .c file. It might look something like this:

SRC.each do |srcfile|
  objfile = File.join(OBJDIR, File.basename(srcfile).ext('o'))
  file objfile => [srcfile, OBJDIR] do
    sh "cc -c -o #{objfile} #{srcfile}" 
  end
end

What I like about this solution is the ability to put the OBJDIR dependency directly in the task definition.


blog comments powered by Disqus

 

Formatted: 10-Mar-10 18:53
Feedback: jim@weirichhouse.org