As the Waf file is meant to be a generic utility for building projects, project-specific details are best kept and versioned in files residing along with the project source code. These files are modules written in the Python programming language and are named *wscript*. Although they can contain any Python code, Waf can use specific functions and classes defined in them. The next sections will explore a particularly useful concept called *function commands*.
Waf is typically run in a command-line interpreter called terminall or shell; there are three main ways of passing data to the Waf process to tell it to do something:
<2> Waf is instructed to run the two commands called *distclean* and *configure* in this specific order. Commands are passed after the 'waf' file and contain no *-* or *=* characters
<3> The *-j1* and *--help* elements are command-line options; they are optional and their position or order in the list of arguments is not meant to be significant.
As previously mentioned, commands are executed in the order defined on the command-line. A wscript file may thus provide an arbitrary amound of commands in the same _wscript_ file:
NOTE: Command functions are passed a new context object when they are called; the class for that object is command-specific: ConfigureContext for configure, BuildContext for build, OptionContext for option, and Context for any other command.
Although a Waf project must contain a top-level _wscript_ file, the contents may be split into several sub-project files. We will now illustrate this concept on a small project:
[source,shishell]
---------------
$ tree
|-- src
| `-- wscript
`-- wscript
---------------
The commands in the top-level _wscript_ will call the same commands from a subproject _wscript_ file by calling a context method named _recurse_:
The following concepts help to structure a Waf project:
. Project directory: directory containing the source files that will be packaged and redistributed to other developers or to end users
. Build directory: directory containing the files generated by the project (configuration sets, build files, logs, etc)
. System files: files and folders which do not belong to the project (operating system files, etc)
The predefined command named _configure_ is used to gather and store the information about these folders.
We will now extend the example from the previous section with the following top-level wscript file:
// execution_configure
[source,python]
---------------
top = '.' <1>
out = 'build_directory' <2>
def configure(ctx): <3>
print('→ configuring the project in ' + ctx.path.abspath())
def ping(ctx):
print('→ ping from ' + ctx.path.abspath())
ctx.recurse('src')
---------------
<1> string representing the project directory. In general, top is set to '.', except for some proprietary projects where the wscript cannot be added to the top-level, top may be set to '../..' or even some other folder such as '/checkout/perforce/project'
<2> string representing the build directory. In general, it is set to 'build', except for some proprietary projects where the build directory may be set to an absolute path such as '/tmp/build'. It is important to be able to remove the build directory safely, so it should never be given as '.' or '..'.
<3> the _configure_ function is called by the 'configure' command
The script in 'src/wscript' is left unchanged:
[source,python]
---------------
def ping(ctx):
print('→ ping from ' + ctx.path.abspath())
---------------
The execution output will be the following:
////
$ waf ping
→ ping from /tmp/execution_configure
→ ping from /tmp/execution_configure/src
'ping' finished successfully (0.001s)
$ cd src
$ waf ping
→ ping from /tmp/execution_configure/src
'ping' finished successfully (0.001s)
$ cd ..
////
[source,shishell]
---------------
$ cd /tmp/execution_configure <1>
$ tree
|-- src
| `-- wscript
`-- wscript
$ waf configure <2>
→ configuring the project in /tmp/execution_configure
'configure' finished successfully (0.021s)
$ tree -a
|-- build_directory/ <3>
| |-- c4che/ <4>
| | |-- build.config.py <5>
| | `-- _cache.py <6>
| `-- config.log <7>
|--.lock-wafbuild <8>
|-- src
| `-- wscript
`-- wscript
$ waf ping
→ ping from /tmp/execution_configure
→ ping from /tmp/execution_configure/src
'ping' finished successfully (0.001s)
$ cd src
$ waf ping <9>
→ ping from /tmp/execution_configure
→ ping from /tmp/execution_configure/src
'ping' finished successfully (0.001s)
---------------
<1> To configure the project, change to the directory containing the top-level project file
<2> The execution is called by calling _waf configure_
<3> The build directory was created
<4> The configuration data is stored in the folder 'c4che/'
<5> The command-line options and environment variables in use are stored in 'build.config.py'
<6> The user configuration set is stored in '_cache.py'
<7> Configuration log (duplicate of the output generated during the configuration)
<8> Hidden file pointing at the relevant project file and build directory
<9> Calling _waf_ from a subfolder will execute the commands from the same wscript file used for the configuration
NOTE: _waf configure_ is always called from the directory containing the wscript file
==== Removing generated files (the _distclean_ command)
A command named _distclean_ is provided to remove the build directory and the lock file created during the configuration. On the example from the previous section:
[source,shishell]
---------------
$ waf configure
→ configuring the project in /tmp/execution_configure
'configure' finished successfully (0.001s)
$ tree -a
|-- build_directory/
| |-- c4che/
| | |-- build.config.py
| | `-- _cache.py
| `-- config.log
|--.lock-wafbuild
`-- wscript
$ waf distclean <1>
'distclean' finished successfully (0.001s)
$ tree <2>
|-- src
| `-- wscript
`-- wscript
---------------
<1> The _distclean_ command definition is implicit (no declaration in the wscript file)
<2> The tree is reverted to its original state: no build directory and no lock file
The behaviour of _distclean_ is fairly generic and the corresponding function does not have to be defined in the wscript files. It may be defined to alter its behaviour though, see for example the following:
[source,python]
---------------
top = '.'
out = 'build_directory'
def configure(ctx):
print('→ configuring the project')
def distclean(ctx):
print(' Not cleaning anything!')
---------------
Upon execution:
[source,shishell]
---------------
$ waf distclean
Not cleaning anything!
'distclean' finished successfully (0.000s)
---------------
==== Packaging the project sources (the _dist_ command)
The _dist_ command is provided to create an archive of the project. By using the script presented previously:
// execution_dist
[source,python]
---------------
top = '.'
out = 'build_directory'
def configure(ctx):
print('→ configuring the project in ' + ctx.path.abspath())
---------------
Execute the _dist_ command to get:
[source,shishell]
---------------
$ cd /tmp/execution_dist
$ waf configure
→ configuring the project in /tmp/execution_dist
'configure' finished successfully (0.005s)
$ waf dist
New archive created: noname-1.0.tar.bz2 (sha='a4543bb438456b56d6c89a6695f17e6cb69061f5')
'dist' finished successfully (0.035s)
---------------
By default, the project name and version are set to 'noname' and '1.0'. To change them, it is necessary to provide two additional variables in the top-level project file:
[source,python]
---------------
APPNAME = 'webe'
VERSION = '2.0'
top = '.'
out = 'build_directory'
def configure(ctx):
print('→ configuring the project in ' + ctx.path.abspath())
---------------
Because the project was configured once, it is not necessary to configure it once again:
[source,shishell]
---------------
$ waf dist
New archive created: webe-2.0.tar.bz2 (sha='7ccc338e2ff99b46d97e5301793824e5941dd2be')
'dist' finished successfully (0.006s)
---------------
More parameters may be given to alter the archive by adding a function 'dist' in the script:
--targets=TARGETS task generators, e.g. "target1,target2"
step options:
--files=FILES files to process, by regexp, e.g. "*/main.c,*/test/main.o"
install/uninstall options:
--destdir=DESTDIR installation root [default: '']
-f, --force force file installation
---------------
Accessing a command-line option is possible from any command. Here is how to access the value _prefix_:
[source,python]
---------------
top = '.'
out = 'build_directory'
def configure(ctx):
print('→ prefix is ' + ctx.options.prefix)
---------------
Upon execution, the following will be observed:
[source,shishell]
---------------
$ waf configure
→ prefix is /usr/local/
'configure' finished successfully (0.001s)
---------------
To define project command-line options, a special command named _options_ may be defined in user scripts. This command will be called once before any other command executes.
print('→ the value of foo is %r' % ctx.options.foo)
---------------
Upon execution, the following will be observed:
[source,shishell]
---------------
$ waf configure --foo=test
→ the value of foo is 'test'
'configure' finished successfully (0.001s)
---------------
The command context for options is a shortcut to access the optparse functionality. For more information on the optparse module, consult the http://docs.python.org/library/optparse.html[Python documentation]
=== The _build_ commands
==== Building targets (the _build_ command)
The 'build' command is used for building targets. We will now create a new project in '/tmp/execution_build/', and add a script to create an empty file +foo.txt+ and then copy it into another file +bar.txt+:
The _clean_ command is used to remove the information about the files and targets created during the build. It uses the same function _build_ from the wscript files so there is no need to add a function named _clean_ in the wscript file.
After cleaning, the targets will be created once again even if they were up-to-date: