Dependencies, Rules and Actions

A-A-P home page | A-A-P Recipe Executive
--- | ---
Prev |  | Next

# Chapter 12. Dependencies, Rules and Actions

## Automatic dependency checking

When a source file includes other files, the targets that depend on it need to be rebuild. 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.

A-A-P detects these implied dependencies automatically for the types it knows about. Currently that is C and C++. For other types of files you can add your own dependency checker. For example, this is how to define a checker for the "tt" file type:

       foo : foo.tt
    	:sys tt_compiler $source > $target
       :autodepend 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 A-A-P.

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

## Multiple targets

When a dependency 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. 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. A-A-P 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. "source_list" 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

    RECIPE STRUCTURE

    Generally there are these sections in a recipe used for building an
    application:

    1. global settings, include recipes with (project, user) settings
    2. automatic configuration
    3. specify variants
    4. generic build rules
    5. explicit dependencies

    For larger projects sections can be moved to other recipes.

    1. global settings, include recipes with (project, user) settings
    	When the recipe is part of a project, it's often useful to move
    	settings (and rules) that apply to the whole project to one file.
    	User preferences (e.g. configuration choices) should be in a separate
    	file that the user edits (using a template)
    2. automatic configuration
    	Find out properties of the system, and handle user preferences.  This
    	may result in building the application in a different way.
    3. specify variants
    	Usually debug and release, but can include many more choices (type of
    	GUI, small or big builds, etc.)
    4. generic dependencies and build commands
    	Rules that define dependencies and build commands that apply to
    	several files.
    5. explicit dependencies and build commands
    	Dependencies and build commands that apply to specific files.

    RECIPE EXECUTING:

    This is done in these steps:
    1. Read the startup recipes, these define default rules and variables.  These
       recipes are used:
       - "default.aap" from the distribution
       - all recipes matching "/usr/local/share/aap/startup/*.aap"
       - all recipes matching "~/.aap/startup/*.aap"
    2. Read the recipe and check for obvious errors.
    3. Execute the toplevel items in the recipe.  Dependencies and rules are
       stored.
    4. Apply the clever stuff to add missing dependencies and rules.
    5. Update the targets.  The first of the following that exists is used:
    	- targets specified on the command line
    	- the items in $TARGET
    	- the "all" target
    6. If the "finally" target is specified, execute its build commands.

    When there is one item in $TARGET, no dependency for $TARGET is specified and
    $SOURCE is not empty, a dependency is automatically generated. This will have
    the form:

    	$TARGET : $SOURCE_OBJ
    		:sys $CC $CFLAGS $LDFLAGS -o $target $source

    Where $SOURCE_OBJ is $SOURCE with all items changed to use $OBJSUF, if there
    is a suffix.  Example:

    	TARGET = foo
    	SOURCE = main.c extra.p

    Results in:

    	foo : main$OBJSUF extra$OBJSUF
    		:sys $CC $CFLAGS $LDFLAGS -o $target $source

    This then uses the default rules to make "main$OBJSUF" from main.c and
    extra$OBJSUF from extra.p.

    DEPENDENCIES:

    A dependency can have several targets.  How this is interpreted depends on
    whether commands are defined.

    Without commands the dependency is used like each target depends on the list
    of source files.  Thus this dependency:

    	t1 t2 : s1 s2 s3

    Can be rewritten as:

    	t1 : s1 s2 s3
    	t2 : s1 s2 s3

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

    When commands are specified, the commands are expected to produce all these
    targets. Example:

    	t1 t2 : s1 s2 s3
    		:sys produce s1 s2 s3  > t1
    		:sys filter < t1  > t2

    When either t1 or t2 is outdated relative to s1, s2 or s3, the commands are
    executed and both t1 and t2 are updated.

    Only when no dependency is specified for a target, the rules defined with
    ":rule" are checked.  All the matching rules without commands are used.
    TRICK: For the rules with commands, only the matching one with the longest
    pattern is used.  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

    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 default dependency added for $SOURCE and $TARGET

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

    VIRTUAL TARGETS

    When a target is virtual it is always build.  A-A-P 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 build 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 build 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 "srcpath" attribute overrules using $SRCPATH for an item.
    To avoid using $SRCPATH for a source, 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.

    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.

    CROSS REFERENCER

    A separate module in the A-A-P project is the cross referencer.  It can be
    used to lookup where symbols are defined and used.  The database that the
    cross referencer uses can be generated with a recipe.  That makes it possible
    to put the right files in the database.

    The standard way to create or update the database is:

    	aap reference

    The recipe can specify a "reference" target to implement it.  Example:

    	SOURCE = main.c version.c
    	INCLUDE = common.h

    	...

    	reference:
    		:sys aref -u $SOURCE $INCLUDE

    The advantage over letting the cross referencer use all files is that backup
    files and old versions are skipped, and files in other locations can be
    included.

    When there is no "reference" target, the default is to execute this command:

    	:do reference $SOURCE $?INCLUDE

    The default for the "reference" action is to run "aref -u", thus this does
    almost the same as the example.

    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 the value changes, the
    build commands will be executed.

    The default buildcheck is made from the command 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 file type of this argument
    3. the filetype of the first source argument of the dependency.

    To combine the check for the build commands themselves with specific variable
    values the $commands variable can be used:

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

    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

    If you want to ignore the buildcheck once, use the "--contents" option when
    running Aap.

Prev | Contents | Next
User Manual | Up | Variants