Chapter 21. Using Python

Python commands can be used where Aap commands are not sufficient. This includes flow control, selecting the commands to be executed and repeating commands.

Using Python Lines

Single lines of Python code can be included in the recipe by prepending "@". This is most often used for flow control:

        @if os.path.isdir("/usr/local/bin"):
            :copy $File /usr/local/bin

You can write multiple Python commands, just prefix a "@" to every line. Do remember that the amount of indent is used to form command blocks. The indent that is used excludes the "@" character. When there is a non-white character after the "@", the "@" is removed. When there is white space after the "@" it is replaced with a space. Generally you don't need to worry about this, if the indenting looks right it probably is.

The main advantage of using single Python lines is that they can be mixed freely with Aap recipe command lines. You can use Python lines both at the recipe level and in build commands. Again, just make sure the indent indicates command blocks.

To learn using Python start at the Python web site: http://www.python.org/doc/

Using Aap variables in Python

You can use all the Aap variables in Python code. You don't use the "$", Python syntax does not use a dollar for variables.

When using a scope name, as in "_recipe.var", the variable otherwise looks the same as in Aap commands. But when omitting a scope name it works differently. While "$var" in Aap searches for "var" in the "_no" scope, this does not happen for Python. Python only looks in the current scope. Thus you must explicitly specify "_no.var" in Python code to get the same effect as "$var" in an Aap command.

In the following example a local variable "system" is used:

        CFLAGS = -DFOO
        all:
            system = $OSTYPE
            @if system == "posix":
            @   flags = _recipe.CFLAGS

The same thing could be done by accessing $OSTYPE directly from Python:

        CFLAGS = -DFOO
        all:
            @if _no.OSTYPE == "posix":
            @   flags = _recipe.CFLAGS

Using Python Expressions

In an assignment, command arguments and most other places a Python expression can be used in backticks. Expanding this is done before expanding $VAR items, because this allows the possibility to use the Python expression to result in the name of a variable. Example:

        foovaridx = 5
        Foo = $Src`foovaridx`

Is equivalent to:

        Foo = $Src5

The result of the Python expression in backticks should be a string or a list of strings. A list is automatically converted to a white-separated string of all list items.

A Python expression cannot be used for the variable name in an assignment. This doesn't work:

        `varname` = this does not work

If you really need this, use a Python command instead:

        @eval(varname + ' = "value"')

When using a function from a module, it must be imported first. Example:

        @from httplib import HTTP
        Connection = `HTTP('www.microsoft.com')`

For your convenience these things are imported for you already:

        from glob import glob
        from RecPython import *

The RecPython module defines the Python functions listed in Chapter 41, A-A-P Python functions.

A backtick in the Python expression has to be escaped to avoid it being recognized as the end of the expression:

        CMD = `my_func("$(`)grep -l foo *.c$(`)")`

contains the Python expression:

        my_func("`grep -l foo *.c`")

In the result of the Python expression $ characters are doubled, to avoid them being interpreted as the start of a variable reference. Otherwise Python expressions with arbitrary results would always have to be filtered explicitly. When the resulting string is used the $$ will be reduced to a single $ again.

The result of the expression is used as a string in place of the expression and the backticks. Example:

        Foo = foo/`glob("*.tmp")`

Would be equivalent to:

        Foo = foo/one.tmp two.tmp

Note that "foo/" is only prepended to the whole result, not each white-separated item. If you do want rc-style expansion, use two commands:

        TT = `glob("*.tmp")`
        Foo = foo/$*TT

Equivalent to:

        Foo = foo/one.tmp foo/two.tmp

Note that a following attribute is only attached to the last item resulting from the Python expression.

        Source = `glob('*.c')` {check = md5}

Can be equivalent to:

        Source = foo.c bar.c {check = md5}

To apply it to all items, use the :attr command:

        Source = `glob('*.c')`
        :attr {check = md5} $Source

Watch out for unexpected results when rc-style expansion is done for $*VAR. Example:

        VAR = one two
        Foo = $*VAR/`glob("*.tmp")`

Would result in:

        Foo = one/one.tmp two/one.tmp two.tmp

because the `` part is expanded first, thus the assignment is executed like:

        Foo = $*VAR/one.tmp two.tmp

The backticks for a Python expression are also recognized inside quotes. Thus you need to escape the special meaning there:

        Foo = "this$(`)file" that$(`)file

Backtick expressions can be used inside a string if you really need this:

        DIR = /home/foo /home/bar
        :print "`DIR + "/fi le"`"

results in:

        "/home/foo /home/bar/fi le"

Compare this to:

        :print "$*DIR/fi le"

which results in:

        "/home/foo/fi le" "/home/bar/fi le"

Python Block

A block of Python commands is started with a :python command. If no terminator string is specified the python code ends where the indent is equal to or less than the :python command:

    Source = foo.c bar.c
    :python
        for i in items:
            Source = Source + " " + i
    Target = foo

Optionally a terminator string may be specified. The indent of the Python code may then drop below the indent of the :python command.

The terminator cannot contain white space. A comment may follow. The Python block continues until the terminator string is found in a line by itself. It may be preceded and followed by white space and a comment may follow. Example:

    @if ok:
        :print finding include files
        :python EOF         # start of the Python block
    include = glob("include/*.c")
                EOF         # end of the Python block

Useful Python Items

A list of Python functions defined by Aap can be found in the reference manual, Chapter 41, A-A-P Python functions.

        VAR = `os.environ.get('VAR', 'default')`

Obtain environment variable $VAR. If it is not set use "default".

        @os.environ["PATH"] = mypath

Set an environment variable.

        files = `glob("*.ext")`

Obtain a list of files matching "*.ext". Aap will take care of turning the list that glob() returns into a string, using quotes where needed.

The difference with using "*.ext" directly is that the expansion is done right here, not later when $files is used. The catch with using glob() here is that when a file name contains a wildcard character it may be expanded again. So long as that expansion fails or matches the same file name it is still OK, but it becomes rather unpredictable. Use wildescape() when needed.

        choice = `raw_input("Enter the value: ")`

Prompt the user to enter a value.

        tempfile = `tempfname()`

Get a file name to use for temporary files. The file will not exist. See tempfname().

If you create it you need to make sure it is deleted afterwards. Example:

        tempfile = `tempfname()`
        @try:
          :print >$tempfile  start of file
          :print >>$tempfile $this variable may not exist and cause an error
          :cat $tempfile
        @finally:
          # this is executed whether there is an error or not
          :del $tempfile

Exception handling can be useful to handle situations where you know something might fail. Example:

     @try:
         :sys links -dump exec.html >exec.txt
     @except:
         :sys lynx -dump -nolist exec.html >exec.txt

This will execute the "links" command. If this fails for some reason (e.g., because a new version of "links" does not support the "-dump" argument) then the "lynx" command will be used.