|A-A-P home page||A-A-P Recipe Executive|
Executing recipes is a two step process:
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.
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
A recipe used for building an application often has these parts:
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.
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
User preferences (e.g. configuration choices) should be in a separate file that the user edits (using a template).
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.
Usually debug and release, but can include many more choices (type of
GUI, small or big builds, etc.).
This changes the value of
See Chapter 14, Variants.
build rules and actions
Rules that define dependencies and build commands that apply to
several files, defined with
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.
Dependencies and build commands that apply to specific files. Use these where the automatic dependency checking doesn't work and for exceptions.
high level build commands
: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.
Since commands at the recipe level are executed in the first step, some
building may already be done. Especially the
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.
: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.
:include config.aap includes the up-to-date "config.aap"
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
: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
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,
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
to the :include command.
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:
Read the startup recipes, these define default rules and variables. These recipes are used:
| - |
|- all recipes in system and user Aap directories (see below)|
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
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.
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|
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:
| - |
| - |
For other systems one directory is used, the first one that can be found from this list:
| - |
| - |
| - |
$HOME, $HOMEDRIVE and $HOMEPATH are environment variables, not Aap 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:
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 argument||resulting character|
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 >
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