Command change recognition
A Command task is similar to a file task: It is intended to create on file which may depend on other files. But instead of creating the file by executing a block of Ruby code, the Command task creates the target file by executing a shell command.
A file task rebuilds the target file if at least one of the following two conditions is met:
- The target file doesn’t exist.
- One of the prerequisites changed since the last build.
A Command task rebuilds the target file if at least one of the following three conditions is met:
- The target file doesn’t exist.
- One of the prerequisites changed since the last build.
- The command to create the target file changed since the last build.
General usage
Consider the following Rantfile for rant 0.4.6:
var :CFLAGS => "-g -O2" # can be overriden from commandline
file "foo" => ["foo.o", "util.o"] do |t|
sys "cc -o #{t.name} #{var :CFLAGS} #{t.prerequisites.join(' ')}"
end
gen Rule, ".o" => ".c" do |t|
sys "cc -c -o #{t.name} #{var :CFLAGS} #{t.source}"
end
The problem with this buildfile is, that it won’t recognize a change of CFLAGS, i.e. foo.o, util.o and foo should get rebuilt whenever CFLAGS changes.
Since Rant 0.4.8, it is possible to do the following:
import "command"
var :CFLAGS => "-g -O2" # can be overriden from commandline
gen Command, "foo" => ["foo.o", "util.o"] do |t|
# notice: we're not calling the sys method, we
# are returning a string from the block
"cc -o #{t.name} #{var :CFLAGS} #{t.prerequisites.join(' ')}"
end
# if the block to Rule takes two arguments,
# it is expected to return a task
gen Rule, ".o" => ".c" do |target, sources|
gen Command, target => sources do |t|
"cc -c -o #{t.name} #{var :CFLAGS} #{t.source}"
end
end
Now, whenever the command to build foo or a *.o file changes, it will be rebuilt.
There is also a more concise syntax:
import "command"
var :CFLAGS => "-g -O2"
# first argument (string) is the task/file name, second
# argument (string, array or filelist) is a list of
# prerequisites (notice: no `target => prereqs' syntax!)
# third argument is a command string, which will be
# executed by a subshell.
gen Command, "foo", ["foo.o", "util.o"],
'cc -o $(>) $[CFLAGS] $(<)'
gen Rule, ".o" => ".c" do |target, sources|
gen Command, target, sources,
'cc -c -o $(>) $[CFLAGS] $(-)'
end
For the last syntax:
Interpolation of variables into command strings:
Which variables are interpolated?
- Instance variables (mostly for internal usage), e.g.:
@cc = "cc" - "var" variables (can be set from commandline, easy
synchronization with environment variables), e.g.:
var :cc => "cc" - Special, task specific variables
"name" (symbol equivalent ">"): task name "prerequisites" (symbol equivalent "<"): all prerequisites seperated by spaces "source" (symbol equivalent "-"): first prerequisite more task specific variables might get added later
Syntax of variable interpolation
Variable names must consist only of "word characters", i.e. matching \w in Ruby regexes.
- Plain interpolation. Example command:
"echo $[ARGS]"The contents of variable ARGS (either @ARGS or var[:ARGS]) are converted to a string and interpolated. If the variable contains an array, ARGS.join(’ ’) is interpolated.
- Escaped interpolation. Example command:
"echo ${ARGS}"Like plain interpolation, but spaces will be escaped (system dependent). Consider this Rantfile:
import "command" @args = ["a b", "c d", "ef"] @sh_puts = "ruby -e \"puts ARGV\"" gen Command, "foo", '$[sh_puts] ${args} > $(>)'Running rant will give on Windows:
ruby -e "puts ARGV" "a b" "c d" ef > fooand on other systems:
ruby -e "puts ARGV" a\ b c\ d ef > foo - Path interpolation. Example command:
"echo $(ARGS)"Like escaped interpolation, but additionally, forward slashes (as used for filenames in Rantfiles) will be replaced with backslashes on Windows.
More on semantics of variable interpolation
Interpolation is recursive, except for special target variables.
There is a small semantic difference between the verbose special target variables (name prerequisites source) and the symbolic ones (< > -): The symbolic ones are interpolated after checking if the command has changed since the last build, the verbose forms are interpolated before.
Consider this (artifical, using the Unix tool "cat") example: Rantfile (symbolic special target vars):
import "command"
@src = ["src1", "src2"]
@src = var[:SRC].split if var[:SRC]
gen Command, "foo", @src, 'cat $(<) > $(>)'
% echo a > src1
% echo b > src2
% echo b > src3
% rant
cat src1 src2 > foo
"foo" didn’t exist, so it was built anyway. Now let us change the prerequisite list:
% rant "SRC=src1 src3"
won’t cause a rebuild of foo. Dependencies of foo changed from ["src1", "src2"] to ["src1", "src3"] but since src2 and src3 have the same content and $(<) isn’t expanded for command change recognition, rant considers foo up to date.
Now change Rantfile to (verbose special target vars):
import "command"
@src = ["src1", "src2"]
@src = var[:SRC].split if var[:SRC]
gen Command, "foo", @src, 'cat $(prerequisites) > $(name)'
Starting from scratch:
% echo a > src1
% echo b > src2
% echo b > src3
% rant
cat src1 src2 > foo
% rant "SRC=src1 src3"
cat src1 src3 > foo
This time, Rant expanded $(prerequisites) for command change recognition, and since the prerequsite list changed, it caused a rebuild.
See also
If you want more details, look in the test/import/command directory of the Rant distribution.
| Using MD5 checksums instead of file modification times: | doc/md5.rdoc |
| Advanced Rantfiles: | doc/advanced.rdoc |
| Support for C/C++: | doc/c.rdoc |
| Packaging: | doc/package.rdoc |
| Ruby project howto: | doc/rubyproject.rdoc |
| Rant Overview: | README |