Chapter 34. Variables and Scopes

Using Variables

A variable value is normally a string. The meaning of the value depends on where it is used. For example, "*" is interpreted as a wildcard when a variable is used where a file name is expected. Thus the wildcard is not expanded in the assignment. If you need it, use a Python expression to expand wildcards.

When using Python you can assign any type of value to a variable. Only a few types are supported for variables used in Aap commands:

String 
Integer or Longconverted to a string
ExpandVar objectused for delayed expansion of variables

In Python code the ExpandVar object needs to be expanded before you can use the value it contains. Use the var2string() function for that.

Normally using $VAR gets what you want. Aap will use the kind of quoting expected and add attributes when needed. This depends on the place where the variable is used. However, when you want something else, this can be specified:

$vardepends on where it's used
  
$?varwhen the variable is not set or defined use an empty string instead of generating an error
  
$-varwithout attributes (may collapse white space)
$+varwith attributes
  
$*var use rc-style expansion (may collapse white space)
  
$/varslashes changed to backslashes (for MS-Windows commands)
  
$=varno quotes or backslashes
$'varaap quoted (using ' and/or " where required, no backslashes)
$"varquoted with " (doubled for a literal ")
$\varspecial characters escaped with a backslash
$!vardepends on the shell, either like $'var or $"var

In most places $var is expanded as $+'var (with attributes, using ' and " for quoting). The exceptions are:

:sys$-!varno attributes, shell quoting
$n in $(v[$n])$-=varno attributes, no quoting
:del$-'varno attributes, normal quoting

The quoted variables don't handle the backslash as a special character. This is useful for MS-Windows file names. Example:

        prog : "dir\file 1.c"
                :print $'source

Results in: "dir\file 1.c"

Be careful with using "$\var" and quotes, you may not always get what you wanted.

RC-style expansion

RC-style expansion means that each item in a variable is concatenated to the item immediately before and after the variable. Example:

        var = one two three
        :print dir/$*var

Results in:

        dir/one dir/two dir/three 

For the expansion the variable is used as a list of white-separated items. Quotes can be used to include white space in an item. Use double quotes around a single quote and single quotes around a double quote. Escaping the meaning of quotes with a backslash is not supported.

When using rc-style expansion of a variable that is empty, the result is empty. Example:

        aa = 
        :print bla$*aa

This prints an empty line. The "bla" is omitted, because the rc-style expansion has zero items.

When concatenating variables and using rc-style expansion, the attributes of the last variable overrule the identical attributes of a previous one.

        v1 = foo {check = 1}
        v2 = bar {check = 2}
        vv = $*v1$v2

Is equivalent to:

        vv = foobar{check = 1}{check = 2}

When using rc-style expansion, quotes will not be kept as they are, but removed and re-inserted where used or necessary. Example:

        foo: "file 1.c" foo.c
                :print "dir/$*source"

Results in:

         "dir/file 1.c" "dir/foo.c" 

Variable Indexing

To get one item out of a variable that is a list of items, use an index number in square brackets. Parenthesis or curly braces must be used around the variable name and the index. The first item is indexed with zero. Example:

        BAR = beer coffee cola
        :print $(BAR[0])

        BAR_ONE = $(BAR[2])
        :print $BAR_ONE

Results in:

        beer
        cola 

Using an index for which no item exists gives an empty result. When $MESSAGE includes "warning" a message is printed about this.

Using Scopes

A dot is considered part of the variable name. It separates the scope name from the variable name within that scope. However, a trailing dot is not part of the variable name, so that this works:

          :print $result.     # print the result

In Python code you need to explicitly specify the scope name. When no scope name is given only the local scope is used. To get the equivalent of an Aap command that does not specify a scope, you need to use the "_no" scope in Python. The same example as above but now with a Python expression looks like this:

          :print `_no.result`.      # print the result

Predefined Scopes

Aap defines a scope for each recipe and each block of commands.

A user may also define a specific scope, see below. These scope names must start with an alphabetical name. Scope names starting with an underscore are used for predefined scopes.

Each time a block of commands is executed a new scope is created. Thus when executing the commands for a dependency a second time, its scope will not contain items from the first time.

A variable may exist in several scopes with a different value. To specify which scope is to be used, a scope name is prepended before the variable name, using a dot to separate the two.

These scope specifiers can be used to access a specific scope:

_recipeThe current recipe. Useful in build commands that are defined in the recipe.
_topThe toplevel recipe. This can be regarded as the global scope.
_defaultThe scope of default values, after the defaults settings have been done, but before reading user or system startup recipes. Cannot be used in the recipe that sets the default settings.
_startThe scope of startup values, as it was before reading the toplevel recipe. Cannot be used in the recipe that sets the default settings and in the startup recipes.
_argThe scope of variables set on the command line. Can be used to obtain the values set when Aap was executed or arguments of the :execute command. Although the scope is writable, thus you can mess it up...
_parentThe parent recipe. Only valid in a child recipe.
_callerThe scope of the command block that invoked the current command block. Can only be used in command blocks of dependencies, rules, actions and :tree.

These scope specifiers can be used to search scopes to find a variable. The first scope in which the variable exists is used.

_noNo scope, equal to leaving out the scope specifier in recipe commands, but required in Python commands. First looks in the current scope, then "_stack" and then "_tree".
_stackUses the scope of the command block that invoked the current scope, the command blocks that invoked that scope, and further up the call stack. Excludes the toplevel. Can only be used in command blocks of dependencies, rules and actions.
_treeUses the scope of the current recipe, its parent, the parent of its parent, etc., up to the toplevel. In the toplevel recipe it is equal to "_top".
_upFirst uses "_stack" and then "_tree", but excludes the current scope.

These are the scopes searched for a variable with the "_up" scope when it is used in a build command block:

  1. Invoking command blocks. That is, the scope of the command block that invoked the current command block with an :update command or because of a dependency. Then the scope of the command block that invoked that command block, and so on. This excludes the toplevel.

  2. The recipe in which the command block was defined, its parent and so on. This goes on until and including the toplevel.

This is used both for reading a variable and assigning a new value. It is an error when assigning a new value to a variable that does not exist.

For the commands of an action the sequence is slightly different:

  1. Invoking command blocks. That is, the scope of the command block that invoked the action with a :do command. Then the scope of the command block that invoked that command block, and so on. This excludes the toplevel.

  2. The recipe in which the ":do" command was invoked, its parent and so on. This goes on until and including the toplevel.

  3. The recipe in which the action was defined, its parent and so on. This goes on until the toplevel.

The "_no" scope is used for a variable in recipe commands without a specified scope. Thus these two are equivalent:

          :print $foobar
          :print $_no.foobar

But in Python commands a variable without a specified scope is always in the local scope. You must use "_no" to get the same effect:

          foo = $bar         # finds "bar" in local or "_up" scope
          @foo = bar         # finds "bar" in local scope only
          @foo = _no.bar     # finds "bar" in local or "_up" scope

When reading a variable with the "_no" scope it is first looked up in the local scope. If it does not exist, the "_stack" and "_tree" scopes are used, as explained above.

When writing a variable without a specified scope it is always put in the local scope. A specific situation where this may lead to an unexpected result is appending:

          foo += something

This is equivalent to:

          foo = $foo something

This obtains the value of "foo" from the first scope where it is defined, but it is set in the current scope. To change the variable where it is defined use the "_no" scope explicitly:

          _no.foo += something

User Scopes

The user can define a new scope by assigning a value to a variable, using the scope name:

        s_debug.foo = xxx

This creates the scope "s_debug" if it didn't exist yet. The variable "foo" within that scope is assigned the value "xxx".

The scope name must start with an alphabetic character. Following characters may be letters, digits and the underscore.

A user defined scope is only used when explicitly specified. The "_no" and "_up" scopes do not use it.

The scope can be accessed from everywhere, except recipes that create a new toplevel scope have their own set of user defined scopes. That is when using :execute or ":child {nopass}". ":execute {pass}" and :child do share the user scopes.

There cannot be a scope name and a variable with the same name. This applies to variables in ALL scopes! Thus when you have a scope "foo" in one place, you cannot use the variable "foo" anywhere else. The only exception is that you can use the variable "foo" in scopes that have been abandoned when the user scope "foo" is created, but that is tricky.

Recommendation: Let user scope names start with "s_".

A user scope can be specified for a dependency:

        s_foo.OPTIMIZE = 4
        ...
        foo : {scope = s_foo} foo.c
            :do build $source

A user scope can be specified for a rule:

        :rule %.a : {scope = s_aaa} %.b

A user scope can be specified for an action:

        :do foobar {scope = s_some} foo.bar

Variables In Build Commands

A dependency and a rule can have a list of commands. For these commands the following variables are available:

$sourceThe list of input files as a string.
$source_listThe list of input files as a Python list.
$source_dlOnly for use in Python commands: A list of dictionaries, each input item is one entry.
$dependThe list of dependencies (source files plus virtual dependencies) as a string.
$depend_listThe list of dependencies (source files plus virtual dependencies) as a Python list.
$depend_dlOnly for use in Python commands: A list of dictionaries, each dependency item is one entry.
$targetThe list of output files as a string.
$target_listThe list of output files as a Python list.
$target_dlOnly for use in Python commands: A list of dictionaries, each output item is one entry.
$buildtargetThe name of the target for which the commands are executed. It is one of the items in $target.
$matchFor a rule: the string that matched with %

Example:

        doit {virtual}:
            :print building $target
        prog : "main file.c" doit
            :print building $target from $source

Results in:

          building doit{virtual=1}
          building prog from "main file.c" 

Note that quoting of expanded $var depends on the command used.

The Python lists $source_list and $target_list can be used to loop over each item. Example:

        $OUT : foo.txt
            @for item in target_list:
                :print $source > $item

Note the difference between $source and $depend: $source only contains real files, $depend also contains virtual dependencies.

The list of dictionaries can be used to access the attributes of each item. Each dictionary has an entry "name", which is the (file) name of the item. Other entries are attributes. Example:

        prog : file.c {check = md5}
            @print source_dl[0]["name"], source_dl[0]["check"]

Results in: file.c md5