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