The _configuration_ command is used to check if the requiremements for working on a project are met and to store the information. The parameters are then stored for use by other commands, such as the build command.
=== Using persistent data
==== Sharing data with the build
The configuration context is used to store data which may be re-used during the build. Let's begin with the following example:
<1> Store the option _foo_ into the variable _env_ (dict-like structure)
<2> Configuration routine used to find the program _touch_ and to store it into _ctx.env.TOUCH_ footnote:['find_program' may use the same variable from the OS environment during the search, for example 'CC=gcc waf configure']
<3> Print the value of _ctx.env.FOO_ that was set during the configuration
<4> The variable _$\{TOUCH}_ corresponds to the variable _ctx.env.TOUCH_.
<1> Output of the configuration test _find_program_
<2> The value of _TOUCH_
<3> Command-line used to create the target 'foo.txt'
The variable _ctx.env_ is called a *Configuration set*, and is an instance of the class 'ConfigSet'. The class is a wrapper around Python dicts to handle serialization. For this reason it should be used for simple variables only (no functions or classes). The values are stored in a python-like format in the build directory:
[source,shishell]
---------------
$ tree
build/
|-- foo.txt
|-- c4che
| |-- build.config.py
| `-- _cache.py
`-- config.log
$ cat build/c4che/_cache.py
FOO = 'abcd'
PREFIX = '/usr/local'
TOUCH = '/usr/bin/touch'
---------------
NOTE: Reading and writing values to _ctx.env_ is possible in both configuration and build commands. Yet, the values are stored to a file only during the configuration phase.
==== Configuration set usage
We will now provide more examples of the configuration set usage. The object *ctx.env* provides convenience methods to access its contents:
Although these methods are provided by the context class _waflib.Configure.ConfigurationContext_, they will not appear on it in http://docs.waf.googlecode.com/git/apidocs_16/index.html[API documentation]. For modularity reasons, they are defined as simple functions and then bound dynamically:
<2> Use the decorator to bind the method _hi_ to the configuration context and build context classes. In practice, the configuration methods are only used during the configuration phase.
<3> Decorators are simple python function. Python 2.3 does not support the *@* syntax so the function has to be called after the function declaration
<4> Use the method previously bound to the configuration context class
The execution will produce the following output:
[source,shishell]
---------------
$ waf configure
→ hello, world!
'configure' finished successfully (0.005s)
---------------
==== Loading and using Waf tools
For efficiency reasons, only a few configuration methods are present in the Waf core. Most configuration methods are loaded by extensions called *Waf tools*.
The main tools are located in the folder +waflib/Tools+, and the tools in testing phase are located under the folder +waflib/extras+.
Yet, Waf tools may be used from any location on the filesystem.
We will now demonstrate a very simple Waf tool named +dang.py+ which will be used to set 'ctx.env.DANG' from a command-line option:
<1> First the tool is imported as a python module, and then the method _configure_ is called by _load_
<2> The tools loaded during the configuration will be loaded during the build phase
==== Multiple configurations
The 'conf.env' object is an important point of the configuration which is accessed and modified by Waf tools and by user-provided configuration functions. The Waf tools do not enforce a particular structure for the build scripts, so the tools will only modify the contents of the default object. The user scripts may provide several 'env' objects in the configuration and pre-set or post-set specific values:
[source,python]
---------------
def configure(ctx):
env = ctx.env <1>
ctx.setenv('debug') <2>
ctx.env.CC = 'gcc' <3>
ctx.load('gcc')
ctx.setenv('release', env) <4>
ctx.load('msvc')
ctx.env.CFLAGS = ['/O2']
print ctx.all_envs['debug'] <5>
---------------
<1> Save a reference to 'conf.env'
<2> Copy and replace 'conf.env'
<3> Modify 'conf.env'
<4> Copy and replace 'conf.env' again, from the initial data
<5> Recall a configuration set by its name
=== Exception handling
==== Launching and catching configuration exceptions
Configuration helpers are methods provided by the conf object to help find parameters, for example the method 'conf.find_program'
[source,python]
---------------
top = '.'
out = 'build'
def configure(ctx):
ctx.find_program('some_app')
---------------
When a test cannot complete properly, an exception of the type 'waflib.Errors.ConfigurationError' is raised. This often occurs when something is missing in the operating system environment or because a particular condition is not satisfied. For example:
[source,shishell]
---------------
$ waf
Checking for program some_app : not found
error: The program some_app could not be found
---------------
These exceptions may be raised manually by using 'conf.fatal':
[source,python]
---------------
top = '.'
out = 'build'
def configure(ctx):
ctx.fatal("I'm sorry Dave, I'm afraid I can't do that")
---------------
Which will display the same kind of error:
[source,shishell]
---------------
$ waf configure
error: I'm sorry Dave, I'm afraid I can't do that
$ echo $?
1
---------------
Here is how to catch configuration exceptions:
// configuration_exception
[source,python]
---------------
top = '.'
out = 'build'
def configure(ctx):
try:
ctx.find_program('some_app')
except ctx.errors.ConfigurationError: <1>
self.to_log('some_app was not found (ignoring)') <2>
---------------
<1> For convenience, the module _waflib.Errors_ is bound to _ctx.errors_
<2> Adding information to the log file
The execution output will be the following:
[source,shishell]
---------------
$ waf configure
Checking for program some_app : not found
'configure' finished successfully (0.029s) <1>
$ cat build/config.log <2>
# project configured on Tue Jul 13 19:15:04 2010 by
from /tmp/configuration_exception: The program ['some_app'] could not be found
some_app was not found (ignoring) <3>
---------------
<1> The configuration completes without errors
<2> The log file contains useful information about the configuration execution
<3> Our log entry
Catching the errors by hand can be inconvenient. For this reason, all *@conf* methods accept a parameter named 'mandatory' to suppress configuration errors. The code snippet is therefore equivalent to:
[source,python]
---------------
top = '.'
out = 'build'
def configure(ctx):
ctx.find_program('some_app', mandatory=False)
---------------
As a general rule, clients should never rely on exit codes or returned values and must catch configuration exceptions. The tools should always raise configuration errors to display the errors and to give a chance to the clients to process the exceptions.
==== Transactions
Waf tools called during the configuration may use and modify the contents of 'conf.env' at will. Those changes may be complex to track and to undo. Fortunately, the configuration exceptions make it possible to simplify the logic and to go back to a previous state easily. The following example illustrates how to use a transaction to to use several tools at once:
[source,python]
---------------
top = '.'
out = 'build'
def configure(ctx):
for compiler in ('gcc', 'msvc'):
try:
ctx.env.stash()
ctx.load(compiler)
except ctx.errors.ConfigurationError:
ctx.env.revert()
else:
break
else:
ctx.fatal('Could not find a compiler')
---------------
Though several calls to 'stash' can be made, the copies made are shallow, which means that any complex object (such as a list) modification will be permanent. For this reason, the following is a configuration anti-pattern:
[source,python]
---------------
def configure(ctx):
ctx.env.CFLAGS += ['-O2']
---------------
The methods should always be used instead:
[source,python]
---------------
def configure(ctx):
ctx.env.append_value('CFLAGS', '-O2')
---------------
////
To conclude this chapter on the configuration, we will now insist a little bit on the roles of the configuration context and of the configuration set objects. The configuration context is meant as a container for non-persistent data such as methods, functions, code and utilities. This means in particular that the following is an acceptable way of sharing data with scripts and tools:
In practice, values are frequently needed in the build section too. Adding the data to 'conf.env' is therefore a logical way of separating the concerns between the code (configuration methods) and the persistent data.