
== 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 <tt>a.t</tt>.

5. Since no task with the name <tt>a.t</tt> 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 <tt>t.rant</tt>.

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

   After the build, <tt>t.rant</tt> will be read as Rantfile.

7. The action block is done, the action is "deleted".

8. Now, Rant finds a task with the name <tt>a.t</tt>, invokes it and
   executes the associated ruby block.

   Output: "making a.t"

9. Invoke last prerequisite of +default+, which happens to be
   <tt>b.t</tt> 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[link:files/doc/rantfile_rdoc.html]
Support for C/C++::
    doc/c.rdoc[link:files/doc/c_rdoc.html]
Packaging::
    doc/package.rdoc[link:files/doc/package_rdoc.html]
Rant Overview::
    README[link:files/README.html]
