A-A-P home page | A-A-P Recipe Executive | |
Prev | Tutorial | Next |
When you are working on a project that is split up in several directories it is convenient to use one recipe for each directory. There are several ways to split up the work and use a recipe from another recipe.
A large program can be split in several parts. This makes it easy for several persons to work in parallel. You then need to allow the files in each part to be compiled separately and also want to build the complete program. A convenient way to do this is putting files in separate directories and creating a recipe in each directory. The recipe at the top level is called the parent. Here is an example that includes two recipes in subdirectories, called the children:
1 :child core/main.aap # sets Core_obj 2 :child util/main.aap # sets Util_obj 3 4 :program theprog : core/$*Core_obj util/$*Util_obj
In the first two lines the child recipes are included. These specify how the
source files in each directory are to be compiled and assign the list of
object files to Core_obj
and Util_obj
. This parent
recipe then defines how the object files are linked together to build the
program "theprog".
In line 4 a special mechanism is used. Assume that Core_obj
has
the value "main.c version.c". Then "core/$*Core_obj" will expand into
"core/main.c core/version.c". Thus "core/" is prepended to each item in
Core_obj
. This is called rc-style expansion. You can
remember it by thinking of the "*" to multiply the items.
An important thing to notice is that the parent recipe does not need to know what files are present in the subdirectories. Only the child recipes contain the list of files. Thus when a file is added, only one recipe needs to be changed. The "core/main.aap" recipe contains the list of files in the "core" directory:
1 Source = main.c 2 version.c 3 4 CPPFLAGS += -I../util 5 6 _top.Core_obj = `src2obj(Source)` 7 8 all: $_top.Core_obj
Variables in a child recipe are local to that recipe. The
CPPFLAGS
variable that is changed in line 4 will remain unchanged
in the parent recipe and other children. That is desired here, since finding
header files in "../util" is only needed for source files used in this recipe.
The Core_obj
variable we do want to be available in the parent
recipe. That is done by prepending the "_top" scope name. The generic way to
use a scope is:
{scopename} . {variablename}
Several scope names are defined, such as "_recipe" for the current recipe and "_top" for the toplevel recipe. The full list of scope names can be found in the reference manual, chapter Chapter 34, Variables and Scopes"Recipe Syntax and Semantics". When a variable is used without a scope name, it is looked up in the local scope and surrounding scopes. Thus the variables from the parent recipe are also available in the child. But when assigning to a variable without a scope, it is always set in the local scope only. To make the variable appear in another scope you must give the scope name.
The value of Core_obj
is set with a Python expression. The
src2obj() function takes a list of
source file names and transforms them into object file names. This takes care
of changing the files
in Source
to prepend $BDIR and change the file suffix to $OBJSUF.
It also takes care of using the "var_BDIR" attribute if it is present.
In the last line is specified what happens when running aap
without arguments in the "core" directory: The object files are built. There
is no specification for how this is done, thus the default rules will be used.
All the files in the child recipe are defined without mentioning the "core"
directory. That is because all parent and child recipes are executed with the
current directory set to where the recipe is. Note the files in
Core_obj
are passed to the parent recipe, which is in a different
directory. That is why the parent recipe had to prepend "core/" when using
Core_obj
. This is so that the child recipe doesn't need to know
what its directory name is, only the parent recipe contains this directory
name.
Another mechanism to use a recipe is by including it. This is useful to put common variables and rules in a recipe that is included by several other recipes. Example:
CPPFLAGS += -DFOOBAR :rule %$OBJSUF : %.foo :sys foocomp $source -o $target
This recipe adds something to CPPFLAGS
and defines a rule to turn
a ".foo" file into an object file. Suppose you want to include this recipe in
all the recipes in your project. Write the above recipe as "common.aap" in
the top directory of the project. Then in "core/main.aap" and "util/main.aap"
put this command at the top:
:include ../common.aap
The :include
command works like the commands
in the
included recipe were typed instead of the
:include
command.
There is no change of directory, like with the
:child
command and the included recipe uses the same scope.
In the toplevel recipe you need include "common.aap" as well. Suppose you
include it in the first line of the recipe, before the
:child
commands. The children also include
"common.aap". The CPPFLAGS
variable would first be appended to
in the toplevel recipe, then passed to the child and appended to again.
That is not what is supposed to happen.
To avoid this, add the {once}
option to the
:include command.
This means that the recipe is only included once and not a second time.
The child recipes use:
:include {once} ../common.aap
And the parent uses:
1 :include {once} common.aap 2 :child core/main.aap # sets Core_obj 3 :child util/main.aap # sets Util_obj 4 5 all: theprog$EXESUF 6 7 theprog$EXESUF : core/$*Core_obj util/$*Util_obj 8 :do build $source
You might argue that another way would be to put the
:include
command at the top of the parent recipe, so that
the children don't have to include "common.aap". You could do this, but then
it is no longer possible to execute a child recipe by itself.
Note that using :include like this will
always use the _top
scope for the variables
set in the included recipe. Be careful that the
_recipe
scope isn't
used in one of the child recipes.
Sometimes a group of settings is so generally useful
that you want to use it in many different projects.
A typical example of such a group of settings
is language support for a specific programming language.
In order to add support for a new language
(say, D
), you need
to define actions, set variables, etc.
It is tedious to use :include
,
so Aap allows you to store such settings in a module.
A module is a recipe like any other, except it is stored in the
main Aap directory (along with the system
default.aap
).
You can read a module with the
:import
command.
This works very much like the
:include
command, except:
The recipe is read from the main Aap directory.
Each module is imported only once.
Aap includes modules for standard languages and build systems. It does not read these recipes by default because they add additional overhead, even when you do not use the languages they specify. Therefore, support for the D language, using libtool to build libraries, and KDE support (among others) is included in modules that you can use when needed. A full list of modules can be found in Chapter 31, Adding A Language Module.
Besides :child
and :include
there is a
third way to use another recipe: :execute
. This command
executes a recipe. This works as if Aap was run as a separate program with
this recipe, except that it is possible to access variables in the recipe that
has the
:execute command. Here is an example:
:program prog : main.c common.c test: :execute test.aap test :print $TestResult
This recipe uses the :program
command as we have seen
before. This takes care of building the "prog" program. For testing a
separate recipe is used, called "test.aap". The first argument of the
:execute
command is the recipe name. Further arguments are
handled like the arguments of the aap
command. In this case the
target "test" is used.
The "test.aap" recipe sets the TestResult
variable to a message
that summarizes the test results. To get this variable back to the recipe
that executed "test.aap" the "_parent" scope is used:
@if all_done: _parent.TestResult = All tests completed successfully. @else: _parent.TestResult = Some tests failed!
It would also be possible to use the :child
command to
reach the "test" target in it. The main difference is that other targets in
"test.aap" could interfere with targets in this recipe. For example,
"test.aap" could define a different "prog" target, to compile the program with
specific test options. By using :execute
we don't need to
worry about this. In general, the :child
command is useful
when splitting up a tree of dependencies in parts, while
:execute
is useful for two tasks that have no common
dependencies.
So far we assumed the included recipes were stored on the local system. It is also possible to obtain them from elsewhere. The example with children above can be extended like this:
1 Origin = ftp://ftp.foo.org/recipes 2 :include {once} common.aap {fetch = $Origin/common.aap} 3 :child core/main.aap {fetch = $Origin/core.aap} 4 :child util/main.aap {fetch = $Origin/util.aap} 5 6 all: theprog$EXESUF 7 8 theprog$EXESUF : core/$*Core_obj util/$*Util_obj 9 :do build $source
The fetch
attribute is used to specify the URL where the recipe
can be obtained from. This works just like fetching source files. Notice in
the example that the file name in the URL can be different from the local file
name. When Aap reads this recipe and discovers that a child or included
recipe does not exist, it will use the fetch
attribute to download
it. The fetch
attribute can also be used with the
:execute
command.
Once a recipe exists locally it will be used, even when the remote version has
been updated. If you explicitly want to get the latest version of the recipes
used, run aap -R
or aap fetch
.