More Than One Recipe

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

# 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    all: theprog$EXESUF
    5
    6    theprog$EXESUF : core/$*CORE_OBJ util/$*UTIL_OBJ
    7        :do build $source

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 6 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    CORE_OBJ = `src2obj(SOURCE)`
    7    :export CORE_OBJ
    8
    9    all: $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 files used in this recipe.

The `CORE_OBJ` variable we do want to be available in the parent recipe. That is done with the `:export` command in line 7.

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.

In the last line is specified what happens when running `aap` without arguments in the "core" directory: The object files are build. 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`.

## 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 there is no need to use `:export` in the included recipe.

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. To avoid this, put the `:include` command after the `:child` commands:

    1    :child core/main.aap       # sets CORE_OBJ
    2    :child util/main.aap       # sets UTIL_OBJ
    3    :include common.aap
    4
    5    all: theprog$EXESUF
    6
    7    theprog$EXESUF : core/$*CORE_OBJ util/$*UTIL_OBJ
    8        :do build $source

You might argue that it's easier to put the `:include` command at the top, so that the children don't have to include "common.aap". You could do this, but then it is no longer possible to execute the child recipe by itself.

## 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 export variables. Here is an example:

        SOURCE = main.c common.c
        TARGET = prog

        test:
            :execute test.aap test
    	:print $TEST_RESULT

This recipe uses `SOURCE` and `TARGET` 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 `TEST_RESULT` variable to a message that summarizes the test results. To get this variable back to the recipe that executed "test.aap" these lines are used:

        @if all_done:
            TEST_RESULT = All tests completed successfully.
        @else:
            TEST_RESULT = Some tests failed!
        :export TEST_RESULT

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    :child core/main.aap {fetch = $ORIGIN/core.aap}
    3    :child util/main.aap {fetch = $ORIGIN/util.aap}
    4    :include common.aap {fetch = $ORIGIN/common.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`.

Prev | Contents | Next
Filetypes and Actions | Up | Commands in a Pipe