Chapter 12. How it all works

How Recipes Are Executed

Executing recipes is a two step process:

  1. recipe processing

    Read and parse the toplevel recipe, child recipes and included recipes. Commands at the recipe level are executed. Build commands (commands for dependencies, rules, actions, etc.) are stored.

  2. target building

    Build each of the specified targets, following dependencies. Build commands are executed.

Generally, one can say that in the first step the specification for the building is read and stored. In the second step the actual building is done.

In a simple recipe the first step is used to set variables and define dependencies. In the second step the dependencies are followed and their commands are executed to build the specified target.

      :print executed during the first step

      target1 : source1 source2
            :print executed during the second step

An exception is when Aap was started to execute a command directly. The recipe processing step will still be done, but instead of building a target the specified command is executed. Example, using the recipe above:

    % aap -c ':print $BDIR'
    executed during the first step
    build-FreeBSD4_5_RELEASE
    %

Common Recipe Structure

A recipe used for building an application often has these parts:

  1. global settings, include recipes with project and/or user settings

  2. automatic configuration

  3. specify variants (e.g., debug/release)

  4. build rules and actions

  5. explicit dependencies

  6. high level build commands ( :program, :dll, etc.)

You are free to use this structure or something else, of course. This is an explanation that you can use as a base. Many times you will be able to use this structure as a starting point and make small modifications where it is needed.

Now let us look into each part in more detail.

  1. global settings, include recipes with project and/or 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. Then use the :include command in every recipe that can be used to build something.

    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. See Chapter 24, Using Autoconf.

  3. specify variants

    Usually debug and release, but can include many more choices (type of GUI, small or big builds, etc.). This changes the value of BDIR. See Chapter 14, Variants.

  4. build rules and actions

    Rules that define dependencies and build commands that apply to several files, defined with :rule commands. Actions can be defined for what is not included in the default actions or to overrule the defaults actions to do a different way of building.

  5. explicit dependencies

    Dependencies and build commands that apply to specific files. Use these where the automatic dependency checking doesn't work and for exceptions.

  6. high level build commands

    :program, :dll, etc. can be used for standard programs, libraries, etc. This comes last, so that explicitly defined dependencies for building some of the items can be used.

For larger projects sections can be moved to other recipes. How you want to do this depends on whether these sub-recipes need to be executed by themselves and who is going to maintain each recipe. More about that below.

Building A Target In The First Step

Since commands at the recipe level are executed in the first step, some building may already be done. Especially the :update command gives you a powerful mechanism. This means you can already build a target halfway the first step. Note that only dependencies that have already been encountered will be used then.

A good use for the :update command at the recipe level is to generate a recipe that you want to include. Useful for automatic configuration. You would do something like this:

      config.aap : config.aap.in
          :print executing the configuration script...
          :sys ./conf.sh < $source > $target

      :update config.aap
      :include config.aap

First a dependency is specified with build commands for the included recipe. In this case the "config.aap.in" file is used as a template. The command :update config.aap invokes building "config.aap". If it is outdated (config.aap.in was changed since config.aap was last build) the build commands are executed. If "config.aap" is up-to-date nothing happens. Then the :include config.aap includes the up-to-date "config.aap" recipe.

Nesting The Steps

In the second step commands of dependencies are executed. One of these commands may be :execute. This means another recipe is read and targets are build. These are again the first and second step mentioned before, but now nested inside the second step. Here is an example that executes a recipe when "docfile.html" is to be build:

      docfile.html :
          :execute docs/main.aap $target

This construction is useful when you do not want to read the other recipe in the first step. Either because it is a large recipe that is not always needed, because the recipe does not always exist, or because the recipe must first be build by other commands. Here is an example of using a depencency on a recipe:

      docfile.html : docs/main.aap
          :execute docs/main.aap $target

      docs/main.aap: docs/main.aap.in
          :cd docs
          :sys ./conf.sh < main.aap.in > main.aap

The :execute command can also be used at the recipe level. This means another recipe is executed during the first step. A good example for this is building an application in different variants:

      # build the GTK version
      :execute main.aap Gui=GTK myprog
      :move myprog myprog-GTK

      # build the Motif version
      :execute main.aap Gui=Motif myprog
      :move myprog myprog-Motif

Using Multiple Recipes

There are many ways to split up a project into multiple recipes. If you are building one application, you mostly build the whole application, using a toplevel recipe. This recipe specifies the configuration, specifies variants and sets variables for choices. Separate recipes are used to handle specific tasks. For example, you can move related sources to a sub-directory and put a recipe in that directory to build those sources. For this situation you use the :child command.

When a project gets bigger, and especially when working together with several people, you may want to be able to split the project up in smaller pieces, which each can be build separately. To avoid replicating commands, you should put the configuration, variants and setting variables in a separate recipe. Each recipe can use the :include command to use this recipe. You need to take care that the recipe is not included twice, because commands like :route give an error when repeated and appending to variables must only be done once. Aap will read a recipe only the first time it is included when you add the {once} argument to the :include command.

Recipe Execution Details

The two-step processing of recipes is part of all the work that Aap does. There are a few other steps. This is what happens when Aap is run:

  1. Read the startup recipes, these define default rules and variables. These recipes are used:

    - default.aap from the distribution
    - all recipes in system and user Aap directories (see below)

  2. Recipe processing: Read the recipe main.aap or the one specified with the "-f" argument and check for obvious errors. Then execute the toplevel items in the recipe. Dependencies and rules are stored. Also read included and child recipes and execute the toplevel items in them.

  3. Apply the clever stuff to add missing dependencies and rules. This adds a "clean" rule only if the recipe didn't specify one, for example.

  4. Target building. The first of the following that exists is used:

    - targets specified on the command line
    - items specified with :program, :dll and :lib
    - the "all" target

  5. If the "finally" target is specified, execute its build commands. Each recipe can have its own "finally" target, they are all executed.

The startup recipes are read from directories that depend on the system. For Unix systems files in two directories are used:

- /usr/local/share/aap/startup/*.aap
- ~/.aap/startup/*.aap

For other systems one directory is used, the first one that can be found from this list:

- $HOME/aap/startup/*.aap
- $HOMEDRIVE/$HOMEPATH/aap/startup/*.aap
- c:/aap/startup/*.aap

$HOME, $HOMEDRIVE and $HOMEPATH are environment variables, not Aap variables.

Use Of Variables

Variables with uppercase letters are generally used to pass choices and options to actions. For example, $CC is the name of the C compiler and $CFLAGS optional arguments for the C compiler. The list of predefined variables is in the reference manual here.

To avoid clashing with an existing or future variable that is defined by Aap, use one or more lower case letters or prepend "MY". Examples:

        $n
        $sources
        $FooFlags
        $MYPROG

Also be careful with chosing a name for a user scope, it must be different from all variables used in recipes! Prepending "s_" is recommended. Examples:

        $s_debug.CFLAGS
        $s_ovr.msg

Special Characters

Some characters in expressions have a special meaning. And a command like :print also handles a few arguments in a special way. This table gives an overview of which characters you need to watch out for when using the :print command:

Table 12.1. Special characters in the ":print" command

:print argumentresulting character
$($)$
$(`)` (backtick)
$(#)#
$(>)>
$(<)<
$(|)|

Example:

    all:
        :print tie $(#)2 $(`)green$(`) $(|) price: $($) 13 $(<) incl vat $(>)

Write this in the file "try.aap". Executing it results in:

    % aap -f try.aap
    tie #2 `green` | price: $ 13 < incl vat >
    %

Line Syntax

Aap parses the recipe into a sequence of lines. A line is a sequence of characters terminated by a newline. You can escape the newline with a backslash to continue a logical line over more than one physical line, as follows:

1   One line
2   A longer line \
3   that continues \
4   over three physical lines.

You can always use backslash continuations to continue lines in Aap. Indentation does not matter.

In many constructions, Aap also supports Python-style line continuations, where a line is continued by increasing the indentation of subsequent physical lines. The above example would look different with Python-style continuation:

1   One line
2   A longer line
3       that continues
4       over three physical lines.

As you can see, the "block" of lines with an increased amount of indentation is considered to belong to the line above it.

Python-style line continuations are supported in all Aap constructions except when the command cannot be recognized if the linebreak comes early. For example, in dependencies the colon separating the targets from the sources cannot be in a continuation line. This does not work:

    myprog
            : mysource
        :print This Does Not Work!

It is also not possible to split a dependency by indent when it does not have build commands:

    myprog :
          mysource
    this = Does Not Work

You must use a backslash in this situation:

    myprog : \
          mysource
    this = OK