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/