Chapter 2. Compiling a Program

The "Hello world" example

Most programming languages start with a short example that prints a "hello world" message. This is the recipe for compiling such a program with A-A-P:

    SOURCE = hello.c
    TARGET = hello$EXESUF

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
    Aap: Creating directory "/home/mool/tmp/build-FreeBSD4_5_RELEASE"
    Aap: cc -I/usr/local/include -g -O2 -E -MM hello.c > build-FreeBSD4_5_RELEASE/hello.c.aap
    Aap: cc -I/usr/local/include -g -O2 -c -o build-FreeBSD4_5_RELEASE/hello.o hello.c
    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. Gcc is invoked with the "-MM" argument. This doesn't compile the program but only finds header files used. 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. More about that below.

  3. The "hello.c" program file is compiled into the "hello.o" object file.

  4. The "hello.o" object file is linked to produce the "hello" program. Note that on MS-Windows this would be "hello.exe". That is why $EXESUF was used in the TARGET variable. Aap makes it empty on Unix and sets it to ".exe" on MS-Windows.

Assignments

The two lines in the example recipe are variable assignments. 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

Now execute the recipe:

    % aap -f try.aap
    one two three
    Aap: No target on the command line and no $TARGET or "all" target in a recipe
    %

The ":print" command prints its argument. You can see that a variable name predeced with a dollar is replaced by the value of the variable. The three variables that only differ by case each have a different value.

All Aap commands, except the assignment, start with a colon. That makes them easy to recognize.

Finally Aap complains there is nothing interesting to do...

Some characters in the expression have a special meaning. And the ":print" command 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 2-1. Special characters in the ":print" command

":print" argumentresulting character
$$$
`` (two backticks)` (one backtick)
$##
$gt>
$lt<
$bar|

Example:

    :print tie $#2 ``green`` $bar price: $$ 13 $lt incl vat $gt

Write this in the file "try.aap". Executing it results in:

    % aap -f try.aap
    tie #2 `green` | price: $ 13 < incl vat >
    Aap: No target on the command line and no $TARGET or "all" target in a recipe
    %

Several Source Files

When you have several files with source code you can specify them as a list:

    SOURCE =
           main.c
           version.c
           help.c
    TARGET = myprogram$EXESUF

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.

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!

To try out the example write it in the file "main.aap" in the same directory as where the three C files are. Then run Aap without arguments. You will see the commands used to compile the files and end up with the "myprogram" program. If you are less fortunate you will get an error message. Hopefully it makes clear what mistake you made.

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"
    SOURCE =
            main.c            # startup stuff
            version.c         # just the date stamp
            help.c            # display a help message

    TARGET = myprogram$EXESUF { comment = build my program }

Now run Aap:

    % aap comment
    target "myprogram": build my program
    %

The text inside curly braces is called an attribute. In this case the attribute name is "comment" and the attribute value is "build my program". 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 <stdio.h>
    #include "hello.h"
    main()
    {
        printf("Hello %s\n", world);
    }

The included "hello.h" file defines "world":

    #define world "World!"

If you run Aap, the "hello" program will be build 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 specify "SOURCE" and "TARGET" 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.o
          :do build $source

      $BDIR/hello.o : 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.o" and the build command is ":do build $source". This specifies how to build the "hello$EXESUF" program from the "$BDIR/hello.o" object file. The second dependency specifies how to compile "hello.c" into "$BDIR/hello.o" 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.

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.o", 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 build. 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.o".

  2. The second dependency with "$BDIR/hello.o" 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 build, or the target was never build before, the target is considered "outdated" and the build commands are executed.

  5. The second dependency is now finished, "$BDIR/hello.o" is up-to-date. Aap goes back to the first dependency.

  6. Aap computes a signature for "$BDIR/hello.o". 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 build, or the target was never build 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 build, because the produced intermediate file "$BDIR/hello.o" 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).

By the way, the "Hello world" example for an Aap recipe is very simple:

    all:
       :print Hello world!

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.    foo : $COMMON foo.c
6.            :do build $source
7.
8.    bar : $COMMON bar.c
9.            :do build $source

This recipe defines three targets: "all", "foo" and "bar". We left out the $EXESUF here to keep it simple. "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-2. items in a dependency

sourceitem on the right hand side of a dependency
source filesource that is a file
virtual sourcesource that is NOT a file
targeton the left hand side of a dependency
target filetarget that is a file
virtual targettarget that is NOT a file
nodesource or target
file nodesource or target that is a file
virtual nodesource 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. 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. Note that the assignment in the first line uses "COMMON" without a dollar, while in line 5 and 8 "$COMMON" is used. This makes it possible to mix the use of a variable with plain text and file names. This recipe would do the same thing:

1.    all : foo bar
2.
3.    foo : help.c util.c foo.c
4.            :do build $source
5.
6.    bar : help.c util.c bar.c
7.            :do build $source

You can see that "$COMMON" has been replaced by the value of the "COMMON" variable.

A variable name consists of ASCII letters, numbers and the underscore. If you want to append one of these after a variable name you must put parenthesis around the variable name, so that Aap knows where it ends:

    MAKENAME = Make
    BUILDFILE = $(MAKENAME)file
    :print $BUILDFILE

    % aap -f try.aap
    Makefile
    Aap: No target on the command line and no $TARGET or "all" target in a recipe
    %

The "-f" argument is used to specify the recipe to be read by Aap. If you don't give it, the recipe "main.aap" is read.