A-A-P home page | A-A-P Recipe Executive |
Prev | Tutorial | Next

## 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
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.

| 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/

Prev | Up | Next
Chapter 5. Building Variants | Contents | Chapter 7. Version Control with CVS