How to write an Rantfile

As already mentioned, an Rantfile is a Ruby script. Additionally, Rant provides methods and classes which aid you in automating your work.

Centric to the processing done by Rant is the task. The Rantfile defines tasks and their dependencies, Rant invokes the required tasks.

Also important is the Rant application. When the rant command starts, it instantiates one Rant application which will read one or more Rantfiles. The Rantfile communicates with the following list of methods with the Rant application:

task:Defines a task with prerequisites and an action.
file:A special form of task which creates one file.
desc:Describes the next defined task. A task with description is considered a "public" task.
gen:Takes a generator object as first argument which usually creates one or more tasks. An example would be the RubyPackage generator which produces tasks for packaging.
import:Imports additional code which usually provides additional generators. Example: to use the RubyPackage generator you have to import "rubypackage" first.
sys:Run external commands or do usual file system operations (copy files, unlink files, install, …).
source:Takes a filename as argument and causes Rant to read it in as Rantfile.
subdirs:Takes a list of subdirectories in which Rant should look for Rantfiles.
var:Provides access to variables accessible in Rantfiles and from the commandline.
make:Immediately build a target.

Defining a task

Just call the task function and give it a task name as argument. The task name may be a string or symbol:

    task :taskname

That’s it, your first task. Not very useful, because it doesn’t do anything. To associate an action with the task, add a block:

    task :mytask do
        puts "Hello, mytask running."
    end

Put these 3 lines of code into a file called Rantfile and run rant:

    % rant mytask
    Hello, mytask running.

Note: you could have omited the mytask argument to rant because it is the only task in the Rantfile.

You can add a block parameter which will be a reference to the created task:

    task :mytask do |t|
        puts t.name
    end

Running rant now:

    % rant
    mytask

Add prerequisites to create relations between tasks:

    task :first => [:t1, :t2] do |t|
        puts t.name
    end
    task :t1 do |t|
        puts t.name
    end
    task :t2 do |t|
        puts t.name
    end

In the definition of the "first" task we told Rant that it depends on task "t1" and task "t2". "t1" and "t2" are called prerequisites for "first". Try it out:

    % rant first
    t1
    t2
    first
    % rant t1
    t1
    % rant t2
    t2

Defining a file task

You will notice that rant will run the actions for a normal task always its name is given on the commandline or it is required by another task. Often you want to create a file, e.g. a program by invoking a compiler. In this case, the action needs only to be run if the source files (prerequisites) have changed. Use a file task instead of a normal task.

In this example we use the sys.touch method to test our file task. (This method works the same as the Unix touch command: Update the modification time of a file or create an empty file):

    file "testfile" do |t|
        sys.touch t.name
    end

Now run rant:

    % rant
    touch testfile

This would have been the same with a normal task. But now run rant a second time:

    % rant

This time rant doesn’t run the action, because "testfile" is up to date. Of course you can add prerequisites the same way as for a normal task. Additionally you can add filenames as prerequisites. Assuming the files "a.o" and "b.o" are in the same directory as the Rantfile:

    file "myprog" => %w(a.o b.o) do |t|
        sys %w(cc -o), t.name, t.prerequisites
    end

Running rant:

    % rant
    cc -o myprog a.o b.o

Running a second time:

    % rant

Does nothing, myprog is up to date. Don‘t be irritated by the %w() syntax. It creates a list of strings. The following expressions are equivalent:

    ["a", "b", "c"]
    %w(a b c)

Adding task descriptions

The desc function lets you describe your tasks. A small example Rantfile:

    # Generate C source file ls.c with the xgen command.
    file "ls.c" => %w(ls1.x ls2.x) do |t|
        sys %w(xgen -o), t.name, t.prerequisites
    end

    desc "Build ls program."
    file "ls" => "ls.c" do
        sys "cc -o ls ls.c"
    end

    desc "Remove autogenerated files."
    task :clean do
        sys.rm_f %w(ls.c ls)
    end

(Note that xgen is a hypothetical command ;) The —tasks (or the short form, -T) option of rant shows this descriptions:

    % rant -T
    rant ls     # Build ls program.
    rant clean  # Remove autogenerated files.

Only the tasks which have a description are listed.

The sys method

After using the sys method quite often in the examples, I should explain it a little bit. It can be used in three ways:

  1. File system operations

    The first form is with no arguments. It returns an object on which you can invoke methods for common file system operations:

    Examples are:

         sys.rm ["file1", "file2"]       # remove files
         sys.cp "src", "dest"            # copy from "src" do "dest"
         sys.mkdir "dir"                 # create directory "dir"
    

    For a list of all available methods read doc/sys.rdoc.

  2. Running external commands

    Invoke the sys method with a string as argument to run a shell:

         sys "echo *.c"
    

    will print a list of C files to stdout.

    When given multiple arguments, sys invokes the program named with the first argument giving it the remaining arguments as arguments:

         sys "echo", "*.c"
    

    will print "*.c" to stdout.

    When the external program returns with an exit code other than 0, Rant will abort with an error message. Sometimes this is not desirable. E.g. the diff program, which compares two files, returns 0 if the files are equal, 1 if they have differences and something else if an error occurs. Rant allows you to handle/ignore the exit code of a program yourself. Example:

         task :diff do |t|
             sys "diff -u a/util.c b/util.c > util.diff" do |ps|
                 # ps is an instance of Process::Status
                 case ps.exitstatus
                 when 0:
                     puts "a/util.c and b/util.c are equal"
                 when 1:
                     puts "a/util.c and b/util.c are not equal"
                 else
                     t.fail "diff error"
                 end
             end
         end
    
  3. Selecting files

    To select files with the help of glob patterns use sys with the [] operator:

         file "program" => sys["*.o"]
    

    The task "program" depends on all files ending in ".o".

    For extensive documentation read doc/sys_filelist.rdoc.

Generators

The gen function takes a generator which usually creates one or more tasks for you. The following list of generators is immediately available:

Directory:Create directories.
Task:Define custom task.
Rule:Define a rule (a rule produces tasks on the fly).
Action:Run a block of code immediately. The Action generator is discussed in doc/advanced.rdoc.

The Directory generator

An example usage of the Directory generator would be the backup example shown in the README file:

    file "misc/backup/data" => %w(misc/backup data) do |t|
        sys.cp "data", t.name
    end

    gen Directory, "misc/backup"

Now rant will create the directories "misc" and "backup" on demand. Assuming "misc/backup" doesn’t exist:

    % rant
    mkdir misc
    mkdir misc/backup
    cp data misc/backup/data

The Task generator

The Task generator allows you to determine by hand when your task action needs to be run:

    desc "Install with setup.rb"
    gen Task, :install do |t|
        t.needed { !File.exist? "InstalledFiles" }
        t.act do
            sys.ruby "setup.rb"
        end
    end

The act block of the "install" task will only be run if "InstalledFiles" doesn’t exist. Of course you can add prerequisites like with any other task.

Rules

A Rule allows you to tell Rant how it should build files matching a common pattern, e.g. how to build files ending in ".o". A standard rule usage is to create C object files:

    gen Rule, '.o' => '.c' do |t|
        sys "cc -c -o #{t.name} #{t.source}"
    end

Assuming that we have the C source file util.c in the current directory:

    % rant util.o
    cc -c -o util.o util.c

Because Rant didn’t find a task for util.o, it looked for a matching rule and created a task for util.o.

The first line above could also be written as:

    gen Rule, :o => :c do |t|

The source method of the task object gives us the first dependency. So the following line has the same effect:

        sys "cc -c -o #{t.name} #{t.prerequisites.first}"

You can also refine the rule pattern by using a regular expression. To refine dependency selection give a block as source argument:

    src = lambda { |target| [target.sub_ext("c"), target.sub_ext("h")] }
    gen Rule, /^my_[^.]+\.o$/ => src do |t|
        sys "cc -c -o #{t.name} #{t.source}"
    end

This rule generates a task for files beginning with "my_" and ending in ".o" (like "my_program.o"). The task has a file ending in ".c" and one ending in ".h" as dependencies (like "my_program.c" and "my_program.h") . Since t.source gives us the first dependency, the ".c" file will appear as argument to cc, but not the ".h" file.

The sub_ext method of the String class replaces anything after the last dot with the given string.

Importing additional generators

The import function lets you import additional generators. Currently the following come with Rant:

Clean:Remove selected files.
AutoClean:Remove all files generated by any file task (including those generated by rules).
DirectedRule:A Rule which takes sources from one or more directories and puts generated files into a specified directory.
SubFile:Create file task and necessary directory tasks.
Command:Tasks with command change recognition.
RubyTest:Run Test::Unit tests for your Ruby code.
RubyDoc:Run RDoc.
RubyPackage:Generate tar, zip and gem packages of your Ruby application/library.
Archive::Tgz:Create gzipped tar archives.
Archive::Zip:Create zip archives.
Package::Tgz:Create gzipped tar packages.
Package::Zip:Create zip packages.
C::Dependencies:Determine dependencies between C/C++ source/header files caused by include statements.
Win32::RubyCmdWrapper:Create .cmd wrapper scripts for installation of Ruby scripts on Windows.

Read doc/advanced.rdoc and doc/rubyproject.rdoc for documentation.

The subdirs command

The subdirs command allows you to give Rant a list of subdirectories (relative to the Rantfile) where it should look for additional Rantfiles. The tasks defined in those subdirectories can be referenced from your main Rantfile and vice versa.

A small example: We are assuming the following files:

    myprog/
        Rantfile        # the main Rantfile
        README
        src/
            Rantfile
            main.c
            lib.c
            lib.h

Then the main Rantfile could look like:

    desc "Build myprog."
    file "myprog" => "src/myprog" do
        sys.cp "src/myprog", "myprog"
    end

    desc "Remove compiler products."
    task :clean => "src/clean" do
        sys.rm_f "myprog"
    end

    # Tell Rant to look in src for an Rantfile,
    # we could list more directories here.
    subdirs "src"

And src/Rantfile:

    file "lib.o" => %w(lib.c lib.h) do
        sys "cc -c -o lib.o lib.c"
    end

    file "main.o" => "main.c" do
        sys "cc -c -o main.o main.c"
    end

    file "myprog" => %w(lib.o main.o) do
        sys "cc -o myprog main.o lib.o"
    end

    task :clean do
        sys.rm_f Dir["*.o"] + %w(myprog)
    end

Note that we refer to the task in subdirectory simply by prepending the directory name and a slash to the task name.

    file "myprog" => "src/myprog" do

This tells Rant that the "myprog" task depends on the "myprog" task defined in the Rantfile in the "src" directory. The same goes for the "clean" task.

Let’s examine what Rant thinks about our build specification:

    myprog % rant -T
    rant myprog   # Build myprog.
    rant clean    # Remove compiler products.

Since the "myprog" task is the first in our Rantfile, we don’t need to specify a task to build myprog:

    myprog % rant
    cc -c -o lib.o lib.c
    cc -c -o main.o main.c
    cc -o myprog main.o lib.o
    cp src/myprog myprog

And to save disk space, remove generated files:

    myprog % rant clean
    rm -f lib.o main.o myprog
    rm -f myprog

The nice thing about our buildfiles is, that we can use the src/Rantfile independent from our main Rantfile. Consider you are working on lib.c and only want to check that it compiles correctly. You can specify the task explicitely:

    myprog % rant src/lib.o
    cc -c -o lib.o lib.c

But if you are working a long time only in the src/ directory it is convinient to do:

    myprog % cd src
    myprog/src % rant lib.o
    cc -c -o lib.o lib.c

To clean only the src directory:

    myprog/src % rant clean
    rm -f lib.o main.o myprog

You can find this example project in the doc/examples/myprog directory of the Rant distribution.

Of course you can also have a subdirectory in src/ containing an Rantfile. Simply put a subdirs command into the src/Rantfile. Rant integrates well into your file system :)

If you want to refer to a task in the main Rantfile from a subdir Rantfile, put a @ in front of the task name:

    task "a" => "@b"

But note that this Rantfile won’t work as a standalone buildfile, because it refers to a main Rantfile.

For further documentation about managing build files in project subdirectories, read doc/subdirs.rdoc.

See also

Advanced Rantfiles:doc/advanced.rdoc
Support for C/C++:doc/c.rdoc
Packaging:doc/package.rdoc
Ruby project howto:doc/rubyproject.rdoc
Using filelists in Rantfiles:doc/sys_filelist.rdoc
Rant Overview:README