Advanced Rantfiles

Sharing variables

The var command allows you to share variables between Rantfiles and to set variables from the commandline. Just use var like a hash to set/get variables:

    var[:manifest] = %w(README Rantfile myprog.rb lib)

In this example, :manifest is the variable name, which has to be a string or symbol. Symbols as variable names are converted to strings. Now you can access the "manifest" variable in every Rantfile of your project:

    file "MANIFEST" do |t|
        open(t.name, "w") { |f|
            var[:manifest].each { |str| f.puts(str) }
        }
    end

Arguments of the form VAR=VAL to the rant command are also available through var:

    % cat Rantfile
    task :show_test do
        puts var[:test]
    end
    % rant
    nil
    % rant test=hello
    hello

For some variables it is necessary to make them available for subprocesses, like CC or CFLAGS:

    var.env %w(CC CFLAGS)

The variables CC and CFLAGS are available through var as always and are mapped to environment variables.

Cleaning up generated files

Use the Clean generator in your Rantfiles:

    import "clean"

    file "junk" do
       # create junk
    end

    # create a task called clean
    desc "cleanup generated files"
    gen Clean

    # var[:clean] is a filelist object now
    var[:clean] << "junk"
    var[:clean].include "**/*.bak", "**/*.obj"

Let Rant cleanup for you

Use the AutoClean generator which will remove all files generated by any filetask (including those created by rules):

    import "autoclean"

    file "junk" do
        # create junk
    end

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

    desc "Cleanup generated files."
    gen AutoClean, :clean

    # The clean task automatically detects which files where created
    # by our rule and the junk task.
    # Additionally we can add files to remove to the variable with the
    # same name as the AutoClean taskname (here: clean):
    var[:clean].include "**/*.bak"
TAKE CARE:AutoClean will recursively remove directories for which a task exists. Meaning:
   gen Directory, "doc/html"

AutoClean will recursively remove the doc directory!

The Directory generator takes an optional base directory as first argument. Example:

    gen Directory, "doc", "html"

Now Rant assumes that the "doc" directory already exists and creates only a task for the "doc/html" directory. If you run AutoClean now, it will only remove the "html" directory.

The same goes for the SubFile generator:

    gen SubFile, "doc/html/index.html" do |t|
        # do something
    end

This creates (amongst the other two tasks) a task for the "doc" directory, thus AutoClean will recursively unlink the "doc" directory.

    gen SubFile, "doc", "html/index.html" do |t|
        # do something
    end

This doesn’t create a task for the "doc" directory, thus AutoClean will only unlink the "doc/html" directory.

The DirectedRule generator

A directed rule is some sort of special rule. It searches for source files in one or more given directories and produces file in one output directory.

    import "directedrule"

    ro = gen DirectedRule, "obj" => sys["src_*"], :o => :c do |t|
        sys "cc -c -o #{t.name} #{t.source}"
    end

This rule produces a file task for targets in the obj/ directory ending in `.o’. It looks for a source file in all directories starting with `src_’ and files ending in `.c’.

Practically, this means that it compiles the C files in src_x/, src_y, … to object files which are placed in the obj/ directory.

Look in the doc/examples/directedrule directory of the Rant distribution for a small example project.

The SubFile generator

A SubFile is just a shortcut for the combination of a Directory and a file task. The following example without the SubFile generator:

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

can be directly translated to:

    gen SubFile, "backup/data" => "data" do |t|
        sys.cp t.source, t.name
    end

The SubFile generator automatically creates all necessary directory tasks and adds them as prerequisites to the final file task.

Constraining variables

Rant allows you to constrain variables which are managed by the var command (and thus can be set from the commandline):

    import "var/numbers"
    var :count, 0..10

This initializes the variable count to 0 and restricts it to the integer range 0 to 10. Create a task to test it:

    task :show_count do
        puts var[:count]
    end

And now try to set the count variable from the commandline:

    % rant
    0
    % rant count=5
    5
    % rant count=-1
    rant: [ERROR] in file `/home/stefan/tmp/Rantfile', line 2:
                  "-1" doesn't match constraint: integer 0..10
    rant aborted!
    % rant count=100
    rant: [ERROR] in file `/home/stefan/tmp/Rantfile', line 2:
                  "100" doesn't match constraint: integer 0..10
    rant aborted!

Other available constraints:

    import "var/strings"
    # variable str is ensured to be a string
    var :str, :String

    import "var/booleans"
    # variable b is a bool (always true or false)
    # can be set to "yes", "no", "1", "0", "true", "false"
    var :b, :Bool

The Action generator

Consider a C project. In some C source file, let’s say config.h you define the project version, e.g.:

    #define VERSION 2.3

Many of your tools use this version number, so you have decided to duplicate it in a file called version, which contains just a line with the program version. One solution to automate the version file creation would be to write a file task:

    file "version" => "config.h" do |t|
        puts "updating version file"
        open("w", t.name) { |f| f.puts(extract_config_version()) }
    end

and make all other tasks that need this version dependent on it. But this can get very tedious if you have many tasks that need this version file. Another solution is to just run the task every time the Rantfile is sourced. This can be achieved by replacing the file command with the make command:

    make "version" => "config.h" do |t|
        puts "updating version file"
        open("w", t.name) { |f| f.puts(extract_config_version()) }
    end

This creates a file task like before and tells rant to immediately invoke all tasks that are required to build the version file. But imagine your users just want to see the list of available tasks:

    % rant --tasks
    updating version file
    rant foo            # build foo program
    rant lib            # build libfoo.so
    rant clean          # remove generated files

Hmm, we really didn’t need the version to show our users the available tasks. To avoid this, wrap such code in an Action:

    gen Action do
        make "version" => "config.h" do |t|
            puts "updating version file"
            open("w", t.name) { |f| f.puts(extract_config_version()) }
        end
    end

And now on a clean source base:

    % rant --tasks
    rant foo            # build foo program
    rant lib            # build libfoo.so
    rant clean          # remove generated files

OK. Didn‘t confuse our users! Run any task:

    % rant foo
    updating version file
    cc -o foo foo.c

This means, Action blocks are executed whenever we actually want to build something, not just extract information from our Rantfile. It is recommended to wrap any code that has effects on the environment (mainly the file system) inside an Action block instead of embedding it plain in the Rantfile.

An Action block also won’t be run when our Rantfile is read by rant-import.

More selective actions

If a regular expression is given as argument to the Action generator, the block will be executed the first time Rant searches for a task matching this regular expression.

An artifical example:

    import "sys/more"

    gen Action, /\.t$/ do
        puts "executing action for files/tasks ending in `.t'"
        source "t.rant"
    end

    file "t.rant" do |t|
        sys.write_to_file t.name, <<-EOF
            task "a.t" do
                puts "making a.t"
            end
            task "b.t" do
                puts "making b.t"
            end
        EOF
    end

    task :a do
        puts "making a"
    end

    task :default => ["a", "a.t", "b.t"]

Running rant:

    % rant
    making a
    executing action for files/tasks ending in `.t'
    writing 128 bytes to file `t.rant'
    making a.t
    making b.t

rant performed the following steps:

  1. Invoke task default
  2. Invoke first prerequisite of default, which happens to be a.
  3. Execute action block of task a.

    Output: "making a"

  4. Search for a task for the second prerequisite of default, which happens to be a.t.
  5. Since no task with the name a.t exists, it checks for a matching action/rule. The only defined action matches, so the action block is executed.

    Output: "executing action for files/tasks ending in `.t’"

  6. The action block contains a source statement, which will first cause a build of t.rant.

    Output: "writing 128 bytes to file `t.rant’"

    After the build, t.rant will be read as Rantfile.

  7. The action block is done, the action is "deleted".
  8. Now, Rant finds a task with the name a.t, invokes it and executes the associated ruby block.

    Output: "making a.t"

  9. Invoke last prerequisite of default, which happens to be b.t and execute the associated ruby block.

    Output: "making b.t"

Generally, an action is usable to autogenerate a (bigger) set of tasks that are needed by a subset of other tasks.

See also

Rantfile basics:doc/rantfile.rdoc
Support for C/C++:doc/c.rdoc
Packaging:doc/package.rdoc
Rant Overview:README