Chapter 9. More Than One Recipe

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.

Children

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.

Sharing Settings

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.

Sharing Modules

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:

  1. The recipe is read from the main Aap directory.

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

Executing a Recipe

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.

Fetching a Recipe

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.