{ |one, step, back| } 10 of 13 articles Syndicate: full/short

RedMine For Rake   11 Aug 08
[ print link all ]

The is now a RedMine setup for Rake, FlexMock and Builder.

RedMine

As part of an effort to get better control of changes to the my open source projects, I’ve setup a RedMine issue tracking site for Rake, FlexMock and Builder. You can find it at http://onestepback.org/redmine.


comments

Another Rake Tutorial   12 Jun 07
[ print link all ]

Greg Pollack has written a Rake Tutorial.

A Rake Tutorial from the “Rails Guy”

Remember this (and this, this, and this)?

Gregg Pollack (the “Rails” guy in the above videos) has written a delightful little rake tutuorial that you might enjoy. You can find the tutorial at http://railsenvy.com/2007/6/11/ruby-on-rails-rake-tutorial.


comments

Is This a Bad Idea?   23 Apr 07
[ print link all ]

This idea occured to me as I was reviewing a Rakefile recently.

Ugly

Rakefile task comments are a bit ugly. Since I want Rake to report task descriptions, I can’t use regular Ruby comments (which would be invisible to Rake). Instead, Rake will use the desc command, as in:

desc "This task will do something" 
task :do_something do ... end

An Alternative Syntax

While I was reviewing a Rakefile recently, it occured to me it would be entirely possible to write a task comment like this:

-- "This task will do something" 
task :do_something do ... end

You can think of this as a kind of marriage between Ada comments (using—) and Smalltalk comments (using ’”’).

The Implementation

  class String
    def -@
      desc self
      0
    end
  end

Yep, its that simple.

Fortunately, my better judgement took over at that point and this comment style will not make it into Rake. Unfortunately, it didn’t take over in time to prevent me from blogging about it.


comments

Custom Rake Applications   21 Apr 07
[ print link all ]

It’s odd how ideas come to you. I was working on a script unrelated to Rake, when a different way of using Rake occurred to me.

Start / Stop Script

Today a coworker passed on a shell script for staring and stopping a server on a local box. The script was pretty typical. You would type “server start” and “server stop” to bring the aforementioned server up and down. It looked something like this:

#!/bin/sh

SERVER_DIR=/path/to/server/directory

case $1 in
    start)
        pushd $SERVER_DIR
        ./startServer.sh >out.log &
        popd
    ;;

    debug)
        pushd $SERVER_DIR_
        ./startServerDEBUG.sh >out.log &
        popd
    ;;

    stop)
        pushd $SERVER_DIR
        sh stopServer.sh
        popd
    ;;

  *) echo "invalid" ;;
esac

There were a few more options, but you get the drift. Anyways, I wanted to to customize the script a bit and realized that my shell scripting abilities were a bit rusty. So I translated the whole thing to Ruby …

The Ruby Version

The translation was pretty straight forward. The guts of the script looked like this:

ARGV.each do |arg|
  case arg
  when 'start'
    Dir.chdir(WLS) do
          system "./startWebLogic.sh >out.log &" 
    end

  when 'debug'
    Dir.chdir(WLS) do
          system "./startWebLogicDEBUG.sh >out.log &" 
    end

# [... code elided ...]

and so on.

After about the second or third “when” clause in the “case” statement, I realized that I was essentially writing tasks. And we have a perfectly good Ruby tool for managing task based software: Rake!

So why wasn’t I writing this as a Rake script? One reason is that Rake depends on the presence of a Rakefile in the current (or parent) directories. I needed to be able to run any of my server commands from anywhere in the file system, not just from the directory containing the Rakefile.

Fair enough. But there’s nothing preventing me from writing a ruby script that includes the Rake library. We saw something similar in the FindInCode script earlier. The only additional thing we need to do is include the task definitions directly in the script, and then explicit invoke the tasks we need.

The Initial Rake Version

So I abandoned my first cut at a Ruby version and went with this:

#!/usr/bin/env ruby

require 'rake'

SERVER_DIR = /path/to/server/directory

task :start do
  Dir.chdir(SERVER_DIR) do
    sh "./startServer.sh >out.log &" 
  end
end

task :stop do
  Dir.chdir(SERVER_DIR) do
    sh "./stopServer.sh >out.log &" 
  end
end

# [... more task defintions go here ...]

ARGV.each do |arg| Rake::Task[arg].invoke end

Now I have a version that can handle all the task dependency stuff that Rakefiles do, but can be run from anywhere in the file system. Assuming I named the script “server”, I can say:

server start

and the right code gets run. I can specify dependencies between the tasks in the script (e.g. “task :bounce => [:stop, :start]“), and do all the other great stuff that building on Rake allows.

The Improved Rake Version

There are a couple of downsides to the above script. Even though I can invoke the server script as if it were a rake-like command, it doesn’t do everything that rake does. For example, there is no way to get a list of all the defined tasks (.e.g rake -T). In fact, no rake command line options are support in the above code. Neither are the environment variable parameters (e.g. TEST=test_file_name.rb). Furthermore, the error messages are not trapped and handled the way that Rake does it.

Now, all of the above could be manually added to the server script, but a better way is to slightly refactor Rake to better support customer rake applications. The init and top_level public methods were added to the Rake Application class, so now we can write the above script as:

#!/usr/bin/env ruby

gem 'rake', '>= 0.7.3'
require 'rake'

Rake.application.init('server')

SERVER_DIR = /path/to/server/directory

task :start do
  Dir.chdir(SERVER_DIR) do
    sh "./startServer.sh >out.log &" 
  end
end

task :stop do
  Dir.chdir(SERVER_DIR) do
    sh "./stopServer.sh >out.log &" 
  end
end

# [... more task defintions go here ...]

Rake.application.top_level

All we did was add a call to Rake.application.init to initialize the command line parameter information in the Rake application. You can specify an optional application name to init to allow the Rake software to correctly report the application name when handling a -T command line option.

The second thing we did was change the explicit loop through all the parameters at the end of the script into a single call to top_level. The top_level method not only handles the invocation of the top level tasks, but also is smart enough about the Rake command line options to properly handle them.

One final note about the “gem” command line near the top. The init and top_level methods were introduced in version 0.7.3 of Rake. By using the gem command we can ensure that are using a compatible version of the Rake software.

Summary

When should you use a normal Rakefile, and when should you write a custom Rake application? A Rakefile works best for per-project type of commands, such as those used to build and test your individual projects. You are (almost) always somewhere in the project directory tree when invoking Rake, and so it just does the right thing.

Custom Rake applications work well when the command are not per-project, and you need to run them anywhere in the file system. A custom application gives you the freedom to run the command anywhere and not need a Rakefile first.

Here’s another quick example where I found a custom Rake application to be useful. Lately I have been doing a lot of small Ruby scripts. I have setup TextMate to autorun the tests via rake, but that means before I can test any of my mini-ruby program, I need to take the time to write a Rakefile to run my tests. Normally I would use rake to automate such a repetitive task, but rake isn’t effective until I have a Rakefile, so we’re in a kind of Catch-22 situation. But with a custom Rake application, I can say something like “proj rakefile” ... and have proj do all the heavy lifting for me without writing an explicit Rakefile first.

Ain’t automation grand?


comments

Using Rake as a Library -- Update   31 Aug 06
[ print link all ]

Why Not Find?

Why Not Find?

After posting FindInCode, someone legitimately asks: “Why not just use Find?”

Good question! Find is a Ruby supplied module to recursively search directories. So why not use it instead of Rake. Then your script won’t have a dependency on Rake.

Well, in short:

  1. Rake provides two functions, recursive directory searching and line by line searching (note that egrep is a FileList method provided by Rake, and is not the same as the grep method provide by Ruby arrays). Find only handles the directory search thing.
  2. I almost never use a Ruby installation without Rake installed as well. So the extra dependency is not a concern to me.
  3. Since I use Rake all the time, the syntax is at my fingertips. I don’t use Find often enough to use it without checking RI.
  4. The Find version of the script is several times longer than the Rake version (included below for comparison).

However …

The find version has one advantage over the Rake version in that it does not need to pull in the entire list of files into memory at once. That may be important to you.

Comparisons

Here’s the original version again:

  require 'rake'
  FileList["**/*.rb"].egrep(Regexp.new(ARGV.first))

And here is the Find version:

  require 'find'
  RE = Regexp.new(ARGV[0])
  Find.find('.') do |fn|
    next if fn =~ /(^(\.svn|CVS)$)/
    next unless fn =~ /\.rb$/
    open(fn) do |file|
      lines = 0
      file.each do |line|
        lines += 1
        puts "#{fn}:#{lines}:#{line}" if line =~ RE
      end
    end
  end

Update (31/Aug/06)

James Edward Grey II points out that my find example could use a little makeover. First, the regex that causes .svn directories to be skipped is incorrect, it only skips the actual directory, not the contents of the directory. Removing the ”$” from the match will fix that, but he provides a better solution. Replace that entire line with:

    Find.prune if fn =~ /(^(\.svn|CVS)$)/ 

I totally forgot about prune.

A minor correction is the use of the lines counter. He suggests either using each_with_index (I should have thought of that), or better yet, use file.lineno.

Thanks James!


comments

Using Rake as a Library   26 Aug 06
[ print link all ]

I’ve been exploring using Rake as a Ruby Library.

Find In Code

Lately, I’ve been experimenting with using Rake as a normal library in regular Ruby scripts (rather than in Rakefiles). It turns out there are some very useful things you can do.

For example, to locate a particular piece of code in a not so small Ruby project, I often use the following command line:

    $ find . -name '*.rb' | xargs grep -ni "snippet of code" 

This quickly searches all my Ruby files and displays matching lines with file names and line numbers. However, the line is long to type and won’t work in windows (unless you have cygwin installed).

So I wrote the following Ruby program and saved it in a file called “fic” (Find In Code):

  #!/usr/bin/env ruby
  require 'rake'
  FileList["**/*.rb"].egrep(Regexp.new(ARGV.first))

An Enhanced Version

This proved so useful, that I enhanced the code to allow for searching for files ending in arbitrary extendsions and to handle more matching options on the command line.

Here’s the enhanced version:

  #!/usr/bin/env ruby
  # -*- ruby -*-

  exts = ['.rb']
  if ARGV[0] =~ /^\.[a-zA-Z0-9]+$/
    exts = []
    while ARGV[0] =~ /^\.[a-zA-Z0-9]+$/
      exts << ARGV.shift
    end
  end

  ext = "{" + exts.join(',') + "}" 

  if ARGV.size < 1
    puts "Usage: #{File.basename($0)} [.ext ...] pattern ..." 
    exit 1
  end

  require 'rake'
  FileList["**/*#{ext}"].egrep(Regexp.new(ARGV.join('|')))

And if you are using Emacs …

If you are using Emacs, and you have my “visit source” code loaded, all you need to do is put the cursor on the line you are interested in and press F2.

Oh, you would like to see the elisp code for “visit source”? Let’s see, where did I put that?

  $ cd .elisp/
  $ fic .el jw-visit-source
  ini/ini-cust.el:131:(defun jw-visit-source ()
  ini/ini-zkeys.el:7:(global-set-key [f2] 'jw-visit-source)    

Now, I move the cursor to the first line and press F2, and I find:

  ;;; Source File Visiting =============================================

  (defun jw-current-line ()
    "Return the current line." 
    (let
        ((bol (save-excursion (beginning-of-line)(point)))
         (eol (save-excursion (end-of-line)(point))))
      (buffer-substring bol eol) ))

  (defun jw-extract-file-lines (line)
    "Extract a list of file/line pairs from the given line of text." 
    (let*
        ((unix_fn "[^ \t\n\r\"'([<{]+")
         (dos_fn  "[a-zA-Z]:[^\t\n\r\"'([<{]+")
         (flre (concat "\\(" unix_fn "\\|" dos_fn "\\):\\([0-9]+\\)"))
         (start nil)
         (result nil))
      (while (string-match flre line start)
        (setq start (match-end 0))
        (setq result
              (cons (list 
                     (substring line (match-beginning 1) (match-end 1))
                     (string-to-int (substring line (match-beginning 2) (match-end 2))))
                    result)))
      result))

  (defun jw-select-file-line (candidates)
    "Select a file/line candidate that references an existing file." 
    (cond ((null candidates) nil)
          ((file-readable-p (caar candidates)) (car candidates))
          (t (jw-select-file-line (cdr candidates))) ))

  (defun jw-visit-source ()
    "If the current line contains text like '../src/program.rb:34', visit 
  that file in the other window and position point on that line." 
    (interactive)
    (let* ((line (jw-current-line))
           (candidates (jw-extract-file-lines line))
           (file-line (jw-select-file-line candidates)))
      (cond (file-line
             (find-file-other-window (car file-line))
             (goto-line (cadr file-line)) )
            (t 
             (error "No source location on line.")) )))

comments

Martin Fowler Writes About Rake   11 Aug 05
[ print link all ]

Fowler: After all until we tried it I thought XML would be a good syntax for build files..

Martin Fowler On Rake

Martin Fowler has written a great introduction to using Rake for building systems. The article is gentle and doesn’t assume a deep knowledge of Ruby.

Go read it now. You know you want to.


comments

Problems with Directories   02 Jul 05
[ print link all ]

Someone reported an interesting problem in Rake, and I thought you might enjoy the problem and its resolution.

The Problem

Consider the following Rakefile (I’ve left out some of the uninteresting parts):

task :run

BUILD_DIR = 'build'
TARGET_DIR = 'build/copies'

FileList['src/*'].each do |src|
  directory TARGET_DIR
  target = File.join TARGET_DIR, File.basename(src)
  file target => [src, TARGET_DIR] do
    cp src, target
    sleep 3
  end
  task :run => target
end

Assume the src directory has a lot of files, and that it takes a while to copy them to the build directory (I artificially slowed down the copy by including a sleep command). Also assume for this first run, the build directory has not been created yet.

The first time you run “rake run”, you will see …

  cp src/foo1 build/copies/foo1
  cp src/foo2 build/copies/foo2
  cp src/foo3 build/copies/foo3
  ...

and so one for each of the copies.

Now run “rake run” again. You shouldn’t see any copies because the source files have all been copied at this point and there is no more work to do … but instead you will see (if you are running rake 0.5.3 or earlier) a number of duplicate copies being performed.

Analysis

Why are those extra copies performed? Each target file build/copies/foon^ is dependent upon the source file src/foon^ (so it gets updated when the source changes) and the target directory (so the directory is created by the time the file is copied).

The first time task run is invoked, it populates the target directory with each copy, and in doing so updates the time stamp of the target directory. The next time run is invoked, the earliest target files are out of date with respect to the timestamp on the directory. Rake thinks it needs to update the targets, hence the extra copies.

A Solution

Rake already supports two kinds of tasks. Task objects always run when invoked and are useful for defining simple jobs that need to be performed whenever invoked. FileTasks are different in the they are only invoked if (1) the file they are associated with is does not exist, or (2) the time stamp of any prerequisites are newer than the target file.

What we need for directories is a task that runs when a file needs created but (1) doesn’t trigger on timestamps and (2) returns a timestamp that is earlier than any time stamp of files that depend upon it.

It turns out this is fairly easy in Rake. Tasks define two methods, needed? and timestamp. The first is easy … only return true if the file doesn’t exist.

  def needed?
    ! File.exist?(name)
  end

Handling timestamp was a bit more interesting. What is the earliest possible time stamp? I played around with Time.mktime to find the earliest possible time stamp it could encode, but gave up after a bit. Even if I found it, it would be an implementation dependent issue. I wanted an object that would report it is less than any timestamp.

Class EarlyTime

The first pass at an EarlyTime object was simple:

  class EarlyTime
    include Comparable
    include Singleton

    def <=>(other)
      -1
    end
  end

We made the comparison operator (<=>) always return -1. This means that an object of EarlyTime will claim to be smaller than any other object. The Comparable inclusion makes sure all the comparison operators are properly defined (based on <=>). The Singleton inclusion make sure the is only one copy of the early time (we only need one … really, one of few times I’ve used Singleton).

This works for early_time < time, but how do we handle time < early_time? The Time class doesn’t know about EarlyTime, so it won’t return the right result (in fact it will choke on the value).

We just need to teach Time about the new class:

  class Time
    alias pre_early_time_compare :<=>
    def <=>(other)
      if Rake::EarlyTime === other
        - other.<=>(self)
      else
        pre_early_time_compare(other)
      end
    end     
  end

We create an alias the existing comparison operator <=>. Then we redefine <=> to check for an EarlyTime value. If other is an early time, we redispatch the comparison to the early time value and reverse the sign of the result. If other is not an EarlyTime, then we invoke the old behavior through the alias we created.

Beta Rake

If you want to try the new version of Rake, I’ve uploaded a beta version to my betagems site. You can get it via:

gem install rake --source http://onestepback.org/betagems

Version 0.5.4.3 is the latest beta version. Once I get a little time on it, I’ll make a 0.5.5 release.

Thanks

Thanks to Martin Fowler for pointing out this problem and correctly deducing the reason behind the problem.


comments

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.


comments

Rake Tutorial -- Handling Common Actions   05 Apr 05
[ print link all ]

Rake is a tool for controlling builds. In this part of the Rake tutorial, we see how to organize the Rake actions to apply to many similar tasks.

In the RakeTutorialIntroduction, we talked about the basics of specifying dependencies and associating actions to build the files. We ended up with a nice Rakefile that built our simple C program, but with some duplication in the build rules.

But First, Some Extra Rake Targets

But before we get into all that, lets add some convience targets to our Rakefile. First of all, it would be nice to have a default target that is invoked when we don’t give any explicit task names to rake. The default target looks like this:

   task :default => ["hello"]

Until now, the only kind of task we have seen in Rake are file tasks. File tasks are knowledgable about time stamps on files. A file task will not execute its action unless the file it represents doesn’t exist, or is older than any of its prerequisites.

A non-file task (or just plain “task”) does not represent the creation of a file. Since there is no timestamp for comparison, non-file tasks always execute their actions (if they have any). Since the default task does not represent a file named “default”, we use a regular non-file task for this purpose. Non-file tasks just use the task keyword (instead of the file keyword).

Here are a couple of other really useful tasks that I almost always include in a Rakefile.

clean:

Remove temporary files created during the build process.

clobber:

Remove all files generated during the build process.

clean tidies up the directories and removes any files that generated as part of the build process, but are not the final goal of the build process. For example, the .o files used to link up the final executable hello program would fall in this category. After the executable program is built, the .o files are no longer needed and will be removed by saying “rake clean”.

clobber is like clean, but even more aggressive. “rake clobber” will remove all files that are not part of the original package. It should return a project to the “just checked out of CVS” state. So it removes the final executable program as well as the files removed by clean.

In fact, these tasks are so common, Rake comes with a predefined library that implements clean and clobber.

But every project is different, how do we specify which files are to be cleaned and clobbered on a per project basis?

The answer is File lists.

File Lists to the Rescue

A file list is simply a list of file names. Since a lot of what Rake does involves files and lists of those files, a file list has some special features to make manipulating file names rather easy.

Suppose you want a list of all the C files in your project. You could add this to your rake file:

  SRC = FileList['*.c']

This will collect all the files ending in ”.c” in the top level directory of your project. File lists understand glob patterns (i.e. things like "*.c") and will find all the matching files.

By the way, no matter where you invoke it, rake always executes in the directory where the Rakefile is found. This keeps your path names consistent without depending on the current directory the user interactive shell.

The clean and clobber tasks use file lists to manage the files to remove. So if we want to clean up all the .o files in a project we could try …

  CLEAN = FileList['*.o']

(CLEAN is the file list associated with the clean task. I’ll let you guess the name of the file list associated with clobber).

The Rakefile So Far …

With the addtion of a few extra tasks, our Rakefile now looks like this. Notice the require ‘rake/clean’ line used to enable the clean and clobber tasks.

  require 'rake/clean'

  CLEAN.include('*.o')
  CLOBBER.include('hello')

  task :default => ["hello"]

  file 'main.o' => ["main.c", "greet.h"] do
    sh "cc -c -o main.o main.c" 
  end

  file 'greet.o' => ['greet.c'] do
    sh "cc -c -o greet.o greet.c" 
  end

  file "hello" => ["main.o", "greet.o"] do
    sh "cc -o hello main.o greet.o" 
  end

Ok, now its time to address the redundant compile commands.

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 …

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

Just a couple things to note about the above code.

  • The dependencies are not specified. This is a common where we specify the dependents at one place and the actions in another. Rake is smart enough to combine the dependencies with the actions.
  • Although the task was named after the .o (which is, after all, what we want to generate), the file list is defined in terms of the .c files. Why?

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.

  rule '.o' => '.c' do |t|
    sh "cc -c -o #{t.name} #{t.source}" 
  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.

  require 'rake/clean'

  CLEAN.include('*.o')
  CLOBBER.include('hello')

  task :default => ["hello"]

  SRC = FileList['*.c']
  OBJ = SRC.ext('o')

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

  file "hello" => OBJ do
    sh "cc -o hello #{OBJ}" 
  end

  # File dependencies go here ...
  file 'main.o' => ['main.c', 'greet.h']
  file 'greet.o' => ['greet.c']

Up Next

In our next tutorial, we will look at using Rake to handle some tasks other than compiling C code.


comments

 

Formatted: 19-Nov-08 17:11
Feedback: jim@weirichhouse.org