Building a project - Introduction to Tasks

The function build indicates the start of the actual construction of the software, it is used for two main purposes:

Subdirectory recursion

The function build may reference build functions in wscript present in sub folders, for example:

.
`-- wscript
|-- src
|   `-- wscript
			

The top-level wscript may indicate there is a build function to execute in src/wscript using:

def build(bld):
	bld.add_subdirs('src')
			

The wscript files that are not on the top-level often contain a large build function, with no additional configuration or command-line options. In this case the file only contain one method and the indentation is an annoyance. For this reason, the wscript_build files contain the body of a build function defined in a wscript file. A folder may only contain either a wscript or a wscript_build but not both.

Waf task declaration

The process of building a piece of software require the transformation of source code (input) into the result (output). Since the source code is usually split in various files, the construction of the result requires the processing of each of the files. Several new concepts derive from this:

  • Intermediate results (object files) may be produced to speed up the build (no need to rebuild everything when small changes occur)
  • Source files may be processed in parallel to speed up the build
  • Source files may be transformed into other source files (sequences of transformations)

In a more general scope, the input necessary for a build may come from arbitrary data instead of files (database, commands to execute). The concepts of input, output and transformation and the transformation constraints (in parallel, in sequence) remain identical.

In Waf, the operation of transforming data is performed by tasks, which are instances of the class TaskBase (in the Waf library). The tasks in the following example illustrate the declaration of Tasks from the base class. The task instances must be located in a build context (the function build) to be taken into account in the build phase, and to obtain the reference on the source and build folders:

srcdir = '.'
blddir = 'build'

def set_options(opt):
    pass
def configure(conf):
    pass

import Task
class task_test_a(Task.TaskBase):
	pass
class task_test_b(Task.TaskBase):
	after = 'task_test_a'

def build(bld):
    tb = task_test_b()
    ta = task_test_a()
			

The execution trace will be the following:

$ waf configure
Configuration finished successfully (00:00:00); project is now ready to build.
$ waf
[0/2] task_test_a
[1/2] task_test_b
Compilation finished successfully (00:00:00)
			

Task generator encapsulation

In practice, when lots of tasks are declared one by one, scripts tend to become complicated, and contain a lot of redundant code. Special objects named task generators are provided to facilitate the creation of several tasks at once. In the following example, a task generator is used to create the compilation and link tasks needed by a c++ project.

srcdir = '.'
blddir = 'build'

def configure(conf):
    conf.check_tool('gcc')

def build(bld):
    bld.new_task_gen(features='cc cprogram', source='main.c test.c', target='myapp')
			

This example provides a modified configure function, which is the topic of the next section. The task generator system and the default generators will be reviewed in detail in Chapter 6, Task encapsulation using task generators.