A-A-P Recipe Executive Bram Moolenaar For Aap version 1.090 Copyright (c) 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 I. Tutorial 1. Getting Started 2. Compiling a Program 3. Publishing a Web Site 4. Distributing a Program 5. Building Variants 6. Using Python 7. Version Control with CVS 8. Filetypes and Actions 9. More Than One Recipe 10. Commands in a Pipe 11. A Ported Application II. User Manual 12. How it all works 13. Dependencies, Rules and Actions 14. Variants 15. Publishing 16. Fetching 17. Installing 18. Version Control 19. Using CVS 20. Issue Tracking 21. Using Python 22. Porting an Application 23. Automatic Configuration 24. Using Autoconf 25. Automatic Package Install 26. Debugging a Recipe 27. Differences from make 28. Customizing Filetype Detection and Actions 29. Customizing Automatic Depedencies 30. Customizing Default Tools 31. Adding A Language Module III. Reference Manual 32. Aap Command Line Arguments 33. Recipe Syntax 34. Variables and Scopes 35. Common Variables 36. Assignments 37. Attributes 38. Filetype Detection 39. Standard Modules The "D" Programming Language GNU Libtool The Qt Library 40. Standard Tools 41. A-A-P Python functions 42. A-A-P Commands IV. Appendixes A. License List of Tables 2.1. items in a dependency 12.1. Special characters in the ":print" command 17.1. Install targets 17.2. Settings for the install target 33.1. Notation 35.1. Naming scheme for variables 35.2. Standard Variables 37.1. Virtual Targets 37.2. Sticky attributes 37.3. supported check attribute values 39.1. Variables of the D module Part I. Tutorial Table of Contents 1. Getting Started 2. Compiling a Program 3. Publishing a Web Site 4. Distributing a Program 5. Building Variants 6. Using Python 7. Version Control with CVS 8. Filetypes and Actions 9. More Than One Recipe 10. Commands in a Pipe 11. A Ported Application Chapter 1. Getting Started 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. Installation To start using Aap you must have two applications: o Python version 1.5 or later o 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: o Create a directory called "aap/Exec". o Download the latest Aap zip archive and unpack it in the new directory. o 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. Chapter 2. Compiling a Program A "Hello world" (of sorts) 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. Using Aap to compile "hello.c" 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: 1. 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. 2. 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. 3. The "hello.c" program file is compiled into the "hello.o" object file (on MS-Windows that would be "hello.obj"). 4. 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). Other things to do with "hello world" 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. Several Source Files 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. Variables and Assignments 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. Comments 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. Dependencies 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 #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: 1. The first dependency with "hello$EXESUF" as the target is found, it depends on "$BDIR/hello$OBJSUF". 2. 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. 3. 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. 4. 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. 5. The second dependency is now finished, "$BDIR/hello$OBJSUF" is up-to-date. Aap goes back to the first dependency. 6. 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). Compiling Multiple Programs 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. Kinds of things you can build 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. Chapter 3. Publishing a Web Site 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. Uploading The Files 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. Generating a HTML File 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. Using ":rule" to Generate Several HTML Files 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. Chapter 4. Distributing a Program 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. Downloading 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: 1. Dependencies will be created by the :program command to build "theprog" from main.c and version.c. 2. 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. 3. 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. 4. Now that all the files are present they are compiled and linked into "theprog". Uploading 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. Chapter 5. Building Variants 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. One Choice 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. Two Choices 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". [Note] Environment variables Environment variables are not used for variables in the recipe, like make does. When you happen to have a Gui environment variable, this will not influence the variant in the recipe. This is especially useful if you are not aware of what environment variables are set and/or which variables are used in the recipe. If you intentionally want to use an environment variable this can be specified with a Python expression (see the next chapter). 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. Chapter 6. Using Python 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. Expressions (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] Note Using environment variables is probably not portable. Conditionals 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 Scope 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 Loops 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 % Python Block 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. Expressions for Files 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. [Note] Using glob() is dangerous It is actually a bit dangerous to get the list of source files with the glob() function, because a "test.c" file that you temporarily used will accidentally be included. It is often better to list the source files explicitly. 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". Further Reading 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/ Chapter 7. Version Control with CVS 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. Downloading (Checkout) 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. Getting Past A Firewall 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. Uploading (Checkin) 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: 1. Files that you changed since the last checkin will be checked in to the CVS server. 2. Files that you added to the list of files with a commit attribute will be added to the CVS module. 3. 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. Further Reading The User manual Chapter 18, Version Control has more information about version control and Chapter 19, Using CVS about using CVS. Chapter 8. Filetypes and Actions 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. A New Type of 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: 1. The :filetype command is used to tell A-A-P to recognize your "hello.foo" file as being a "foo" file. 2. 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". 3. The :route command is used to specify which actions are to be used to turn a "foo" file into an "object" file. Defining a Filetype by Suffix 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. Defining a Compile Action 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. Another Use of Filetypes 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. Chapter 9. More Than One Recipe When you are working on a project that is split up in several directories it is convenient to use one recipe for each directory. There are several ways to split up the work and use a recipe from another recipe. Children A large program can be split in several parts. This makes it easy for several persons to work in parallel. You then need to allow the files in each part to be compiled separately and also want to build the complete program. A convenient way to do this is putting files in separate directories and creating a recipe in each directory. The recipe at the top level is called the parent. Here is an example that includes two recipes in subdirectories, called the children: 1 :child core/main.aap # sets Core_obj 2 :child util/main.aap # sets Util_obj 3 4 :program theprog : core/$*Core_obj util/$*Util_obj In the first two lines the child recipes are included. These specify how the source files in each directory are to be compiled and assign the list of object files to Core_obj and Util_obj. This parent recipe then defines how the object files are linked together to build the program "theprog". In line 4 a special mechanism is used. Assume that Core_obj has the value "main.c version.c". Then "core/$*Core_obj" will expand into "core/main.c core/version.c". Thus "core/" is prepended to each item in Core_obj. This is called rc-style expansion. You can remember it by thinking of the "*" to multiply the items. An important thing to notice is that the parent recipe does not need to know what files are present in the subdirectories. Only the child recipes contain the list of files. Thus when a file is added, only one recipe needs to be changed. The "core/main.aap" recipe contains the list of files in the "core" directory: 1 Source = main.c 2 version.c 3 4 CPPFLAGS += -I../util 5 6 _top.Core_obj = `src2obj(Source)` 7 8 all: $_top.Core_obj Variables in a child recipe are local to that recipe. The CPPFLAGS variable that is changed in line 4 will remain unchanged in the parent recipe and other children. That is desired here, since finding header files in "../util" is only needed for source files used in this recipe. The Core_obj variable we do want to be available in the parent recipe. That is done by prepending the "_top" scope name. The generic way to use a scope is: {scopename} . {variablename} Several scope names are defined, such as "_recipe" for the current recipe and "_top" for the toplevel recipe. The full list of scope names can be found in the reference manual, chapter Chapter 34, Variables and Scopes"Recipe Syntax and Semantics". When a variable is used without a scope name, it is looked up in the local scope and surrounding scopes. Thus the variables from the parent recipe are also available in the child. But when assigning to a variable without a scope, it is always set in the local scope only. To make the variable appear in another scope you must give the scope name. The value of Core_obj is set with a Python expression. The src2obj() function takes a list of source file names and transforms them into object file names. This takes care of changing the files in Source to prepend $BDIR and change the file suffix to $OBJSUF. It also takes care of using the "var_BDIR" attribute if it is present. In the last line is specified what happens when running aap without arguments in the "core" directory: The object files are built. There is no specification for how this is done, thus the default rules will be used. All the files in the child recipe are defined without mentioning the "core" directory. That is because all parent and child recipes are executed with the current directory set to where the recipe is. Note the files in Core_obj are passed to the parent recipe, which is in a different directory. That is why the parent recipe had to prepend "core/" when using Core_obj. This is so that the child recipe doesn't need to know what its directory name is, only the parent recipe contains this directory name. Sharing Settings Another mechanism to use a recipe is by including it. This is useful to put common variables and rules in a recipe that is included by several other recipes. Example: CPPFLAGS += -DFOOBAR :rule %$OBJSUF : %.foo :sys foocomp $source -o $target This recipe adds something to CPPFLAGS and defines a rule to turn a ".foo" file into an object file. Suppose you want to include this recipe in all the recipes in your project. Write the above recipe as "common.aap" in the top directory of the project. Then in "core/main.aap" and "util/main.aap" put this command at the top: :include ../common.aap The :include command works like the commands in the included recipe were typed instead of the :include command. There is no change of directory, like with the :child command and the included recipe uses the same scope. In the toplevel recipe you need include "common.aap" as well. Suppose you include it in the first line of the recipe, before the :child commands. The children also include "common.aap". The CPPFLAGS variable would first be appended to in the toplevel recipe, then passed to the child and appended to again. That is not what is supposed to happen. To avoid this, add the {once} option to the :include command. This means that the recipe is only included once and not a second time. The child recipes use: :include {once} ../common.aap And the parent uses: 1 :include {once} common.aap 2 :child core/main.aap # sets Core_obj 3 :child util/main.aap # sets Util_obj 4 5 all: theprog$EXESUF 6 7 theprog$EXESUF : core/$*Core_obj util/$*Util_obj 8 :do build $source You might argue that another way would be to put the :include command at the top of the parent recipe, so that the children don't have to include "common.aap". You could do this, but then it is no longer possible to execute a child recipe by itself. Note that using :include like this will always use the _top scope for the variables set in the included recipe. Be careful that the _recipe scope isn't used in one of the child recipes. Sharing Modules Sometimes a group of settings is so generally useful that you want to use it in many different projects. A typical example of such a group of settings is language support for a specific programming language. In order to add support for a new language (say, D), you need to define actions, set variables, etc. It is tedious to use :include, so Aap allows you to store such settings in a module. A module is a recipe like any other, except it is stored in the main Aap directory (along with the system default.aap). You can read a module with the :import command. This works very much like the :include command, except: 1. The recipe is read from the main Aap directory. 2. Each module is imported only once. Aap includes modules for standard languages and build systems. It does not read these recipes by default because they add additional overhead, even when you do not use the languages they specify. Therefore, support for the D language, using libtool to build libraries, and KDE support (among others) is included in modules that you can use when needed. A full list of modules can be found in Chapter 31, Adding A Language Module. Executing a Recipe Besides :child and :include there is a third way to use another recipe: :execute. This command executes a recipe. This works as if Aap was run as a separate program with this recipe, except that it is possible to access variables in the recipe that has the :execute command. Here is an example: :program prog : main.c common.c test: :execute test.aap test :print $TestResult This recipe uses the :program command as we have seen before. This takes care of building the "prog" program. For testing a separate recipe is used, called "test.aap". The first argument of the :execute command is the recipe name. Further arguments are handled like the arguments of the aap command. In this case the target "test" is used. The "test.aap" recipe sets the TestResult variable to a message that summarizes the test results. To get this variable back to the recipe that executed "test.aap" the "_parent" scope is used: @if all_done: _parent.TestResult = All tests completed successfully. @else: _parent.TestResult = Some tests failed! It would also be possible to use the :child command to reach the "test" target in it. The main difference is that other targets in "test.aap" could interfere with targets in this recipe. For example, "test.aap" could define a different "prog" target, to compile the program with specific test options. By using :execute we don't need to worry about this. In general, the :child command is useful when splitting up a tree of dependencies in parts, while :execute is useful for two tasks that have no common dependencies. Fetching a Recipe So far we assumed the included recipes were stored on the local system. It is also possible to obtain them from elsewhere. The example with children above can be extended like this: 1 Origin = ftp://ftp.foo.org/recipes 2 :include {once} common.aap {fetch = $Origin/common.aap} 3 :child core/main.aap {fetch = $Origin/core.aap} 4 :child util/main.aap {fetch = $Origin/util.aap} 5 6 all: theprog$EXESUF 7 8 theprog$EXESUF : core/$*Core_obj util/$*Util_obj 9 :do build $source The fetch attribute is used to specify the URL where the recipe can be obtained from. This works just like fetching source files. Notice in the example that the file name in the URL can be different from the local file name. When Aap reads this recipe and discovers that a child or included recipe does not exist, it will use the fetch attribute to download it. The fetch attribute can also be used with the :execute command. Once a recipe exists locally it will be used, even when the remote version has been updated. If you explicitly want to get the latest version of the recipes used, run aap -R or aap fetch. Chapter 10. Commands in a Pipe 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. Changing a timestamp 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 Creating a file from pieces 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:
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". Pipe output in a variable 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. Creating a file from scratch 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). Chapter 11. A Ported Application 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. The Port Recipe 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. Using CVS 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. Part II. User Manual Table of Contents 12. How it all works 13. Dependencies, Rules and Actions 14. Variants 15. Publishing 16. Fetching 17. Installing 18. Version Control 19. Using CVS 20. Issue Tracking 21. Using Python 22. Porting an Application 23. Automatic Configuration 24. Using Autoconf 25. Automatic Package Install 26. Debugging a Recipe 27. Differences from make 28. Customizing Filetype Detection and Actions 29. Customizing Automatic Depedencies 30. Customizing Default Tools 31. Adding A Language Module Chapter 12. How it all works How Recipes Are Executed Executing recipes is a two step process: 1. 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. 2. 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 % Common Recipe Structure A recipe used for building an application often has these parts: 1. global settings, include recipes with project and/or user settings 2. automatic configuration 3. specify variants (e.g., debug/release) 4. build rules and actions 5. explicit dependencies 6. high level build commands ( :program, :dll, etc.) 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. 1. 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). 2. 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. 3. 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. 4. 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. 5. explicit dependencies Dependencies and build commands that apply to specific files. Use these where the automatic dependency checking doesn't work and for exceptions. 6. 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. Building A Target In The First Step 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. Nesting The Steps 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 Using Multiple Recipes 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. Recipe Execution Details 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: 1. 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) 2. 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. 3. 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. 4. 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 5. 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. Use Of 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 Special Characters 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 > % Line Syntax 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 Chapter 13. Dependencies, Rules and Actions Build Commands There are several methods to specify build commands to update a target: 1. 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. 2. 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. 3. 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 recipe commands can be used in the build commands. But these are not allowed, they can only be used at the recipe level: a dependency specification :rule :route :totype :clearrules :delrule :program :dll :lib :recipe :variant In short: all commands that define dependencies cannot be used in build commands. But don't forget you can use :execute to do just about anything. The Production Commands The commands :program, :lib, :dll and :ltlib are called production commands because they explicitly state what things Aap should produce and what sources are involved. Everything the production commands can do, can be done by hand with dependencies as well, but the automation the production commands provide is quite useful. This section discusses how the production commands can be used and the variables that affect them. The form of each of the production commands is :command targets : sources. It is unusual to have more than one target, since both targets would be built from the same sources, but it is allowed. The list of sources should list the actual, original sources, i.e. only files that are actually written by the programmer and that exist on disk. It is these sources that will be packaged together for distributing the program or library in source form. Each production command transforms all of the sources into objects using compile actions. The sources are transformed into object files of a particular type - e.g. libraries use files with type "libobject". Once all of the sources have been compiled, a build action is invoked to turn the object files into the target. The table below lists the production commands and the actions used. Some of the production commands can use different programs to produce the final product, depending on settings in the recipe. In particular, you may need to chose to link a program with the compiler or through libtool, depending on whether your program links to any libtool libraries or not. The alternatives are listed in the table below as well. To select an alternative form to build the final product, set the filetype of the target to a specific value, e.g. :program myProgram { filetype=ltprogram } : source.c This example uses the ltprogram alternative build command to build the program "myProgram." Command Object Type Build Command Build Alternatives (normal) Uses the C compiler to link all the objects into a program. Uses $LIBS and $LDFLAGS. :program object build ltprogram Uses libtool to link all the objects into a program. Uses $LIBS and $LDFLAGS, but also adds $LTLIBS and $LT_RPATH if defined. (normal) Uses the ar utility to link :lib libobject buildlib all the objects into a static library. Uses $ARFLAGS. (normal) Uses the C compiler to link the objects into a dynamic (shared) :dll dllobject builddll library. The object files are different from regular library objects, and use a different extension. Uses $SHLINK, and $LDFLAGS, as well as $SHLINKFLAGS. (normal) Uses the libtool utility to :ltlib ltobject buildltlib link the objects together. Uses $LDFLAGS. In case you do want to have Aap figure out how to turn source files in to objects and then combine them into a target, but the target is not one of the types mentioned above, you can use the :produce command. Attributes for the Production Commands The production commands understand a wide variety of attributes. Let us return to the generic form of a production command: :command targets : sources There are four places attributes can be inserted in this command, as follows (we have split the command across several lines for clarity): 1 :command { command-attributes } 2 targets { per-target-attributes } : 3 { source-global-attributes } 4 sources { per-source-attributes } There is one commonly-used command-attribute: installvar. The production commands add their targets to the variable named in this attribute. This defaults to the "normal" variable, as listed in Table 17.2, "Settings for the install target". Assigning an empty value, through { installvar = }, prevents a target from being installed at all. This is useful for internal helper programs and libraries used during the build process. [Note] Note You should use { installvar = INSTALL_LTLIB } for program targets that have filetype ltprogram, since they need to be installed as if they are libtool libraries, not programs. [Warning] Warning It is a bad idea (excepting ltprograms, which belong in INSTALL_LTLIB) to add targets to the wrong install variable, since the install action that gets called for it will be wrong then as well. A slightly less-used command-attribute is objecttype, which changes the object file type from the default, (dllobject, for instance, for shared libraries), to something else. For programs that need to be linked by libtool, you may also want to force the object files used in the program to be compiled with libtool, since mixing non-libtool objects and libtool libraries can cause problems. For this, use the { objecttype = ltobject } as well, so that libtool programs will usually have build commands like: :program { filetype = ltprogram } { objecttype = ltobject } ... The attributes assigned in the per-target-attributes are used in the build and install actions of the target. Typical attributes assigned here are installdir and keepdir. Variables that affect the build step can be assigned too, such as var_LIBS and var_LDFLAGS. The attributes for sources are used for the compile steps of the build process, and useful attributes here are var_INCLUDE (if one source file needs special include files) and filetype. The attributes in the source-global-attributes position apply to all the sources in the list, and per-source-attributes apply only to the source file immediately preceding the attribute. An example that uses all of these settings is: 1 :ltlib { installvar = } conduit_knotes.la 2 { add_LIBS = -lkdeui } : 3 { add_INCLUDE = -I$BDIR/knotes } 4 knotes/KNotesIface.h { filetype=stub } { var_LTOBJSUF=_stub.lo } 5 knotes/knotes-factory.cc Here we see a libtool library that is not installed (line 1), which must be linked with an additional library (line 2). All of the sources are compiled with an extra include directory (line 3). The first source file (line 4) has additional complications and uses a different compile action due to its filetype. The last source file (line 5) is compiled with normal flags extended only by the source-global-attribute on line 3. Rules And Dependencies When a target is to be build Aap first searches for an explicit dependency with build commands that produces the target. This dependency may come from a high level build command such as :program. When such a dependency is not found then the rules defined with :rule are checked: 1. All the matching rules without commands are used, but only if the source already exists. Thus this cannot be used to depend on a file that is still to be created. 2. One rule with commands will be selected, in this order of preference: A rule for which the sources exist. A rule for which one of the sources does not exist and was not defined with the {sourceexists} option. If there are multiple matches, the rule with the longest pattern is used. Thus if you have these two rules: :rule test/%.html : test/%.in :do something :rule %.html : %.in :do something-else The first one will be used for a file "test/foo.html", the second one for a file "./foo.html". If there are two with an equally long pattern, this is an error. TRICK: When the source and target in a rule are equal, it is skipped. This avoids that a rule like this becomes cyclic: :rule %.jpg : path/%.jpg :copy $source $target Command block sections Sometimes it is useful to execute commands when a target does NOT require updating. For example, to give a message. And sometimes commands need to be executed no matter if the target is outdated. For example to add an attribute to the target. You can add sections to build commands of rules and dependencies. Three kinds of sections are possible: >always - always executed. >build - executed when building the target. >nobuild - executed when NOT building the target. The sections can be used in arbitrary order and may appear multiple times. All section headers must have the same amount of indent. The commands in the sections must have more indent than the section headers. Example: foo.out : foo.in >always # Always attach an attribute to the target :attr {output = yes} $target >build # Only when $target requires updating :copy $source $target >nobuild # Only when $target does not require updating :print $target is up-to-date Multiple targets When a dependency with build commands has more than one target, this means that the build commands will produce all these targets. This makes it possible to specify build commands that produce several files at the same time. Here is an example that compiles a file and at the same time produces a documentation file: foo.o foo.html : foo.src :sys srcit $source -o $(target[0]) --html $(target[1]) People used to "make" must be careful, they might expect the build commands to be executed once for each target. Aap doesn't work that way, because the above example would be impossible. To run commands on each target this must be explicitly specified. Example: dir1 dir2 dir3 : @for item in target_list: :mkdir $item The variable "target_list" is a Python list of the target items. Another such variable is "source_list", it is the list of source files (this excludes virtual items; "depend_list" also has the virtual items). An extreme example of executing build commands for each combination of sources and targets: $OutputFiles : $InputFiles @for trg in target_list: :print start of file >! $trg @for src in source_list: :sys foofilter -D$trg $src >> $trg When multiple targets are used and there are no build commands, this works as if each target depends on the list of sources. Thus this dependency: t1 t2 : s1 s2 s3 Is equivalent to: t1 : s1 s2 s3 t2 : s1 s2 s3 Thus when t1 is outdated to s1, s2 or s3, this has no consequence for t2. Automatic dependency checking When a source file includes other files, the targets that depend on the source file also depend on the included files. Thus when "foo.c" includes "foo.h" and "foo.h" is changed, the build commands to produce "foo.o" from "foo.c" must be executed, even though "foo.c" itself didn't change. Aap detects these implied dependencies automatically for the types it knows about. Currently that is C and C++. Either by using gcc or a Python function the "#include" statements are found in the source code and turned into a dependency without build commands. This works recursively. Thus when "foo.c" includes "foo.h" and "foo.h" includes "common.h", the dependency will look like this: foo.c : foo.h common.h For other types of files than C and C++ you can add your own dependency checker. For example, this is how to define a checker for the "tt" filetype: :action depend tt :sys tt_checker $source > $target The "tt_checker" command reads the file "$source" and writes a dependency line in the file "$target". This is a dependency like it is used in a recipe. In a Makefile this has the same syntax, thus tools that produce dependencies for "make" will work. Here is an example: foo.o : foo.tt foo.hh include/common.hh This is interpreted as a dependency on "foo.hh" and "include/common.hh". Note that "foo.o" and "foo.tt" are ignored. Tools designed for "make" produce these but they are irrelevant for Aap. Since the build commands for ":action depend" are ordinary build commands, you can use Python commands, system commands or a mix of both to do the dependency checking. More about customizing dependency checking in Chapter 29, Customizing Automatic Depedencies. Attributes Overruling Variables Most variables like $CFLAGS and $BDIR are used for all source files. Sometimes it is useful to use a different value for a group of files. This is done with an attribute that starts with "var_". What follows is the name of the variable to be overruled. Thus attribute "var_XYZ" overrules variable "XYZ". The overruling is done for: dependencies rules actions The attributes of all the sources are used. In case the same attribute is used twice, the last one wins. Another method is to use an "add_" attribute. This works like "var_", but instead of overruling the variable value it is appended. This is useful for variables that are a list of items, such as $DEFINE. Example: :attr thefile.c {add_DEFINE = -DEXTRA=yes} The value of the attribute is only appended when it does not appear yet, to avoid adding it two or more times. Another method is to define a scope name. This scope is then used to find variables before searching other scopes, but after using the local scope. For example, to specify that the "s_opt" scope is to be used when compiling "filter.c": OPTIMIZE = 0 DEBUG = yes :program myprog : main.c filter.c version.c :attr {scope = s_opt} filter.c s_opt.OPTIMIZE = 4 s_opt.DEBUG = no Note that you can set the values of the variables in the user scope after adding the scope attribute to "filter.c". Virtual Targets A virtual target is a target that is not an actual file. A Virtual target is used to trigger build commands without creating a file with the name of the target. Common virtual targets are "clean", "all", "publish", etc. When a target is virtual it is always built. Aap does not remember if it was already done a previous time. However, it is only build once for an invocation of Aap. Example: clean: :del {r}{f} temp/* To remember the signatures for a virtual target use the "remember" attribute: version {virtual}{remember} : version.txt.in :print $Version | :cat - $source >! version.txt Now "aap version" will only execute the :print command if version.txt.in has changed since the last time this was done. Using {remember} for one of the known virtual targets (e.g., "all" or "fetch") is unusual, except for "publish". When using {remember} for a virtual target without a dependency, it will only be built once. This can be used to remember the date of the first invocation. all: firsttime firsttime {virtual}{remember}: :print First build on $DATESTR > firstbuild.txt The difference with a direct dependency on "firstbuild.txt" is that when this file is deleted, it won't be built again. Source Path The sources for a dependency are searched for in the directories specified with $SRCPATH. The default is ". $BDIR", which means that the sources are searched for in the current directory and in the build directory. The current directory usually is the directory in which the recipe is located, but a :cd command may change this. The "srcpath" attribute overrules using $SRCPATH for an item. Example: :attr bar.c {srcpath = ~/src/lib} To avoid using $SRCPATH for a source, so that it is only found in the current directory, make the "srcpath" attribute empty: foo.o : src/foo.c {srcpath=} When setting $SRCPATH to include the value of other variables, you may want to use "$=", so that the value of the variable is not expanded right away but when $SRCPATH is used. This is especially important when appending to $SRCPATH before a :variant command, since it changes $BDIR. Example: SRCPATH $+= include Warning: Using the search path means that the first encountered file will be used. When old files are lying around the wrong file may be picked up. Use the full path to avoid this. Depending On A Directory When a target depends on the existence of a directory, it can be specified this way: foodir/foo : foodir {directory} :print >$target this is foo The directory will be created if it doesn't exist. The normal mode will be used (0777 with umask applied). When a different mode is required specify it with an octal value: {directory = 0700}. The number must start with a zero. Build Command Signature A special kind of signature is used to check if the build commands have changed. An example: foo.o : {buildcheck = $CFLAGS} foo.c :sys $CC $CFLAGS -c $source -o $target This defines a check for the value of $CFLAGS. When this value changes, the target is considered outdated. When something else in the build command changes, e.g., $CC, this does not cause the target to become outdated. The default buildcheck is made from the build commands themselves. This is with variables expanded before the commands have been executed. Thus when one of the commands is ":sys $CC $CFLAGS $source" and $CC or $CFLAGS changes, the buildcheck signature changes. The :do commands are also expanded into the commands for the action specified. However, this only works when the action and filetype can be estimated. The action must be specified plain, not with a variable, and the filetype used is the first of: 1. a filetype attribute specified after action 2. if the first argument doesn't contain a "$", the filetype of this argument 3. the filetype of the first source argument of the dependency. To add something to the default check for the build commands the $commands variable can be used. Example: Version = 1.4 foo.txt : {buildcheck = $commands $Version} :del {force} $target :print >$target this is $target :print >>$target version number: $Version If you now change the value of $Version, change one of the :print commands or add one, "foo.txt" will be rebuilt. To simplify this, $xcommands can be used to check the build commands after expanding variables, thus you don't need to specify $Version: foo.txt : {buildcheck = $xcommands} However, this only works when all $VAR in the commands can be expanded and variables used in Python commands are not expanded. To avoid checking the build commands, use an empty buildcheck. This is useful when you only want the target to exist and don't care about the command used to create it: objects : {buildcheck = } :print "empty" > objects Sometimes you might change the build commands in a recipe, which would normally mean the target should be updated, but you are sure that this isn't necessary and want to avoid executing the build commands. You can tell Aap to ignore the buildcheck once with the --contents option. Chapter 14. Variants You might first want to read the tutorial for a few examples of using variants. Here is an example how build variants can be specified. This will be used to explain how it works. :variant Opt some OPTIMIZE = 2 much OPTIMIZE = 6 * OPTIMIZE = 1 "Opt" is the name of a variable. It is used to select one of the variants. Each possible value is listed in the following line and further lines with the same indent. In the example these are "some" and "much". "*" is used to accept any value, it must be the last one. The first value mentioned is the default when the variable isn't set. You can now start Aap with various arguments to specify the kind of optimizing you want to use: aap Opt=some will set OPTIMIZE to 2 aap Opt=much will set OPTIMIZE to 6 aap Opt=other will set OPTIMIZE to 1 aap will set OPTIMIZE to 2 Note that when "Opt" is not given a value the first entry is used, resulting in OPTIMIZE being set to 2. But when it is set to a value that isn't mentioned the last entry "*" is used. The BDIR Variable The $BDIR variable will be adjusted for the variant used. CAREFUL: this means that using $BDIR before :variant commands will use a different value, that might not always be what you want. Inside the :variant command the value of $BDIR has already been adjusted. When a target that is being build starts with $BDIR and $BDIR doesn't exist, it is created. (Actually, this happens when an item in the path is "build" or starts with "build-". $BDIR is relative to the recipe. When using ":child dir/main.aap" the child recipe will use a different build directory dir/$BDIR. Note that when building the same source file twice from recipes that are in different directories, you will get two results. Best is to always build a target from the same recipe (that makes it easier to understand the recipe anyway). Compile only when needed This continues the last example of the tutorial. We happen to know that the main.c file does not depend on the GUI used. With the recipe above it will nevertheless be compiled again for every GUI version. Although this is a small thing in this example, in a bigger project it becomes more important to skip compilation when it is not needed. Here is the modified recipe: 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 :attr {var_DEFINE = $DEFINE} {var_BDIR = $BDIR} main.c 12 13 Gui ?= motif 14 :variant Gui 15 console 16 motif 17 Source += motif.c 18 gtk 19 Source += gtk.c 20 21 DEFINE += -DGUI=$Gui 22 23 :program $Target : $Source The only new line is line 11. The "main.c" file is given two extra attributes: var_DEFINE and var_BDIR. What happens is that when "main.c" is being build, Aap will check for attributes of this source file that start with "var_". The values will be used to set variables with the following name to the value of the attribute. Thus DEFINE gets the value of var_DEFINE. This means that the variable is overruled by the attribute while building "main.c". The var_BDIR attribute is set to "$BDIR" before the second :variant command. It does not yet have the selected GUI appended there. The list of directories used is now: directory contains files build-SYS-release main build-SYS-debug main build-SYS-release-console version, gui build-SYS-debug-console version, gui build-SYS-release-motif version, gui, motif build-SYS-debug-motif version, gui, motif build-SYS-release-gtk version, gui, gtk build-SYS-debug-gtk version, gui, gtk Building multiple variants at once If you want to build all the variants that are possible, use a few lines of Python code. Here is an example: 1 :variant license 2 trial 3 DEFINE += -DTRIAL 4 demo 5 DEFINE += -DDEMO 6 full 7 DEFINE += -DFULL 8 9 :variant language 10 chinese 11 DEFINE += -DCHINESE 12 bulgarian 13 DEFINE += -DBULGARIAN 14 * 15 DEFINE += -DENGLISH 16 17 build: 18 :print Building with $license license for language $language. 19 :print DEFINE=$DEFINE 10 21 all: 22 @for a in ['trial', 'demo', 'full']: #license 23 @ for c in ['chinese', 'bulgarian', 'english']: #language 24 :execute main.aap build license=$a language=$c Invoking Aap without arguments builds the "all" target, which loops over all possible variants and invokes the :execute command with the "build" target. The reason to use the "build" target is that without it the "all" target would be built again and result in an endless loop. This is the resulting output: Building with trial license for language chinese. DEFINE=-DTRIAL -DCHINESE Building with trial license for language bulgarian. DEFINE=-DTRIAL -DBULGARIAN Building with trial license for language english. DEFINE=-DTRIAL -DENGLISH Building with demo license for language chinese. DEFINE=-DDEMO -DCHINESE Building with demo license for language bulgarian. DEFINE=-DDEMO -DBULGARIAN Building with demo license for language english. DEFINE=-DDEMO -DENGLISH Building with full license for language chinese. DEFINE=-DFULL -DCHINESE Building with full license for language bulgarian. DEFINE=-DFULL -DBULGARIAN Building with full license for language english. DEFINE=-DFULL -DENGLISH Chapter 15. Publishing Publishing means distributing the files of your project. This is a generic mechanism. You can use it to maintain a web site or to release a new version of your application. The most straightforward way to publish a file is with the :publish command: :attr {publish = ftp://ftp.my.org/upload/%file%} myfile :publish myfile This uses the "publish" attribute on each of the files. When the "publish" attribute is missing the "commit" attribute is used. If both are missing this is an error. When a file didn't change since the last time it was published, it won't be published again. This works with signatures, like building a target. The remote file is the target in this case. But Aap won't read the remote file to compute the signature, it will remember the signature from when the file was last uploaded (otherwise checking for outdated files would be slow). In the example %file% is used. This is replaced with the name of the file being published, including the directory. This means you can use the same attribute for several files: Files = myfile yourfile hisfile :attr {publish = ftp://ftp.my.org/upload/%file%} $Files :publish $Files Including the directory is sometimes not what you want. To only use the last part of the path use %basename%. Files = one/myfile two/yourfile three/hisfile :attr {publish = ftp://ftp.my.org/common/%basename%} $Files :publish $Files The three files will be uploaded to the "common" directory. The directory names "one", "two" and "three" will not be used on the ftp server. To publish all files with a "publish" attribute start Aap like this: aap publish If the "publish' target is defined explicitly it will be executed. Otherwise, all files with the "publish" attribute are given to the :publish command, just like using the :publishall command. The "publish" attribute may consist of several items. Publishing will use all these items. This means a file can be distributed to several sites at once. This is unlike fetching, which stops as soon as the file could be obtained from one of the items. When publishing fails for one of the sites, e.g., because the server is down, this is remembered. When you publish again, uploading is done only for that site. The destinations for which pusblishing worked will be skipped then. Using Secure Copy Uploading files requires write access to the server. There are several methods for this, but some have the disadvantage that your password is sent as normal text over the internet. Or someone in between can change the files that you send out. There is one that provides sufficient security: scp or secure copy. To publish a file to a server through secure copy use a URL in this form: :publish file {publish = scp://myname@the.