== 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[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]