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:

  1. The target file doesn’t exist.
  2. 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:

  1. The target file doesn’t exist.
  2. One of the prerequisites changed since the last build.
  3. 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?

  1. Instance variables (mostly for internal usage), e.g.:
        @cc = "cc"
    
  2. "var" variables (can be set from commandline, easy synchronization with environment variables), e.g.:
        var :cc => "cc"
    
  3. 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.

  1. 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.

  2. 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 > foo
    

    and on other systems:

         ruby -e "puts ARGV" a\ b c\ d ef > foo
    
  3. 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