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.

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.

      @if OSTYPE == "posix":
          CPPFLAGS += -DFAST
      @else:
          CPPFLAGS += /DFAST

      all:
          :print CPPFLAGS is "$CPPFLAGS"

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
    CPPFLAGS is "-I/usr/local/include -DFAST"
    %

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
    CPPFLAGS is "/IC:\VC\include /DFAST"
    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":
          CPPFLAGS += -DFAST
          LDFLAGS += -L/usr/local
      @else:
          CFLAGS += /DFAST

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. This might be a bit confusing at first. Try to remember that you only put a dollar before a variable name in the argument of a recipe command.

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

In many places a Python expression can be used. We have already seen the use of the glob() function:

   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.

The following example turns the list of source files into a list of header files:

    SOURCE = `glob("*.c")`
    HEADER = `aap_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 "aap_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

Documentation about Python can be found on its web site: http://www.python.org/doc/