Chapter 13. Dependencies, Rules and Actions

Build Commands

There are several methods to specify build commands to update a target:

  1. A dependency

    This is more or less the same as how this is used in a Makefile: One or more targets, a colon and any number of sources. This specifies that the target(s) depends on the source(s). When build commands are given these are the commands to build the target(s) from the source(s). Without build commands the dependency is only used to check if the target is outdated and needs to be build.

  2. A rule

    Specified with a :rule command. A "%" in the target(s) and source(s) stands for any string. This is used to specify a dependency that is to be used for files that match the pattern.

  3. An action

    Specified with a :action command. Unlike dependencies and rules an action does not specify a build dependency. It must be invoked by other build commands with the :do command.

Nearly all recipe commands can be used in the build commands. But these are not allowed, they can only be used at the recipe level:

a dependency specification
:rule
:route
:totype
:clearrules
:delrule
:program
:dll
:lib
:recipe
:variant

In short: all commands that define dependencies cannot be used in build commands. But don't forget you can use :execute to do just about anything.

The Production Commands

The commands :program, :lib, :dll and :ltlib are called production commands because they explicitly state what things Aap should produce and what sources are involved. Everything the production commands can do, can be done by hand with dependencies as well, but the automation the production commands provide is quite useful. This section discusses how the production commands can be used and the variables that affect them.

The form of each of the production commands is :command targets : sources. It is unusual to have more than one target, since both targets would be built from the same sources, but it is allowed. The list of sources should list the actual, original sources, i.e. only files that are actually written by the programmer and that exist on disk. It is these sources that will be packaged together for distributing the program or library in source form.

Each production command transforms all of the sources into objects using compile actions. The sources are transformed into object files of a particular type — e.g. libraries use files with type "libobject". Once all of the sources have been compiled, a build action is invoked to turn the object files into the target. The table below lists the production commands and the actions used.

Some of the production commands can use different programs to produce the final product, depending on settings in the recipe. In particular, you may need to chose to link a program with the compiler or through libtool, depending on whether your program links to any libtool libraries or not. The alternatives are listed in the table below as well. To select an alternative form to build the final product, set the filetype of the target to a specific value, e.g. 

    :program myProgram { filetype=ltprogram } : source.c

This example uses the ltprogram alternative build command to build the program "myProgram."

CommandObject TypeBuild CommandBuild Alternatives
:programobjectbuild
(normal)

Uses the C compiler to link all the objects into a program. Uses $LIBS and $LDFLAGS.

ltprogram

Uses libtool to link all the objects into a program. Uses $LIBS and $LDFLAGS, but also adds $LTLIBS and $LT_RPATH if defined.

:liblibobjectbuildlib (normal) Uses the ar utility to link all the objects into a static library. Uses $ARFLAGS.
:dlldllobjectbuilddll (normal) Uses the C compiler to link the objects into a dynamic (shared) library. The object files are different from regular library objects, and use a different extension. Uses $SHLINK, and $LDFLAGS, as well as $SHLINKFLAGS.
:ltlibltobjectbuildltlib(normal) Uses the libtool utility to link the objects together. Uses $LDFLAGS.

In case you do want to have Aap figure out how to turn source files in to objects and then combine them into a target, but the target is not one of the types mentioned above, you can use the :produce command.

Attributes for the Production Commands

The production commands understand a wide variety of attributes. Let us return to the generic form of a production command:

    :command targets : sources

There are four places attributes can be inserted in this command, as follows (we have split the command across several lines for clarity):

1   :command { command-attributes }
2       targets { per-target-attributes } : 
3       { source-global-attributes }
4       sources { per-source-attributes } 

There is one commonly-used command-attribute: installvar. The production commands add their targets to the variable named in this attribute. This defaults to the "normal" variable, as listed in Table 17.2, “Settings for the install target”. Assigning an empty value, through { installvar = }, prevents a target from being installed at all. This is useful for internal helper programs and libraries used during the build process.

[Note]Note
You should use { installvar = INSTALL_LTLIB } for program targets that have filetype ltprogram, since they need to be installed as if they are libtool libraries, not programs.
[Warning]Warning
It is a bad idea (excepting ltprograms, which belong in INSTALL_LTLIB) to add targets to the wrong install variable, since the install action that gets called for it will be wrong then as well.

A slightly less-used command-attribute is objecttype, which changes the object file type from the default, (dllobject, for instance, for shared libraries), to something else. For programs that need to be linked by libtool, you may also want to force the object files used in the program to be compiled with libtool, since mixing non-libtool objects and libtool libraries can cause problems. For this, use the { objecttype = ltobject } as well, so that libtool programs will usually have build commands like:

:program { filetype = ltprogram } { objecttype = ltobject } ...

The attributes assigned in the per-target-attributes are used in the build and install actions of the target. Typical attributes assigned here are installdir and keepdir. Variables that affect the build step can be assigned too, such as var_LIBS and var_LDFLAGS.

The attributes for sources are used for the compile steps of the build process, and useful attributes here are var_INCLUDE (if one source file needs special include files) and filetype. The attributes in the source-global-attributes position apply to all the sources in the list, and per-source-attributes apply only to the source file immediately preceding the attribute.

An example that uses all of these settings is:

1 :ltlib { installvar = } conduit_knotes.la
2     { add_LIBS = -lkdeui } :
3     { add_INCLUDE = -I$BDIR/knotes }
4     knotes/KNotesIface.h { filetype=stub } { var_LTOBJSUF=_stub.lo }
5     knotes/knotes-factory.cc

Here we see a libtool library that is not installed (line 1), which must be linked with an additional library (line 2). All of the sources are compiled with an extra include directory (line 3). The first source file (line 4) has additional complications and uses a different compile action due to its filetype. The last source file (line 5) is compiled with normal flags extended only by the source-global-attribute on line 3.

Rules And Dependencies

When a target is to be build Aap first searches for an explicit dependency with build commands that produces the target. This dependency may come from a high level build command such as :program. When such a dependency is not found then the rules defined with :rule are checked:

  1. All the matching rules without commands are used, but only if the source already exists. Thus this cannot be used to depend on a file that is still to be created.

  2. One rule with commands will be selected, in this order of preference:

    A rule for which the sources exist.
    A rule for which one of the sources does not exist and was not defined with the {sourceexists} option.

    If there are multiple matches, the rule with the longest pattern is used. Thus if you have these two rules:

            :rule test/%.html : test/%.in
                  :do something
            :rule %.html : %.in
                  :do something-else
    

    The first one will be used for a file "test/foo.html", the second one for a file "./foo.html". If there are two with an equally long pattern, this is an error.

TRICK: When the source and target in a rule are equal, it is skipped. This avoids that a rule like this becomes cyclic:

        :rule %.jpg : path/%.jpg
                :copy $source $target

Command block sections

Sometimes it is useful to execute commands when a target does NOT require updating. For example, to give a message. And sometimes commands need to be executed no matter if the target is outdated. For example to add an attribute to the target.

You can add sections to build commands of rules and dependencies. Three kinds of sections are possible:

>always - always executed.
>build - executed when building the target.
>nobuild - executed when NOT building the target.

The sections can be used in arbitrary order and may appear multiple times. All section headers must have the same amount of indent. The commands in the sections must have more indent than the section headers.

Example:

        foo.out : foo.in
            >always
                # Always attach an attribute to the target
                :attr {output = yes} $target
            >build
                # Only when $target requires updating
                  :copy $source $target
            >nobuild
                # Only when $target does not require updating
               :print $target is up-to-date

Multiple targets

When a dependency with build commands has more than one target, this means that the build commands will produce all these targets. This makes it possible to specify build commands that produce several files at the same time. Here is an example that compiles a file and at the same time produces a documentation file:

   foo.o foo.html : foo.src
        :sys srcit $source -o $(target[0]) --html $(target[1])

People used to "make" must be careful, they might expect the build commands to be executed once for each target. Aap doesn't work that way, because the above example would be impossible. To run commands on each target this must be explicitly specified. Example:

   dir1 dir2 dir3 :
        @for item in target_list:
             :mkdir $item

The variable "target_list" is a Python list of the target items. Another such variable is "source_list", it is the list of source files (this excludes virtual items; "depend_list" also has the virtual items). An extreme example of executing build commands for each combination of sources and targets:

   $OutputFiles : $InputFiles
        @for trg in target_list:
            :print start of file  >! $trg
            @for src in source_list:
                :sys foofilter -D$trg $src >> $trg

When multiple targets are used and there are no build commands, this works as if each target depends on the list of sources. Thus this dependency:

        t1 t2 : s1 s2 s3

Is equivalent to:

        t1 : s1 s2 s3
        t2 : s1 s2 s3

Thus when t1 is outdated to s1, s2 or s3, this has no consequence for t2.

Automatic dependency checking

When a source file includes other files, the targets that depend on the source file also depend on the included files. Thus when "foo.c" includes "foo.h" and "foo.h" is changed, the build commands to produce "foo.o" from "foo.c" must be executed, even though "foo.c" itself didn't change.

Aap detects these implied dependencies automatically for the types it knows about. Currently that is C and C++. Either by using gcc or a Python function the "#include" statements are found in the source code and turned into a dependency without build commands.

This works recursively. Thus when "foo.c" includes "foo.h" and "foo.h" includes "common.h", the dependency will look like this:

        foo.c : foo.h common.h

For other types of files than C and C++ you can add your own dependency checker. For example, this is how to define a checker for the "tt" filetype:

      :action depend tt
          :sys tt_checker $source > $target

The "tt_checker" command reads the file "$source" and writes a dependency line in the file "$target". This is a dependency like it is used in a recipe. In a Makefile this has the same syntax, thus tools that produce dependencies for "make" will work. Here is an example:

   foo.o : foo.tt  foo.hh  include/common.hh

This is interpreted as a dependency on "foo.hh" and "include/common.hh". Note that "foo.o" and "foo.tt" are ignored. Tools designed for "make" produce these but they are irrelevant for Aap.

Since the build commands for ":action depend" are ordinary build commands, you can use Python commands, system commands or a mix of both to do the dependency checking.

More about customizing dependency checking in Chapter 29, Customizing Automatic Depedencies.

Attributes Overruling Variables

Most variables like $CFLAGS and $BDIR are used for all source files. Sometimes it is useful to use a different value for a group of files. This is done with an attribute that starts with "var_". What follows is the name of the variable to be overruled. Thus attribute "var_XYZ" overrules variable "XYZ".

The overruling is done for:

dependencies
rules
actions

The attributes of all the sources are used. In case the same attribute is used twice, the last one wins.

Another method is to use an "add_" attribute. This works like "var_", but instead of overruling the variable value it is appended. This is useful for variables that are a list of items, such as $DEFINE. Example:

              :attr thefile.c {add_DEFINE = -DEXTRA=yes}

The value of the attribute is only appended when it does not appear yet, to avoid adding it two or more times.

Another method is to define a scope name. This scope is then used to find variables before searching other scopes, but after using the local scope. For example, to specify that the "s_opt" scope is to be used when compiling "filter.c":

              OPTIMIZE = 0
              DEBUG = yes
              :program myprog : main.c filter.c version.c
              :attr {scope = s_opt} filter.c
              s_opt.OPTIMIZE = 4
              s_opt.DEBUG = no

Note that you can set the values of the variables in the user scope after adding the scope attribute to "filter.c".

Virtual Targets

A virtual target is a target that is not an actual file. A Virtual target is used to trigger build commands without creating a file with the name of the target. Common virtual targets are "clean", "all", "publish", etc.

When a target is virtual it is always built. Aap does not remember if it was already done a previous time. However, it is only build once for an invocation of Aap. Example:

        clean:
                :del {r}{f} temp/*

To remember the signatures for a virtual target use the "remember" attribute:

        version {virtual}{remember} : version.txt.in
                :print $Version | :cat - $source >! version.txt

Now "aap version" will only execute the :print command if version.txt.in has changed since the last time this was done.

Using {remember} for one of the known virtual targets (e.g., "all" or "fetch") is unusual, except for "publish".

When using {remember} for a virtual target without a dependency, it will only be built once. This can be used to remember the date of the first invocation.

        all: firsttime
        firsttime {virtual}{remember}:
                :print First build on $DATESTR > firstbuild.txt

The difference with a direct dependency on "firstbuild.txt" is that when this file is deleted, it won't be built again.

Source Path

The sources for a dependency are searched for in the directories specified with $SRCPATH. The default is ". $BDIR", which means that the sources are searched for in the current directory and in the build directory. The current directory usually is the directory in which the recipe is located, but a :cd command may change this.

The "srcpath" attribute overrules using $SRCPATH for an item. Example:

        :attr bar.c {srcpath = ~/src/lib}

To avoid using $SRCPATH for a source, so that it is only found in the current directory, make the "srcpath" attribute empty:

        foo.o : src/foo.c {srcpath=}

When setting $SRCPATH to include the value of other variables, you may want to use "$=", so that the value of the variable is not expanded right away but when $SRCPATH is used. This is especially important when appending to $SRCPATH before a :variant command, since it changes $BDIR. Example:

        SRCPATH $+= include

Warning: Using the search path means that the first encountered file will be used. When old files are lying around the wrong file may be picked up. Use the full path to avoid this.

Depending On A Directory

When a target depends on the existence of a directory, it can be specified this way:

        foodir/foo : foodir {directory}
                :print >$target this is foo

The directory will be created if it doesn't exist. The normal mode will be used (0777 with umask applied). When a different mode is required specify it with an octal value: {directory = 0700}. The number must start with a zero.

Build Command Signature

A special kind of signature is used to check if the build commands have changed. An example:

        foo.o : {buildcheck = $CFLAGS} foo.c
                :sys $CC $CFLAGS -c $source -o $target

This defines a check for the value of $CFLAGS. When this value changes, the target is considered outdated. When something else in the build command changes, e.g., $CC, this does not cause the target to become outdated.

The default buildcheck is made from the build commands themselves. This is with variables expanded before the commands have been executed. Thus when one of the commands is ":sys $CC $CFLAGS $source" and $CC or $CFLAGS changes, the buildcheck signature changes. The :do commands are also expanded into the commands for the action specified. However, this only works when the action and filetype can be estimated. The action must be specified plain, not with a variable, and the filetype used is the first of:

  1. a filetype attribute specified after action

  2. if the first argument doesn't contain a "$", the filetype of this argument

  3. the filetype of the first source argument of the dependency.

To add something to the default check for the build commands the $commands variable can be used. Example:

        Version = 1.4
        foo.txt : {buildcheck = $commands $Version}
                :del {force} $target
                :print  >$target this is $target
                :print >>$target version number: $Version

If you now change the value of $Version, change one of the :print commands or add one, "foo.txt" will be rebuilt.

To simplify this, $xcommands can be used to check the build commands after expanding variables, thus you don't need to specify $Version:

        foo.txt : {buildcheck = $xcommands}

However, this only works when all $VAR in the commands can be expanded and variables used in Python commands are not expanded.

To avoid checking the build commands, use an empty buildcheck. This is useful when you only want the target to exist and don't care about the command used to create it:

        objects : {buildcheck = }
                :print "empty" > objects

Sometimes you might change the build commands in a recipe, which would normally mean the target should be updated, but you are sure that this isn't necessary and want to avoid executing the build commands. You can tell Aap to ignore the buildcheck once with the --contents option.