For Aap version 1.090
Copyright © 2002-2003 Stichting NLnet Labs
The license for copying, using, modifying, distributing, etc this documentation and the A-A-P files can be found in Appendix A, License.
2007 Aug 07 11:33:05 GMT
Abstract
This is the documentation for version 1.090 of the Recipe Executive, commonly known as the "aap" command. It is part of the A-A-P project.
The web site of A-A-P can be found here: http://www.a-a-p.org/
The HTML version of this manual can be read on-line: http://www.a-a-p.org/exec/index.html As a single file: http://www.a-a-p.org/exec/exec.html.
The PDF version of this manual can be found here: http://www.a-a-p.org/exec/exec.pdf
The plain text version of this manual: http://www.a-a-p.org/exec/exec.txt.
Table of Contents
List of Tables
Table of Contents
Aap is a program that builds (compiles and links) other programs, much like the venerable make(1) program. However, Aap uses the power of Python to make the "recipes" (instructions on how to build a program) more readable and more flexible. Aap can also replace the age-old autotools and make toolchain.
Aap does not trade power for complexity. For many programs, you need only list the name of the program, the sources and libraries it needs, and Aap does the rest. A powerful module system makes adding new programming languages to Aap's repertoire fairly straightforward.
Aap supports C, C++, D, Qt's moc, KDE's dcopidl, and libtool.
To start using Aap you must have two applications:
Python version 1.5 or later
Aap
Python is often installed already. Try this:
python -V
If you get a "Command not found" error you still need to install Python. Help for this can be found on the Python web site: www.python.org/download/.
For obtaining and installing Aap look here: www.a-a-p.org/download.html.
A summary for the impatient:
Create a directory called "aap/Exec".
Download the latest Aap zip archive and unpack it in the new directory.
Run "./aap install" or "aap install".
To check if your Aap program is working, type this command:
aap --help
You should get a list of the command line arguments. Note that there are two dashes before "help". You can read details about the command line arguments in Chapter 32, Aap Command Line Arguments.
Most programming languages start with a short example that prints a "hello
world" message. With Aap, this is also possible.
In a file called main.aap,
enter the following:
:print Hello, World!
Now run Aap by entering aap at the
command line. Aap will respond something like this:
% aap
Hello, World!
Aap: No target on the command line and no $TARGET, build rules or "all" target in a recipe
As you can see, Aap outputs the desired text, but also prints an error message. This is because Aap is not a programming language, but a language for describing how to compile and build programs (written in other languages). In other words, if you have written a "hello world" program in some language, then you can use Aap to compile that program.
Suppose you have written a "hello world" program in C, and the
sources are stored in a file called hello.c.
Aap already knows about the C language (and several others),
so the instructions to Aap about how to compile this program are very
short.
Instructions for Aap are stored in a file with the extension
.aap. Such a file is called a recipe.
This is the recipe for compiling such a program with Aap:
:program hello : hello.c
Write this text in a file main.aap, in the same directory as
hello.c.
Now invoke Aap to compile hello.c into the program
hello:
% ls
hello.c main.aap
% aap
1 Aap: Creating directory "/home/mool/tmp/build-FreeBSD4_5_RELEASE"
2 Aap: cc -I/usr/local/include -g -O2 -E -MM hello.c > build-FreeBSD4_5_RELEASE/hello.c.aap
3 Aap: cc -I/usr/local/include -g -O2 -c -o build-FreeBSD4_5_RELEASE/hello.o hello.c
4 Aap: cc -L/usr/local/lib -g -O2 -o hello build-FreeBSD4_5_RELEASE/hello.o
You see the commands Aap uses to compile the program:
A directory is created to write the intermediate results in. This directory is different for each platform, thus you can compile the same program for different systems without cleaning up.
Dependencies are figured out for the source file.
Aap will automatically detect
dependencies on included files and knows that if one of the included files
changed compilation needs to be done, even when the file itself didn't
change.
In this example, Aap uses the C compiler with the -MM
option to determine the included files.
The "hello.c" program file is compiled into the "hello.o" object file (on MS-Windows that would be "hello.obj").
The "hello.o" object file is linked to produce the "hello" program (on MS-Windows this would be "hello.exe", the ".exe" is added automatically).
The same simple recipe not only specifies how to build the "hello" program, it can also be used to install the program:
% aap install PREFIX=try
Aap: Creating directory "try/bin/"
Aap: Copied "test/hello" to "try/bin/hello"
Aap: /usr/bin/strip 'try/bin/hello'
The PREFIX variable specifies where to install the program.
The default is /usr/local.
For the example we use the try directory,
which doesn't exist. Aap creates it for you.
Other ways that this recipe can be used:
aap uninstall undo installing the program
aap clean cleanup the generated files
aap cleanALL cleanup all files (careful!)
See the reference manual for details about :program.
When you have several files with source code you can specify them as a list:
:program myprogram : main.c
version.c
help.c
There are three source files: main.c,
version.c and help.c.
Notice that it is not necessary to use a line continuation character, as you
would have to do in a Makefile. The list ends at a line where the indent is
equal to or less than what the assignment started with.
The amount of indent for the continuation lines is irrelevant, so long as it's
more than the indent of the first line.
The Makefile-style line continuation with a backslash just before the line break can also be used, by the way.
Indents are very important, just like in a Python script. Make sure your tabstop is always set to the standard value of eight, otherwise you might run into trouble when mixing tabs and spaces!
When you give a list of files to :program, Aap will determine dependencies and compile each of the source files in turn, and then link them all together into an executable.
Sometimes it is convenient to have an abbreviation for a long list of files. Aap supports this through variables (just like the make command and the shell).
An assignment has the form:
variablename = expression
The variable name is the usual combination of letters, digits and underscore.
It must start with a letter. Upper and lower case letters can be used and case
matters. To see this in action, write this recipe in a file with the name
try.aap:
foo = one
Foo = two
FOO = three
:print $foo $Foo $FOO
Aap normally reads the recipe from main.aap,
but you can tell it to read a different file if you want to.
Use the -f flag for this.
Now execute the recipe:
% aap -f try.aap
one two three
Aap: No target on the command line and no build rules or "all" target in a recipe
The :print command prints its argument. You can see that a variable name preceded with a dollar is replaced by the value of the variable. The three variables that only differ by case each have a different value. Aap also complains that there is nothing to build, just like in the hello world example.
If you want text directly after the variable's value, for example, to append an extension to the value of a variable, the text may be impossible to distinguish from a variable name. In these cases you must put parenthesis around the variable name, so that Aap knows where it ends:
all:
MakeName = Make
:print $(MakeName)file # 'f' can be in a variable name
:print $(MakeName).txt # '.' can be in a variable name
:print $MakeName-more # '-' is not in a variable name
% aap -f try.aap
Makefile
Make.txt
Make-more
%
All Aap commands, except the assignment, start with a colon. That makes them easy to recognize.
Some characters in the expression have a special meaning. The :print command also handles a few arguments in a special way. To avoid the special meaning use the $(x) form, where "x" is the special character. For example, to print a literal dollar use $($). See the user manual for a complete list.
Someone who sees this recipe would like to know what it's for. This requires adding comments. These start with a "#" character and extend until the end of the line (like in a Makefile and Python script).
It is also possible to associate a comment with a specific item:
# A-A-P recipe for compiling "myprogram"
:program myprogram { comment = MyProgram is really great } :
main.c # startup stuff
version.c # just the date stamp
help.c # display a help message
Now run Aap with a "comment" argument:
% aap comment
target "myprogram": MyProgram is really great
target "clean": delete generated files that are not distributed
target "cleanmore": delete all generated files
target "cleanALL": delete all generated files, AAPDIR and build-* directories
target "install": install files
target "uninstall": delete installed files
%
The text inside curly braces is called an attribute. In this case the attribute name is "comment" and the attribute value is "MyProgram is really great". An attribute can be used to attach extra information to a file name. We will encounter more attributes later on.
Let's go back to the "Hello world" example and find out what happens when
you change a source file. Use this hello.c file:
#include <stdio.h>
#include "hello.h"
main()
{
printf("Hello %s\n", world);
}
The included "hello.h" file defines "world":
#define world "World!"
If you run Aap, the "hello" program will be built as before. If you run Aap again you will notice that nothing happens. Aap remembers that "hello.c" was already compiled. Now try this:
% touch hello.c
% aap
%
If you have been using the "make" program you would expect something to happen. But Aap checks the contents of the file, not the timestamp. A signature of "hello.c" is computed and if it is still the same as before Aap knows that it does not need to be compiled, even though "hello.c" is newer than the "hello" program.
Aap uses the mechanism of dependencies. When you use the :program command Aap knows that the target depends on the sources. When one of the sources changes, the commands to build the target from the sources must be executed. This can also be specified explicitly:
hello$EXESUF : $BDIR/hello$OBJSUF
:do build $source
$BDIR/hello$OBJSUF : hello.c
:do compile $source
The generic form of a dependency is:
target : list-of-sources
build-commands
The colon after the target is important, it separates the target from the sources. It is not required to put a space before it, but there must be a space after it. We mostly put white space before the colon, so that it is easy to spot. There could be several targets, but that is unusual.
There are two dependencies in the example. In the first one the target is "hello$EXESUF", the source file is "$BDIR/hello$OBJSUF" and the build command is ":do build $source". This specifies how to build the "hello$EXESUF" program from the "$BDIR/hello$OBJSUF" object file. The second dependency specifies how to compile "hello.c" into "$BDIR/hello$OBJSUF" with the command ":do compile $source". The "BDIR" variable holds the name of the platform-dependent directory for intermediate results, as mentioned in the first example of this chapter. In case you need it, the $EXESUF variable Aap is empty on Unix and ".exe" on MS-Windows.
The relation between the two dependencies in the example is that the source of the first one is the target in the second one. The logic is that Aap follows the dependencies and executes the associated build commands. In this case "hello$EXESUF" depends on "$BDIR/hello$OBJSUF", which then depends on "hello.c". The last dependency is handled first, thus first hello.c is compiled by the build command of the second dependency, and then linked into "hello$EXESUF" by the build command of the first dependency.
Now change the "hello.h" file by replacing "World" with 'Universe":
#define world "Universe!"
If you now run Aap with "aap hello" or "aap hello.exe" the "hello" program will be built. But you never mentioned the "hello.h" file in the recipe. How did Aap find out the change in this file matters? When Aap is run to update the "hello" program, this is what will happen:
The first dependency with "hello$EXESUF" as the target is found, it depends on "$BDIR/hello$OBJSUF".
The second dependency with "$BDIR/hello$OBJSUF" as the target is found. The source file "hello.c" is recognized as a C program file. It is inspected for included files. This finds the "hello.h" file. "stdio.h" is ignored, since it is a system file. "hello.h" is added to the list of files that the target depends on.
Each file that the target depends on is updated. In this case "hello.c" and "hello.h". No dependency has been specified for them and the files exist, thus nothing happens.
Aap computes signatures for "hello.c" and "hello.h". It also computes a signature for the build commands. If one of them changed since the last time the target was built, or the target was never built before, the target is considered "outdated" and the build commands are executed.
The second dependency is now finished, "$BDIR/hello$OBJSUF" is up-to-date. Aap goes back to the first dependency.
Aap computes a signature for "$BDIR/hello$OBJSUF". Note that this happens after the second dependency was handled, it may have changed the file. It also computes a signature for the build command. If one of them changed since the last time the target was built, or the target was never built before, the target is considered "outdated" and the build commands are executed.
Now try this: Append a comment to one of the lines in the "hello.c" file. This means the file is changed, thus when invoking Aap it will compile "hello.c". But the program is not built, because the produced intermediate file "$BDIR/hello$OBJSUF" is still equal to what it was the last time. When compiling a large program with many dependencies this mechanism avoids that adding a comment may cause a snowball effect. (Note: some compilers include line numbers or a timestamp in the object file, in that case building the program will happen anyway).
Suppose you have a number of sources files that are used to build two programs. You need to specify which files are used for which program. Here is an example:
1. Common = help.c util.c 2. 3. all : foo bar 4. 5. :program foo : $Common foo.c 6. 7. :program bar : $Common bar.c
This recipe defines three targets: "all", "foo" and "bar". "foo" and "bar are programs that Aap can build from source files. But the "all" target is not a file. This is called a virtual target: A name used for a target that does not exist as a file. Let's list the terminology of the items in a dependency:
Table 2.1. items in a dependency
| source | item on the right hand side of a dependency |
| source file | source that is a file |
| virtual source | source that is NOT a file |
| target | on the left hand side of a dependency |
| target file | target that is a file |
| virtual target | target that is NOT a file |
| node | source or target |
| file node | source or target that is a file |
| virtual node | source or target that is NOT a file |
Aap knows the target with the name "all" is always used as a virtual target. There are a few other names which Aap knows are virtual, see Table 37.1, “Virtual Targets”. For other targets you need to specify it with the "{virtual}" attribute.
The first dependency has no build commands. This only specifies that "all" depends on "foo" and "bar". Thus when Aap updates the "all" target, this dependency specifies that "foo" and "bar" need to be updated. Since the "all" target is the default target, this dependency causes both "foo" and "bar" to be updated when Aap is started without an argument. You can use "aap foo" to build "foo" only. The dependencies for "all" and "bar" will not be used then.
The two files help.c and util.c are used by both the "foo" and the "bar"
program. To avoid having to type the file names twice, the "Common" variable
is used.
Not everything you want to build is a program. Your recipe might need too build a library or a libtool archive. In these cases, :lib, :dll or :ltlib provide the same level of automation as :program does for programs. The :produce command is more generic, you can use this to build various kinds of things.
If all else fails, you can use Aap like the make program and explicitly list the commands you need to build your project.
If you are maintaining a web site it is often a good idea to edit the files on your local system. After trying out the changes you then need to upload the changed files to the web server. A-A-P can be used to identify the files that changed and upload these files only. This is called publishing.
Here is an example of a recipe:
Files = index.html
project.html
links.html
images/logo.png
:attr {publish = scp://user@ftp.foo.org/public_html/%file%} $Files
That's all. You just need to specify the files you want to publish and the URL that says how and where to upload them to. Now "aap publish" will find out which files have changed and upload them:
% aap publish
Aap: Uploading ['/home/mool/www/foo/index.html'] to scp://user@ftp.foo.org/public_html/index.html
Aap: scp '/home/mool/www/vim/index.html' 'user@ftp.foo.org:public_html/index.html'
Aap: Uploaded "/home/mool/www/vim/index.html" to "scp://user@ftp.foo.org/public_html/index.html"
%
The first time you execute the recipe all files will be uploaded. Aap will create the "images" directory for you. If you had already uploaded the files and want to avoid doing it again, first run the recipe with: "aap publish --touch". Aap will compute the signatures of the files as they are now and remember them. Only files that are changed will be uploaded from now on.
The :attr command uses its first argument as an attribute and further arguments as file names. It will attach the attribute to each of the files. In this case the "publish" attribute is added, which specifies the URL where to upload a file to. In the example the "scp" protocol is used, which is a good method for uploading files to a public server. "ftp" can be used as well, but this means your password will go over the internet, which is not safe. The special item "%file%" is replaced with the name of the file being published.
It is common for HTML files to consist of a standard header, a body with the useful info and a footer. You don't want to manually add the header and footer to each page. When the header changes you would have to make the same change in many different files. Instead, use the recipe to generate the HTML files.
Let's start with a simple example: Generate the index.html file. Put the common header, containing a logo and navigation links, in "header.part". The footer, containing contact info for the maintainer, goes in "footer.part". The useful contents of the page goes in "index_body.part". Now you can use this recipe to generate "index.html" and publish it:
Files = index.html
images/logo.png
:attr {publish = scp://user@ftp.foo.org/public_html/%file%} $Files
all: $Files
publish: $Files
:publishall
index.html: header.part index_body.part footer.part
:cat $source >! $target
Notice that only the published files are put in the "Files" variable. These files get a "publish" attribute, which tells Aap that these are the files that need to be uploaded. The ".part" files are not published, thus they do not get the "publish" attribute.
Three dependencies follow. The "all" target is the virtual target we have seen before. It specifies that the default work for this recipe is to update the files in the "Files" variable. This means you don't accidentally upload the files by running "aap" without arguments. The normal way of use is to run "aap", check if the produced HTML file looks OK, then use "aap publish" to upload the file.
For "index.html" a target is specified with a build command. The :cat command concatenates the source files. "$source" stands for the source files used in the dependency: "header.part, "index_body.part" and "footer.part". The resulting text is written to "$target", which is the target of the dependency, thus "index.html". The ">!" is used to redirect the output of the :cat command and overwrite any existing result. This works just like the Unix "cat" command.
In the dependency with the "publish" target the :publishall command is used. This command goes through all the files which were given a "publish" attribute with the :attr command. Note that this does not work:
# This won't work.
Files = index.html {publish = scp://user@ftp.foo.org/public_html/%file%}
Using a "publish" attribute in an assignment will not make it used with the :publishall command.
Your web site contains several pages, thus you need to specify how to generate each HTML page. This quickly becomes a lot of typing. We would rather specify once how to make a "xxx.html" file from a "xxx_body.part" file, and then give the list of names to use for "xxx" (if you have assocations with the name "xxx_body.part" that is your own imagination! :-). This is how it's done:
Files = *.html
images/*.png
:attr {publish = scp://user@ftp.foo.org/public_html/%file%} $Files
all: $Files
publish: $Files
:publishall
:rule %.html : header.part %_body.part footer.part
:cat $source >! $target
This is very similar to the example that only generates the "index.html" file. The first difference is in the value of "Files": It contains wildcards. These wildcards are expanded when they are used where a file name is expected. The expansion is not done in the assignment! More about that later. In the three places where $Files is used the wildcard expansion results in a list of all "*.html" files in the current directory and all "*.png" files in the "images" directory.
The second difference is that there is no specific dependency for the "index.html" file but a :rule command. It looks very much the same, but the word "index" has been replaced by a percent character. You could read the rule command as a dependency where the "%" stands for "anything". In the example the target is "anything.html" and in the sources we find "anything_body.part". Obviously these two occurrences of "anything" are the same word.
If you have made HTML pages, you know they contain a title. We ignored that until now. The following recipe will handle a title, stored in the file "xxx_title.part". You also need a file "start.part", which contains the HTML code that goes before the title.
Files = *.html
images/*.png
:attr {publish = scp://user@ftp.foo.org/public_html/%file%} $Files
all: $Files
publish: $Files
:publishall
:rule %.html : start.part %_title.part header.part %_body.part footer.part
:cat $source >! $target
Notice that "%" is now used three times in the :rule command. It stands for the same word every time.
After writing this recipe you can forget what changes you made to what file. A-A-P will take care of generating and uploading those HTML files that are affected. For example, if you change "header.part", all the HTML files are generated and uploaded. If you change "index_title.part" only "index.html" will be done.
There is one catch: You must create an (empty) xxx.html file the first time, otherwise it will not be found with "*.html". And you have to be careful not to have other "xxx.html" files in this directory. You might want to explicitly specify all the HTML files instead of using wildcards.
The same problem with wildcards happens for the image files. There is a solution for this: use the get_html_images() function. You can find an example in the section called “Publishing images for HTML files”.
A similar recipe is actually used to update the A-A-P website. It's a bit more complicated, because not all pages use the same header.
Open source software needs to be distributed. This chapter gives a simple example of how you can upload your files and make it easy for others to download and install your program.
To make it easy for others to obtain the latest version of your program, you give them a recipe. That is all they need. In the recipe you describe how to download the files and compile the program. Here is an example:
1 Origin = ftp://ftp.mysite.org/pub/theprog
2
3 :recipe {fetch = $Origin/main.aap}
4
5 Source = main.c
6 version.c
7 Header = common.h
8
9 :attr {fetch = $Origin/%file%} $Source $Header
10
11 :program theprog : $Source
The first line specifies the location where all the files can be found. It is good idea to specify this only once. If you would use the text all over the recipe it is more difficult to read and it would be more work when the URL changes.
Line 3 specifies where this recipe can be obtained. After obtaining this recipe once, it can be updated with a simple command:
% aap refresh
Aap: Updating recipe "main.aap"
Aap: Attempting download of "ftp://ftp.mysite.org/pub/theprog/main.aap"
Aap: Downloaded "ftp://ftp.mysite.org/pub/theprog/main.aap" to "/home/mool/.aap/cache/98092140.aap"
Aap: Copied file from cache: "main.aap"
%
The messages from Aap are a bit verbose. This is just in case the downloading is very slow, you will have some idea of what is going on.
Lines 5 to 7 define the source files. This is not different from the examples that were used to compile a program, except that we explicitly mention the header file used.
Line 9 specifies where the files can be fetched from. This is done by
giving the source and header files the fetch attribute.
The :attr command does not cause the files to be fetched
yet. When a file is used somewhere and it has a fetch
attribute, then it is fetched. Thus files that are not used will not be
fetched.
A user of your program stores this recipe as main.aap and runs
aap without arguments. What will happen is:
Dependencies will be created by the :program command
to build "theprog" from
main.c and version.c.
The target "theprog" depends on
main.c and version.c.
Since these
files do not exist and they do have a fetch attribute, they
are fetched.
The main.c file is inspected for dependencies. It includes the
common.h file, which is automatically
added to the list of dependencies.
Since common.h does not exist and has a fetch attribute, it
is fetched as well.
Now that all the files are present they are compiled and linked into "theprog".
You need to upload the files mentioned in the recipe above. This needs to be repeated each time one of the files changes. This is essentially the same as publishing a web site. You will need to upload both the source files and the recipe itself. The {publish} attribute can be used for this. You can add the following two lines to the recipe above in order to upload all the files:
URL = scp://user@ftp.mysite.org//pub/theprog/%file%
:attr {publish = $URL} $Source $Header main.aap
Now you can use aap publish
to upload your source files as well.
A-A-P provides a way to build two variants of the same application. You just need to specify what is different about them. A-A-P will then take care of putting the resulting files in a different directory, so that you don't have to recompile everything when you toggle between two variants.
For the details see :variant in the reference manual.
Quite often you want to compile an application for release with maximal optimizing. But the optimizer confuses the debugger, thus when stepping through the program to locate a problem, you want to recompile without optimizing. Here is an example:
1 Source = main.c version.c gui.c 2 3 :variant Build 4 release 5 OPTIMIZE = 4 6 Target = myprog 7 debug 8 DEBUG = yes 9 Target = myprogd 10 11 :program $Target : $Source
Write this recipe as "main.aap" and run Aap without arguments. This will build "myprog" and use a directory for the object files that ends in "-release". The release variant is the first one mentioned, that makes it the default choice.
The first argument for the :variant command is
Build. This is
the name of the variable that specifies what variant will be selected. The
names of the alternatives
are specified with a bit more indent in lines 4 and 7. For each alternative
two commands are given, again with more indent. Note that the indent not only
makes it easy for you to see the parts of the :variant command, they are
essential for Aap to recognize them.
To select the "debug" variant the Build variable must be set to "debug". A
convenient way to do this is by specifying this on the command line:
% aap Build=debug
This will build the "myprogd" program for debugging instead of for release.
The DEBUG variable is recognized by Aap.
The object files are stored in a directory ending in
"-debug". Once you finished debugging and fixed the problem in, for example,
"gui.c", running Aap to build the release variant will only compile the
modified file. There is no need to compile all the C files, because the object
files for the "release" variant are still in the "-release" directory.
You can extend the Build variant with more items, for example
"profile". This is useful for alternatives that exclude each other. Another
possibility is to add a second :variant command. Let us
extend the example with a selection of the user interface type.
1 Source = main.c version.c gui.c 2 3 :variant Build 4 release 5 OPTIMIZE = 4 6 Target = myprog 7 debug 8 DEBUG = yes 9 Target = myprogd 10 11 Gui ?= motif 12 :variant Gui 13 console 14 motif 15 Source += motif.c 16 gtk 17 Source += gtk.c 18 19 DEFINE += -DGUI=$Gui 20 21 :program $Target : $Source
The :variant command in line 12 uses the Gui
variable to select one of "console", "motif" or "gtk". Together with the
earlier :variant command this offers six alternatives:
"release" with "console", "debug" with "console", "release" with "motif", etc.
To build "debug" with "gtk" use this command:
% aap Build=debug Gui=gtk
In line 11 an optional assignment "?=" is used. This assignment is skipped if
the Gui variable already has a value. Thus if Gui was
given a value on the command line, as in the example above, it will keep this
value. Otherwise it will get the value "motif".
![]() | Environment variables |
|---|---|
Environment variables are not used for variables in the recipe, like
|
In line 15, 17 and 19 the append assignment "+=" is used. This appends the argument
to an existing variable. A space is inserted if the value was not empty. For
the variant "motif" the result of line 15 is that
Source becomes "main.c version.c gui.c motif.c".
The "motif" and "gtk" variants each add a source file in line 15 and 17. For the console version no extra file is needed. The object files for each combination of variants end up in a different directory. Ultimately you get object files in each of the six directories ("SYS" stands for the platform being used):
| directory | contains files |
|---|---|
| build-SYS-release-console | main, version, gui |
| build-SYS-debug-console | main, version, gui |
| build-SYS-release-motif | main, version, gui, motif |
| build-SYS-debug-motif | main, version, gui, motif |
| build-SYS-release-gtk | main, version, gui, gtk |
| build-SYS-debug-gtk | main, version, gui, gtk |
See the user manual for more examples of using variants.
In various places in the recipe Python commands and expressions can be used. Python is a powerful and portable scripting language. In most recipes you will only use a few Python items. But where needed you can do just about anything with it.
(Almost) anywhere you have a value, such as a text string, you can use a Python expression instead. For instance, you could use a Python expression to retrieve the value of an environment variable for use in a recipe, or use an expression to compute some strange value.
Expressions are written between backticks (` `) and must be valid Python expressions. Some examples:
1 myhome = `os.environ.get("HOME")`
2 label = `"L"+str(17*22)`
The first example line shows how to retrieve an environment variable
by using Python's built-in os.environ module.
The second shows how you can use Python to compute something
within an Aap recipe. It doesn't do anything useful, but it
uses Python to compute the value L374,
and then Aap assigns that value to the variable label.
![]() | Note |
|---|---|
| Using environment variables is probably not portable. |
When a recipe needs to work both on Unix and on MS-Windows you quickly run into the problem that the compiler does not use the same arguments. Here is an example how you can handle that.
1 @if OSTYPE == "posix": 2 INCLUDE += -I/usr/local/include 3 @else: 4 INCLUDE += -Ic:/vc/include 5 6 all: 7 :print INCLUDE is "$INCLUDE"
The first and third line start with the "@" character. This means a Python command follows. The other lines are normal recipe lines. You can see how these two kinds of lines can be mixed.
The first line is a simple "if" statement. The OSTYPE variable is
compared with the string "posix". If they compare equal, the next line is
executed. When the OSTYPE variable has a different value the line
below @else: is executed. Executing this recipe on Unix:
% aap
INCLUDE is "-I/usr/local/include"
%
OSTYPE has the value "posix" only on Unix and Unix-like systems.
Executing the recipe on MS-Windows, where OSTYPE has the value
"mswin":
C:> aap
INCLUDE is "-Ic:/vc/include"
C:>
Note that the Python conditional commands end in a colon. Don't forget to add it, you will get an error message! The indent is used to form blocks, thus you must take care to align the "@if" and "@else" lines.
You can include more lines in a block, without the need for extra characters, such as { } in C:
@if OSTYPE == "posix":
INCLUDE += -I/usr/local/include
LDFLAGS += -L/usr/local
@else:
INCLUDE += -Ic:/vc/include
LDFLAGS += -Lc:/vc/lib
In Aap commands a variable without a scope is searched for in other scopes. Unfortunately, this does not happen for variables used in Python. To search other scopes you need to prepend "_no." before the variable name. Changing the above example to print the result from Python:
@if OSTYPE == "posix":
INCLUDE += -I/usr/local/include
@else:
INCLUDE += -Ic:/vc/include
all:
@print 'INCLUDE is "%s"' % _no.INCLUDE
Python has a "for" loop that is very flexible. In a recipe it is often used to go over a list of items. Example:
1 @for name in [ "solaris", "hpux", "linux", "freebsd" ]: 2 fname = README_$name 3 @if os.path.exists(fname): 4 Files += $fname 5 all: 6 :print $?Files
The first line contains a list of strings. A Python list uses square
brackets. The lines 2 to 4 are executed with the name variable
set to each value in the list, thus four times. The indent of line 5 is equal
to the @for line, this indicates the "for" loop has ended.
Note how the name and fname variables are used without
a dollar in the Python code. You only put a dollar before a variable name in
the argument of an Aap command. Not in Python code and not on the left hand
side of an assignment.
In line 2 the fname variable is set to "README_" plus the value of
name. The os.path.exists()
function in line 3 tests if a file exists. Assuming all four files exist,
this is the result of executing this recipe:
% aap
README_solaris README_hpux README_linux README_freebsd
%
When the number of Python lines gets longer, the "@" characters become annoying. It is easier to put the lines in a block. Example:
:python
Files = ''
for name in [ "solaris", "hpux", "linux", "freebsd" ]:
fname = "README_" + name
if os.path.exists(fname):
if Files:
Files = Files + ' '
Files = Files + fname
all:
:print $Files
This does the same thing as the above recipe, but now using Python commands.
As usual, the :python block ends where the
indent is equal to or less than that of the
:python line.
When using the :python command, make sure you
get the assignments right. Up to the "=" character the Python assignment is
the same as the recipe assignment, but what comes after it is different.
In many places a Python expression can be used. For example, the
glob() function can be used to expand wildcards:
Source = `glob("*.c")`
Python users know that the glob() function returns a list
of items. Aap automatically converts the list to a string, because all
Aap variables are strings. A space is inserted in between the items and
quotes are added around items that contain a space.
![]() | Using glob() is dangerous |
|---|---|
It is actually a bit dangerous to get the list of source files with the
|
Why use glob() when you can use wildcards directly?
The difference is that the expansion with glob() takes
place immediately, thus $Source will get the expanded value. When using
wildcards directly the expansion is done when using the variable, but that
depends on where it is used. For example, the
:print command does not do
wildcard expansion:
pattern = *.c
expanded = `glob(pattern)`
all:
:print pattern $pattern expands into $expanded
When "foo.c" and "bar.c" exist, the output will be:
% aap
pattern *.c expands into foo.c bar.c
%
The following example turns the list of source files into a list of header files:
Source = `glob("*.c")`
Header = `sufreplace(".c", ".h", Source)`
all:
:print Source is "$Source"
:print Header is "$Header"
Running Aap in a directory with "main.c" and "version.c"?
% aap
Source is "version.c main.c"
Header is "version.h main.h"
%
The sufreplace() function takes three
arguments. The first argument is the suffix which is to be replaced.
The middle argument is the replacement suffix.
The last argument is the name of a variable that is a list of names, or a
Python expression.
In this example each name in Source ending in ".c" will be changed
to end in ".h".
The User manual Chapter 21, Using Python has more information. Documentation about Python can be found on its web site: http://www.python.org/doc/
CVS is often used for development of Open Source Software. A-A-P provides facilities to obtain the latest version of an application and for checking in changes you made.
For downloading a whole module you only need to specify the location of the CVS server and the name of the module. Here is an example that obtains the A-A-P Recipe Executive:
CVSROOT = :pserver:anonymous@a-a-p.cvs.sourceforge.net:/cvsroot/a-a-p
all:
:fetch {fetch = cvs://$CVSROOT} Exec
Write this recipe as "main.aap" and run aap. The directory
"Exec" will be created and all files in the module obtained from the CVS
server:
% aap
Aap: CVS checkout for node "Exec"
Aap: cvs -d:pserver:anonymous@a-a-p.cvs.sf.net:/cvsroot/a-a-p checkout 'Exec'
cvs server: Updating Exec
U Exec/Action.py
U Exec/Args.py
[....]
%
If there is a request for a password just hit enter (mostly there is no password).
The :fetch command takes care of obtaining the latest
version of the items mentioned as arguments. Usually the argument is one
module, in this example it is "Exec". That CVS needs to be used is
specified with the fetch attribute. This is a kind of URL,
starting with "cvs://" and then the CVS root specification. In the example
the CVSROOT variable was used. This is not required, it just
makes the recipe easier to understand.
If the software has been updated, you can get the latest version by running "aap" again. CVS will take care of obtaining the changed files.
Note that all this only works when you have the "cvs" command installed. When it cannot be found Aap will ask you want Aap to install it for you. Whether this works depends on your system.
Firewalls may block the use of a CVS connection. Some servers have setup another way to connect, so that firewalls will not cause problems. This uses port 80, normally used for http connections. Here is the above example using a different "pserver" address:
CVSROOT = :pserver:anonymous@a-a-p.cvs.sourceforge.net:/cvsroot/a-a-p
all:
:fetch {fetch = cvs://$CVSROOT} Exec
This doesn't always work through a proxy though. If you have problems connecting to the CVS server, try reading the information at this link.
You are the maintainer of a project and want to distribute your latest
changes, so that others can obtain the software with a recipe as used above.
This means you need to checkin your files to the CVS server. This is done by
listing the files that need to be distributed and giving them a
commit attribute. Example:
CVSUSER_FOO = johndoe
CVSROOT = :ext:$CVSUSER_FOO@cvs.foo.sf.net:/cvsroot/foo
Files = main.c
common.h
version.c
:attr {commit = cvs://$CVSROOT} $Files
Write this as "cvs.aap" and run aap -f cvs.aap revise . What
will happen is:
Files that you changed since the last checkin will be checked in to the CVS server.
Files that you added to the list of files with a commit
attribute will be added to the CVS module.
Files that you removed from the list of files with a commit
attribute will be removed from the CVS module.
This means that you must take care the Files variable lists
exactly those files you want to appear in the CVS module, nothing more and
nothing less. Be careful with using something like
*.c, it might find more files that you intended.
Note: This only works when the CVS module was already setup. Read the CVS documentation on how to do this. The A-A-P user manual has useful hints as well.
In the example the CVSUSER_FOO variable is explicitly set, thus
this recipe only works for one user. Better is to move this line to your own
default recipe, e.g., "~/.aap/startup/default.aap". Then the above recipe
does not explicitly contain your user name and can also be used by others.
Once you tested this recipe and it works, you can easily distribute your
software with aap -f cvs.aap revise. You don't have to worry
about the exact CVS commands to be used. However, don't use this when you
want to checkin only some of the changes you made. And the example does not
work well when others are also changing the same module.
The User manual Chapter 18, Version Control has more information about version control and Chapter 19, Using CVS about using CVS.
A-A-P can recognize what the type of a file is, either by looking at the file name or by inspecting the contents of the file. The filetype can then be used to decide how to perform an action with the file.
Suppose you are using the "foo" programming language and want to use A-A-P to compile your programs. Once this is has been setup you can compile "hello.foo" into the "hello" program with a simple recipe:
:program hello : hello.foo
You need to explain Aap how to deal with "foo" files. This is done with a recipe:
:filetype
suffix foo foo
:action compile foo
:sys foocomp $?FOOFLAGS $source -o $target
:route foo object
compile
For Unix, write this recipe as "/usr/local/share/aap/startup/foo.aap" or "~/.aap/startup/foo.aap". The recipes in these "startup" directories are always read when Aap starts up.
Now try it out, using the simple recipe at the top as "main.aap":
% aap
Aap: foocomp hello.foo -o build-FreeBSD4_5_RELEASE/hello.o
Aap: cc -L/usr/local/lib -g -O2 -o hello build-FreeBSD4_5_RELEASE/hello.o
%
The "foo.aap" recipe does three things:
The :filetype command is used to tell A-A-P to recognize
your "hello.foo" file as being a "foo" file.
The :action command is used to specify how the "foocomp"
compiler is used to compile a "foo" program into an object file.
The user can set the FOOFLAGS variable to options he wants to use.
The convention is that the option variable is in uppercase, starts with the
filetype and ends in "FLAGS".
The :route command is used to specify which actions are to
be used to turn a "foo" file into an "object" file.
The :filetype command is followed by the line "suffix foo
foo". The first word "suffix" means that recognizing is done by the suffix of
the file name (the suffix is what comes after the last dot in the name).
The second word is the suffix and the third word is the type. Quite often the
type is equal to the suffix, but not always. Here are a few more examples of
lines used with :filetype:
:filetype
suffix fooh foo
suffix bash sh
It is also possible to recognize a file by matching the name with a pattern, checking the contents of the file or using a Python script. See the user manual.
The lower half of "foo.aap" specifies the compile action for the "foo" filetype:
:action compile foo
:sys foocomp $source -o $target
The :action command has two arguments. The first one
specifies the kind of action that is being defined. In this case "compile".
This action is used to make an object file from a source file. The second
argument specifies the type of source file this action is used for, in this
case "foo".
Below the :action line the build commands are specified. In
this case just one, there could be more.
The :sys command invokes an exteral program, "foocomp", and
passes the arguments. In an action $source is expanded to
the source of the action and $target to the target. These
are obtained from the :do command that invokes the action.
Example:
:do compile {target = `src2obj("main.foo")`} main.foo
This :do command invokes the compile action, specified with
its first argument. The target is specified as an attribute to the action,
the source is the following argument "main.foo".
When executing the :do command the filetype of "main.foo"
is detected to be "foo", resulting in the compile action for "foo" to be
invoked. In the build command of the action
$source and $target are replaced,
resulting in:
:sys foocomp main.foo -o `src2obj("main.foo")`
Note that in many cases $target is passed implicitly from a
dependency and does not appear in the :do command argument.
When building a program you often want to include the date and time when it was built. A simple way of doing this is creating a source file "version.c" that contains the timestamp. This file needs to be compiled every time your program is built. Here is an example how this can be done:
1 :program prog : main.c work.c
2
3 :attr prog {filetype = myprog}
4
5 :action build myprog object
6 version_obj = `src2obj("version.c")`
7 :do compile {target = $version_obj} version.c
8 :do build {filetype = program} $source $version_obj
The target "prog" is explicitly given a different filetype in line 3. The default filetype for a program is "program", here it is set to "myprog". This allows us to specify a different build action for "prog".
Write the recipe as "main.aap" (without the line numbers) and execute it with
aap. The first time all the files will be compiled and linked
together. Executing aap again will do nothing. Thus the
timestamp used in "version.c" will not be updated if the files were not
changed. If you now make a change in "main.c" and run aap you
will see that both "main.c" and "version.c" are compiled.
The :action command in line 5 has three arguments. The
first one "build" is the kind of action, like before. The second argument
"myprog" specifies the target filetype, the third one "object" the source
filetype. Thus the template is:
:action kind-of-action target-filetype source-filetype
This order may seem a bit strange. Remember that putting the target left of the source also happens in a dependency and an assignment.
There are three commands for the build action, lines 6 to 8. The first one
assigns the name of the object file for "version.c" to
version_obj. "version.c" was not included in the
:program command at
the top, it is compiled here explicitly in line 7. This is what makes sure
"version.c" is compiled each time "prog" is built. The other source files
will be compiled with the default rules for :command.
Finally the :do build command in line 8 invokes the build
action to link all the object files together. Note that the filetype for the
build action is explicitly defined to "program". This is required for this
:do command to use the default action for a program target.
Otherwise the action would invoke itself, since the filetype for $target is
"myprog".
For more information about customizing filetype detection and actions see Chapter 28, Customizing Filetype Detection and Actions.
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.
A selection of commands can be connected together with a pipe. This means the output of one command is the input for the next command. It is useful for filtering text from a variable or file and writing the result in a variable or file.
This example shows how you can change the timestamp in a file. It is done in-place.
all:
:print Setting date in foobar.txt.
:cat foobar.txt
| :eval re.sub('Last Change: .*\n', 'Last Change: ' + DATESTR + '\n', stdin)
>! foobar.txt
Lets see how this works:
% cat foobar.txt
This is example text for the A-A-P tutorial.
Last Change: 2002 Feb 29
The useful contents would start here.
% aap
Setting date in foobar.txt.
% cat foobar.txt
This is example text for the A-A-P tutorial.
Last Change: 2002 Oct 21
The useful contents would start here.
%
The last command in the example consists of three parts. First comes the
:cat command. It reads the "foobar.txt" file and passes it
throught the pipe to the next command. "cat" is short for "concatenate".
This is one of the good-old Unix commands that actually does much more than
the name suggests. In this example nothing is concatenated. Below you will
see examples where it does.
The second part of the example is the :eval command.
This is used to read the text coming in through the pipe and modify it with a
Python expression. In this case the expression is a "re.sub()" function call.
This Python function takes three arguments: A pattern, a replacement string
and the text to operate on. All occurences of the pattern in the text are
changed to the replacement string. The pattern "Last Change: .*\n" matches a
line with the date that was inserted previously. The replacement string
contains DATESTR, which is an Aap variable that contains today's
date as a string, e.g., "2002 Oct 19". The text to operate on is
stdin. This is the variable that holds the text that is coming in
through the pipe.
The third and last part >! foobar.txt redirects the
output of the :eval command back to the file "foobar.txt".
Using just ">" would cause an error, since the file already exists.
Note that in a Unix shell command this pipe would not work: The "foobar.txt" would be overwritten before it was read. In Aap this does not happen, the commands in the pipe are executed one by one. That makes it easier to use, but it does mean the text is kept in memory. Don't use pipes for a file that is bigger than half the memory you have available.
Changing a file in-place has the disadvantage that the normal dependencies don't work, since there is no separate source and target file. Often it is better to use a file "foobar.txt.in" as source, change it like in the example above and write it as a new file. The recipe would be:
foobar.txt: foobar.txt.in
:print Setting date in $target.
:cat $source
| :eval re.sub('Last Change: .*\n', 'Last Change: ' + DATESTR + '\n', stdin)
>! $target
Sometimes you need to generate a file from several pieces. Here is an example that concatenates two files and puts a generated text line in between.
manual.html: body.html footer.html
@import time
:eval time.strftime("%A %d %B %Y", time.localtime(time.time()))
| :print $(lt)BR$(gt)Last updated: $stdin$BR
| :cat body.html - footer.html >! $target
There are quite a few items here that need to be explained. First of all, the
"@import time" line. This is a Python command to load the "time" module. So
far we used modules that Aap has already loaded for you. This one isn't,
and since we use the "time" module in the next :eval
command it needs to be loaded explicitly.
The Python function "strftime()" formats the date and time in a specified format. See the Python documentation for the details. In this case the resulting string looks like "Monday 21 October 2002".
The output of the :eval command is piped into a
:print command. The variable stdin contains
the output of the previous command. Note that "$(lt)" is used instead of
"$lt". The meaning is exactly the same: the value of the lt
variable. Without the extra parenthesis it would read "$ltBR", which would be
the value of the "ltBR" variable.
The resulting text is:
<BR>Last updated: Monday 21 October 2002\n
Note that the first "BR" is the HTML code for a line break, while the "$Br" at the end is the Aap variable that contains a line break (here displayed as "\n").
Finally, the :cat command concatenates the file
"body.html", the output of the :print command and the file
"footer.html". Thus the "-" stands for where the pipe input is used.
The result is redirected to target, which is "manual.html".
The generated date in the previous example could be used elsewhere in the
recipe. Since we don't want to repeat a complicated expression the result of
the :eval command should be redirected to a variable, like
this:
@import time
:eval time.strftime("%A %d %B %Y", time.localtime(time.time()))
| :assign Datestamp
manual.html: body.html footer.html
:print $(lt)BR$(gt)Last updated: $Datestamp$Br
| :cat body.html - footer.html >! $target
The :assign command takes the input from the pipe and puts
it in the variable mentioned as its argument, which is "Datestamp" here.
Actually, the same can be done with a normal assignment and a Python
expression in backticks, but we intentionally wanted to show using a pipe
here.
It is also possible to completely generate a file from scratch. Here is an example that generates a C header file:
1 :include config.aap 2 pathdef.c: config.aap 3 :print Creating $target 4 :print >! $target /* pathdef.c */ 5 :print >> $target /* This file is automatically created by main.aap */ 6 :print >> $target /* DO NOT EDIT! Change main.aap only. */ 7 :print >> $target $#include "vim.h" 8 :print >> $target char_u *default_vim_dir = (char_u *)"$VIMRCLOC"; 9 :print >> $target char_u *all_cflags = (char_u *)"$CC -c -I$srcdir $CFLAGS";
The first :print command displays a message, so that it's
clear "pathdef.c" is being generated. The next line contains ">!" to
overwrite an existing file. It doesn't matter if the file already existed or
not, it now only contains the line "/* pathdef.c */". The third and following
lines contain ">>". This will cause each line to be appended to
"pathdef.c".
In the example the VIMRCLOC and srcdir variables are
defined in the recipe "config.aap". That is why this file is used as a source
in the dependency. Also note the use of "$#" in line 7. Since "#" normally
starts a comment it cannot be used directly here. "$#" is a special item that
results in a "#" in the :print output. This is the
resulting file:
/* pathdef.c */
/* This file is automatically created by main.aap */
/* DO NOT EDIT! Change main.aap only. */
#include "vim.h"
char_u *default_vim_dir = (char_u *)"/usr/local/share/vim61";
char_u *all_cflags = (char_u *)"cc -c -I. -g -O2";
The list of ">>" redirections is quite verbose. Fortunately there is a shorter way:
1 :include config.aap 2 pathdef.c: config.aap 3 :print Creating $target 4 text << EOF 5 /* pathdef.c */ 6 /* This file is automatically created by main.aap */ 7 /* DO NOT EDIT! Change main.aap only. */ 8 $#include "vim.h" 9 char_u *default_vim_dir = (char_u *)"$VIMRCLOC"; 10 char_u *all_cflags = (char_u *)"$CC -c -I$srcdir $CFLAGS"; 11 EOF 12 :print $text >! $target
In line 4 "text << EOF" is used. This is called a block assignment.
The following lines, up to the matching "EOF" line, are assigned to the
variable text. You can use something else than "EOF" if you want
to. It must be a word that does not appear inside of the text as a line on
its own. White space before and after the word is ignored.
The indent of the text in the block assignment is removed. The indent of the first line is used, the same amount of indent is removed from the following lines. Thus if the second line has two more spaces worth of indent than the first line, it will have an indent of two spaces in the result. Half a tab is replace with four spaces when necessary (a tab always counts for up to eight spaces).
When an application already exists but for your system it requires a few tweaks, a port recipe can do the work. This can also be used for applications that work fine but you want to apply a number of patches or to add a feature. The recipe can be distributed, so that others can install the application without knowing the details. This works very much like the FreeBSD ports system.
This chapter is specifically for doing the port. If you are only interested in another kind of building you might want to skip this chapter.
Since A-A-P is prepared for doing all the work, usually you only need to specify the relevant information, such as where to find the files involved. Here is an example:
1 # A-A-P port recipe for Vim 6.1 plus a few patches.
2 RECIPEVERSION = 1.0
3
4 PORTNAME = vim
5 LASTPATCH = 003
6 PORTVERSION = 6.1.$LASTPATCH
7 MAINTAINER = Bram@vim.org
8
9 CATEGORIES = editors
10 PORTCOMMENT = Vim - Vi IMproved, the text editor
11 PORTDESCR << EOF
12 This is the description for the Vim package.
13 A very nice editor, backwards compatible to Vi.
14 You can find all info on http://www.vim.org.
15 EOF
16
17 :recipe {fetch = http://www.a-a-p.org/ports/vim/main.aap}
18
19 WRKSRC = vim61
20 BUILDCMD = make
21 TESTCMD = make test
22 INSTALLCMD = make install DESTDIR=$PKGDIR
23 PREFIX = /usr/local
24
25 MASTER_SITES ?= ftp://ftp.vim.org/pub/vim
26 ftp://ftp.us.vim.org/pub/vim
27 PATCH_SITES = $*MASTER_SITES/patches
28
29 DISTFILES = unix/vim-6.1.tar.bz2
30
31 version1 = `range(1, int(LASTPATCH) + 1)`
32 PATCHFILES = 6.1.00$*version1
33
34 #>>> automatically inserted by "aap makesum" <<<
35 do-checksum:
36 :checksum $DISTDIR/vim-6.1.tar.bz2 {md5 = 7fd0f915adc7c0dab89772884268b030}
37 :checksum $PATCHDISTDIR/6.1.001 {md5 = 97bdbe371953b9d25f006f8b58b53532}
38 :checksum $PATCHDISTDIR/6.1.002 {md5 = f56455248658f019dcf3e2a56a470080}
39 :checksum $PATCHDISTDIR/6.1.003 {md5 = 0e000edba66562473a5f1e9b5b269bb8}
40 #>>> end <<<
Well, that is the longest example we have had so far. Let's go through it from top to bottom.
1 # A-A-P port recipe for Vim 6.1 plus a few patches. 2 RECIPEVERSION = 1.0
RECIPEVERSION tells Aap what version of Aap this recipe was
written for. If in the future the recipe format changes, this line causes
Aap to interpret it as Aap version 1.0 would do.
4 PORTNAME = vim
Setting PORTNAME to the name of the port is what actually triggers
Aap to read this recipe as a port recipe. It makes the other settings to be
used to set up a whole range of targets and build commands. The result is
that you can do aap install to install the application, for
example. Note that PORTNAME does not include the version number.
5 LASTPATCH = 003 6 PORTVERSION = 6.1.$LASTPATCH 7 MAINTAINER = Bram@vim.org 8 9 CATEGORIES = editors 10 PORTCOMMENT = Vim - Vi IMproved, the text editor 11 PORTDESCR << EOF 12 This is the description for the Vim package. 13 A very nice editor, backwards compatible to Vi. 14 You can find all info on http://www.vim.org. 15 EOF
In lines 5 to 15 a number of informative items about the port are specified.
These are used in various places. LASTPATCH is not a standard
item, it is used here to only have to define the patchlevel in one place.
17 :recipe {fetch = http://www.a-a-p.org/ports/vim/main.aap}
The :recipe command specifies where to obtain
the recipe itself from. We have seen this before, nothing special here.
19 WRKSRC = vim61 20 BUILDCMD = make 21 TESTCMD = make test
The assignments in lines 19 to 21 specify how building is to be done.
WRKSRC is the directory below which the source files are unpacked.
The default is "$PORTNAME-$PORTVERSION". The archive used for Vim uses
"vim61" instead, thus this needs to be specified. The "CMD" variables set the
commands to be used to build the application. The default is to use Aap.
Since Vim uses "make" this needs to be specified.
22 INSTALLCMD = make install DESTDIR=$PKGDIR 23 PREFIX = /usr/local
Installing a port is done by creating a binary package and installing that
package. This makes it possible to copy the package to another system and
install it there without the need to compile from sources. Lines 22 and 23
specify how to do a "fake install" with Vim. This copies all the files that
are to be installed to a specific directory, so that it is easy to include
them in the package. PREFIX specifies below which directory Vim
installs its files.
25 MASTER_SITES ?= ftp://ftp.vim.org/pub/vim 26 ftp://ftp.us.vim.org/pub/vim 27 PATCH_SITES = $*MASTER_SITES/patches
MASTER_SITES and PATCH_SITES specify the sites where
the Vim files can be downloaded from. The first is for the archives, the
second for the patches. Note the use of "$*" in line 27, this causes
"/patches" to be appended to each item in MASTER_SITES instead of
appending it once at the end of the whole list.
29 DISTFILES = unix/vim-6.1.tar.bz2
DISTFILES is set to the name of the archive to download. This is
appended to items in MASTER_SITES to form the URL.
31 version1 = `range(1, int(LASTPATCH) + 1)` 32 PATCHFILES = 6.1.00$*version1
Lines 32 and 33 specify the list of patch file names. The Python function
"range()" is used, this returns a list of numbers in the specified range (up
to and excluding the upper number). Note the user of "int()" to turn the
patch number in LASTPATCH into an int type, all Aap variables
are strings.
for three patch files this could also have been typed, but when the number of patches grows this mechanism is easier. The example only works up to patch number 009. To make it work for numbers from 100 up to 999:
version1 = `range(1, 10)`
version2 = `range(10, 100)`
version3 = `range(100, int(LASTPATCH) + 1)`
PATCHFILES = 6.1.00$*version1 6.1.0$*version2 6.1.$*version3
34 #>>> automatically inserted by "aap makesum" <<<
35 do-checksum:
36 :checksum $DISTDIR/vim-6.1.tar.bz2 {md5 = 7fd0f915adc7c0dab89772884268b030}
37 :checksum $PATCHDISTDIR/6.1.001 {md5 = 97bdbe371953b9d25f006f8b58b53532}
38 :checksum $PATCHDISTDIR/6.1.002 {md5 = f56455248658f019dcf3e2a56a470080}
39 :checksum $PATCHDISTDIR/6.1.003 {md5 = 0e000edba66562473a5f1e9b5b269bb8}
40 #>>> end <<<
Finally the "do-checksum" target is defined. This part was not typed, but
added to the recipe with aap makesum. This is done by the port
recipe maintainer, when he has verified that the files are correct. When a
user later uses the recipe Aap will check that the checksums match, so that
problems with downloading or a cracked distribution file are found and
reported.
The port recipe specifies which source files and patches to download, thus it has to be adjusted for each version. This is good for a stable release, but when you are releasing a new version every day it is a lot of work. Another method is possible when the files are available from a CVS server. Adding these lines to the recipe will do it:
CVSROOT ?= :pserver:anonymous@vim.cvs.sf.net:/cvsroot/vim
CVSMODULES = vim
CVSTAG = vim-6-1-$LASTPATCH
The first line specifies the cvsroot to use. This is specific for the cvs
program. CVSMODULES is the name of the module to checkout.
Mostly it is just one name, but you can specify several. Specifying
CVSTAG is optional. If it is defined, like here, a specific
version of the application is obtained. When it is omitted the latest version
is obtained.
Much more about the port recipe can be found in Chapter 22, Porting an Application.
Table of Contents
Executing recipes is a two step process:
recipe processing
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.
target building
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
build-FreeBSD4_5_RELEASE
%
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
something.
User preferences (e.g. configuration choices) should be in a separate file that the user edits (using a template).
automatic configuration
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.
specify variants
Usually debug and release, but can include many more choices (type of
GUI, small or big builds, etc.).
This changes the value of BDIR.
See Chapter 14, Variants.
build rules and actions
Rules that define dependencies and build commands that apply to
several files, defined with :rule commands.
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.
explicit dependencies
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
:program, :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 :update command
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.
The command :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.
Then the :include config.aap includes the up-to-date "config.aap"
recipe.
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
The :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,
because commands
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
{once} argument
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:
- default.aap from the distribution |
| - 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
in them.
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:
- /usr/local/share/aap/startup/*.aap |
- ~/.aap/startup/*.aap |
For other systems one directory is used, the first one that can be found from this list:
- $HOME/aap/startup/*.aap |
- $HOMEDRIVE/$HOMEPATH/aap/startup/*.aap |
- c:/aap/startup/*.aap |
$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:
$s_debug.CFLAGS
$s_ovr.msg
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 |
|---|---|
| $($) | $ |
| $(`) | ` (backtick) |
| $(#) | # |
| $(>) | > |
| $(<) | < |
| $(|) | | |
Example:
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
There are several methods to specify build commands to update a target:
A dependency
This is more or less the same as how this is used in a Makefile: One or more targets, a colon and any number of sources. This specifies that the target(s) depends on the source(s). When build commands are given these are the commands to build the target(s) from the source(s). Without build commands the dependency is only used to check if the target is outdated and needs to be build.
A rule
Specified with a :rule command. A "%" in the target(s) and
source(s) stands for any string. This is used to specify a dependency that is
to be used for files that match the pattern.
An action
Specified with a :action command.
Unlike dependencies and rules an action does not specify a build dependency.
It must be invoked by other build commands with the :do
command.
Nearly all