== 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.
+plugin+:: Instantiate a plugin. Example: plugin
:Configure instantiates the Configure plugin.
+sys+:: Run external commands or call portable file system
manipulation methods (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.
+rac+:: The "Rant compiler" which is compiling the Rantfiles.
=== 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
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+ function
After using the +sys+ function quite often in the examples, I should
explain it a little bit. The +sys+ function 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 the methods of the +FileUtils+ module that comes with ruby.
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 invoke ri:
% ri FileUtils
which will also show documentation for them.
Additionally you have the following methos which are not in the
FileUtils module:
sys.ruby "arg1", "arg2", ... # invoke the ruby interpreter
sys.safe_ln "src", "dest" # create a hardlink or fall back to
# copying
2. Running external commands
Invoke the +sys+ function 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.
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". Rant uses
the Dir::glob method internally to resolve patterns, so you can
read the ri docs to get an overview:
ri Dir::glob
From now on we'll call sys[...] the glob
operator.
You can give more patterns:
c_files = sys["**/*.h", "**/*.c"]
gives a list of all files ending in ".h" or ".c" in the current
directory and all subdirectories.
The object returned by the glob operator _behaves_ like a list of
strings, so it is possible to pass it to methods expecting an array.
If you're getting errors or experience strange behaviour convert
the list explicetely to an array:
sys.touch c_files.to_a
=== 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[link:files/doc/advanced_rdoc.html].
=== 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 are coming 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.
RubyTest:: Run Test::Unit tests for your Ruby code.
RubyDoc:: Run RDoc.
RubyPackage:: Generate tar, zip and gem packages of your Ruby
application/library.
Win32::RubyCmdWrapper:: Create .cmd wrapper scripts for installation
of Ruby scripts on Windows.
Read doc/advanced.rdoc[link:files/doc/advanced_rdoc.html] and
doc/rubyproject.rdoc[link:files/doc/rubyproject_rdoc.html] 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.
== See also
Advanced Rantfiles::
doc/advanced.rdoc[link:files/doc/advanced_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]
Ruby project howto::
doc/rubyproject.rdoc[link:files/doc/rubyproject_rdoc.html]
Rant Overview::
README[link:files/README.html]