Bare metal WAF¶
waf is a configure / build / install system.
It can do the jobs of autotools
and make
, so instead of typing (say):
./configure
make
make install
you can type (say):
./waf configure
./waf build
./waf install
You don’t need autotools
or make
installed. The full waf
binary is
90K and usually sits in your source code repository.
waf uses a Python configuration file so you can use Python code to define your
configure, build and install rules rather than the obscure make
syntax and
funny variable names.
Among other projects, SAMBA uses waf for its entire configure / build / install system.
There is a comprehensive waf manual.
What’s the downside? The waf build system (like - say - make
) works at a
fairly high and abstract level, so it can be hard to understand the flow of
control. It can sometimes be hard to find the part of waf that you want.
This page has notes to myself about how waf works.
Overview¶
The heart of the waf process is of course the build.
We’re likely going to do this to build our project:
./waf configure
./waf build
We use the configure step to check for dependencies and set up any variables we need to define for the build step.
The build step receives the result of the configure step, and sets up build rules. The build rules form a directed acyclic graph. At the end of the build step waf will execute the build steps using the dependency information from the DAG to execute the build steps in an efficient order.
A taster¶
Let’s say you have a project myproject
. It’s a directory called
myproject
. It has a single C file myprogram.c
.
Download the waf
binary to the myproject
from the waf site.
You invoke waf thus:
cd myproject
./waf
and you’ll get this:
Waf: Run from a directory containing a file named 'wscript'
the
waf
program loads a configuration file calledwscript
waf loads the wscript file, and executes the code inside in order to do stuff.
Let’s pretend our project creates a file called foo
and then copies it to
bar
. The wscript might look like this:
# the whole wscript
def configure(conf_ctx):
# Check we have 'touch'
conf_ctx.find_program('touch', VAR='TOUCH')
# And 'cp'
conf_ctx.find_program('cp', VAR='CP')
def build(build_ctx):
# Touch foo
build_ctx(rule='${TOUCH} ${TGT}', target='foo')
# Copy foo to bar
build_ctx(rule='${CP} ${SRC} ${TGT}', source='foo', target='bar')
We run the checks with ./waf configure
, and the build with ./waf build
.
It isn’t easy to see exactly how this works, and rest of this page will try to do that.
waf commands¶
You execute a waf command with .waf command-name [options]
You can define any command you like with something like this in the wscript file:
# the whole wscript file
def mycommand(ctx):
print('hello')
and:
./waf mycommand
giving:
hello
'mycommand' finished successfully (0.000s)
What happened? waf loaded the wscript file, looked for a callable object called
mycommand
, created a Context
instance ctx
, and passed it to the
callable thing as the first and only argument.
The ctx
instance contains information about the current waf run.
We investigate with a print in the wscript:
# the whole wscript file
def mycommand(ctx):
print(ctx)
giving:
<waflib.Context.Context object at 0x1011b60d0>
'mycommand' finished successfully (0.000s)
Special commands¶
The following commands are special to waf:
options
configure
distclean
clean
build
install
uninstall
dist
list
step
(there may be others I don’t know).
If you define functions (callables) with these names, they have default and
specialized behavior. For example, each of these commands receives its own
specialized Context object. We can show that for the the options
command.
# the whole wscript
def options(opt_ctx):
print(opt_ctx)
gives:
$ waf options
<waflib.Options.OptionsContext object at 0x1011b53d0>
Let’s make each of the commands print their context instance:
# the whole wscript
from __future__ import print_function
for command_name in (
'options',
'configure',
'distclean',
'clean',
'build',
'install',
'uninstall',
'dist',
'list',
'step',
):
exec('{0} = lambda ctx : print("In: {0} with", ctx)'.format(command_name))
Executing ‘options’ again:
$ waf options
In: options with <waflib.Options.OptionsContext object at 0x1011b55d0>
We can execute all these command in order by concatenating them at the command line to show they each get an instance of their own specialized Context:
$ waf options configure distclean clean build install uninstall dist list step
In: options with <waflib.Options.OptionsContext object at 0x1011b6750>
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
In: configure with <waflib.Configure.ConfigurationContext object at 0x1011b6a50>
'configure' finished successfully (0.017s)
In: distclean with <waflib.Context.Context object at 0x1011b6c10>
'distclean' finished successfully (0.000s)
In: build with <waflib.Build.CleanContext object at 0x1011b6d50>
'clean' finished successfully (0.008s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
In: build with <waflib.Build.BuildContext object at 0x1011b6f50>
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.002s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
In: build with <waflib.Build.InstallContext object at 0x1011c0210>
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'install' finished successfully (0.002s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
In: build with <waflib.Build.UninstallContext object at 0x1011c04d0>
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'uninstall' finished successfully (0.013s)
In: dist with <waflib.Scripting.Dist object at 0x1011c07d0>
New archive created: noname-1.0.tar.bz2 (sha='38a85b35879a5b0703142c3c27e765bafb7fd4b6')
'dist' finished successfully (0.051s)
In: build with <waflib.Build.ListContext object at 0x1011dcfd0>
'list' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
In: build with <waflib.Build.StepContext object at 0x1011e9550>
Add a pattern for the debug build, for example "waf step --files=main.c,app"
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'step' finished successfully (0.002s)
Some commands use the build callable instead of their own callable¶
You can see from the printout above that all of (clean, build, install,
uninstall, list, step) end up executing the ‘build’ command not their own
commands. If you want to specialize behavior of these commands, you’ll need to
put code in the build
function (callable) to check the cmd
attribute of
the build context to find which of these commands the build
function is
running. See “More build commands” in the waf book for more detail.
More on the configure command¶
Executing the configure
command will:
call the options command
set some default path information
This happens before your own configure
command gets executed, and even if
there is no configure
command defined. For example, if wscript is an empty
file:
$ waf configure
<waflib.Options.OptionsContext object at 0x1011b53d0>
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
No function configure defined in /Users/mb312/dev_trees/myproject/wscript
The default configure machinery has set the default ‘source’ (top) directory
and the ‘build’ (out) directory. top is (by default) the directory
containing the wscript file, and out is (by default) a subdirectory of top
called build
. You can customize these directories if you want, for example
by using the top
and out
global variables at the top of the wscript.
How the build gets built¶
See the waf book chapter called “Builds”.
A build consists of tasks. Tasks are instances of subclasses of the
waflib.Task.Task
class. But - where is waflib
?
Where is the waf code?¶
See “Local waflib folders” in the waf book.
The usual way to use waf is to download the waf
binary to your source code
tree, maybe checking the binary into your source control.
In this case, executing the ./waf
binary will automatically unpack the waf
code from within the binary to a specially named folder in the same directory as
the binary, with a name beginning with .waf-
. For example, after I have run
./waf configure
in the test project folder I get:
$ ls -A1
.lock-waf_darwin_build
.waf-1.7.13-5a064c2686fe54de4e11018d22148cfc
build
waf
wscript
The waflib
code is in this special .waf-*
directory:
$ ls .waf-1.7.13-5a064c2686fe54de4e11018d22148cfc/
waflib
As the waf book explains, it’s also possible to run waf with waflib
as a
standard folder in the source tree or elsewhere.
High level API for creating tasks¶
Usually we create tasks using a high-level API called task generators. You already saw these in our first wscript towards the top of this page. The task generator creates the task instances for us:
# the whole wscript
def configure(conf_ctx):
pass
def build(build_ctx):
build_ctx(rule='touch ${TGT}', target='foo')
See run_str Variable substitution below for more on the ${TGT}
string
substitution form.
The rule
is a system command that we can run, and the target
in this
case is a filename of a file we generate with the command.
From here on in, I’ll use the invocation waf distclean configure build
to
run the waf build. distclean
deletes all the generated build targets,
clearing the outputs of any previous waf run.
So:
$ waf distclean configure build
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
[1/1] foo: -> build/foo
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.024s)
The script generates an empty file build/foo
.
The call to build_ctx()
above is a very short piece of code that ends up
creating an instance of waflib.TaskGen.task_gen
. In due course this will
generate all the relevant Task
instances. Adpapting an example from the waf
book:
# the whole wscript
from __future__ import print_function
def configure(conf_ctx):
pass
def build(build_ctx):
tg = build_ctx(rule='touch ${TGT}', target='foo')
# This will show that ``tg`` is a ``task_gen`` instance
print('Type of tg is:', type(tg))
# ``tg.tasks`` is empty because the generator has not run yet
print('Task list before tg.post()', tg.tasks)
# ``tg.post()`` runs the generator and fills the task list
tg.post()
# The task list now contains a single task
print('Task list after tg.post()', tg.tasks)
print('Type of tg.tasks[0] is:', type(tg.tasks[0]))
so:
$ waf distclean configure build
'distclean' finished successfully (0.002s)
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
Type of tg is: <class 'waflib.TaskGen.task_gen'>
Task list before tg.post() []
Task list after tg.post() [
{task 4313537616: foo -> foo}]
Type of tg.tasks[0] is: <class 'waflib.Task.foo'>
[1/1] foo: -> build/foo
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.019s)
Nodes¶
Before we do some low-level API examples, we need some explanation of the waf concept of ‘nodes’.
‘nodes’ are waf objects representing files or directories on the filesystem. The files or directories may or may not exist at the time you create the node.
The waf book has a chapter devoted to “Node objects”.
Two node objects get automatically created and attached to the ‘Context’ of
every command - ‘path’ and ‘root’. Here we use a custom command mycommand
.
Because it’s a custom command (not one of the defined waf commands above), the
command receives an instance of the base Context
class. All command
contexts will have these node objects:
# the whole wscript
from __future__ import print_function
def mycommand(base_ctx):
print(type(base_ctx.path), repr(base_ctx.path))
print(type(base_ctx.root), repr(base_ctx.root))
giving:
$ waf mycommand
<class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject
<class 'waflib.Node.Nod3'> /
'mycommand' finished successfully (0.000s)
base_ctx.path
is a node pointing to the directory containing the executed
wscript file. base_ctx.root
is a node pointing to the root directory of the
filesystem containing the wscript.
Node objects contain various useful methods:
# the whole wscript
from __future__ import print_function
def mycommand(base_ctx):
# Show the attributes that are not private
print([attr for attr in dir(base_ctx.path) if not attr.startswith('_')])
gives:
$ waf mycommand
['abspath', 'ant_glob', 'ant_iter', 'bld_base', 'bld_dir', 'bldpath',
'cache_abspath', 'cache_isdir', 'cache_sig', 'change_ext', 'children',
'chmod', 'ctx', 'delete', 'evict', 'find_dir', 'find_node',
'find_or_declare', 'find_resource', 'get_bld', 'get_bld_sig', 'get_src',
'height', 'is_bld', 'is_child_of', 'is_src', 'listdir', 'make_node',
'mkdir', 'name', 'nice_path', 'parent', 'path_from', 'read', 'relpath',
'search', 'search_node', 'sig', 'srcpath', 'suffix', 'write']
'mycommand' finished successfully (0.001s)
The waf book goes into more detail about these methods.
One interesting method is abspath()
. It gives the absolute path to the
node as a string:
# the whole wscript
from __future__ import print_function
def mycommand(base_ctx):
print(base_ctx.path.abspath())
for output:
$ waf mycommand
/Users/mb312/dev_trees/myproject
'mycommand' finished successfully (0.000s)
Creating nodes¶
Here are some useful ordinary methods for creating nodes. By ‘ordinary’ I mean
methods that work for all command contexts (base Context
, BuildContext
,
OptionContext
etc).
.search(path)
: search for an already-created node forpath
.find_node(path)
: create a node forpath
if it exists on the filesystem, return None otherwise.make_node(path)
: create a node forpath
whetherpath
exists on the filesystem or not..find_dir(dirname)
: create a node fordirname
if it exists on the filesystem and is a directory, return None otherwise. This is just.find_node(path)
with an extra check thatpath
is in fact a directory.
waf creates some nodes automatically, but not many¶
When waf creates path
and root
nodes, it also creates some nodes for
other parts of the system file tree, but not all. This is in order not to waste
time reading the file system for nodes that may not be needed. Call this lazy
node creation.
Lazy node creation may come up in two situations. The first is when using
.search(path)
(above). This will only find nodes that are already
defined. For example, if you do build_ctx.path.search('waf')
at the
beginning of your build command, you will usually get None, even if the file
waf
does exist in the folder referred to by the path
node. You probably
want .find_node(path)
instead, which will read the filesystem and create
a node if the file exists.
Another place where lazy node creation is relevant is interpreting the
children
attribute of a directory node. children
is a dictionary giving
nodes corresponding to the contents of a directory. For efficiency, waf doesn’t
always read the filesystem to fill this structure, so you may need to create
nodes manually if they have not been read from the filesystem already:
# the whole wscript
from __future__ import print_function
import os
def mycommand(base_ctx):
# The children of the root directory
print('root node children', base_ctx.root.children)
# There are many more directories than found in the children
print('ls of root directory', os.listdir('/'))
This gives:
$ waf mycommand
root node children {'Users': /Users}
ls of root directory ['.dbfseventsd', '.DS_Store', '.file', '.fseventsd',
'.hotfiles.btree', '.Spotlight-V100', '.Trashes', '.vol', 'Applications',
'bin', 'cores', 'dev', 'Developer', 'etc', 'home', 'Library', 'mach_kernel',
'net', 'Network', 'opt', 'private', 'sbin', 'System', 'tmp', 'User Guides
And Information', 'Users', 'usr', 'var', 'Volumes']
'mycommand' finished successfully (0.000s)
Specialized noding for builds¶
This describes node stuff that only applies to the BuildContext
- the
context object passed to a build
command.
build and source nodes¶
The build context is a special context passed to the build
command.
The build also needs to know about the location of the directory containing the
source files, and the output directory to put the target files. To solve this,
the build context contains two extra nodes, bldnode
and srcnode
:
# the whole wscript
from __future__ import print_function
def configure(conf_ctx):
pass
def build(build_ctx):
print('build_ctx.path', type(build_ctx.path), repr(build_ctx.path))
print('build_ctx.root', type(build_ctx.root), repr(build_ctx.root))
print('build_ctx.srcnode', type(build_ctx.srcnode), repr(build_ctx.srcnode))
print('build_ctx.bldnode', type(build_ctx.bldnode), repr(build_ctx.bldnode))
so:
$ waf distclean configure build
'distclean' finished successfully (0.001s)
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.009s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
build_ctx.path <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject
build_ctx.root <class 'waflib.Node.Nod3'> /
build_ctx.srcnode <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject
build_ctx.bldnode <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject/build
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.003s)
The source node path may differ from the ‘path’ if you’ve customized the top or out directories:
# the whole wscript
from __future__ import print_function
top = 'tmp2'
out = 'tmp2/build2'
def configure(conf_ctx):
pass
def build(build_ctx):
print('build_ctx.path', type(build_ctx.path), repr(build_ctx.path))
print('build_ctx.root', type(build_ctx.root), repr(build_ctx.root))
print('build_ctx.srcnode', type(build_ctx.srcnode), repr(build_ctx.srcnode))
print('build_ctx.bldnode', type(build_ctx.bldnode), repr(build_ctx.bldnode))
We need to create the tmp2
directory in the project directory in order for
this not to raise an error:
mkdir tmp2
then:
$ waf distclean configure build
'distclean' finished successfully (0.000s)
Setting top to : /Users/mb312/dev_trees/myproject/tmp2
Setting out to : /Users/mb312/dev_trees/myproject/tmp2/build2
Are you certain that you do not want to set top="." ?
'configure' finished successfully (0.002s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/tmp2/build2'
build_ctx.path <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject
build_ctx.root <class 'waflib.Node.Nod3'> /
build_ctx.srcnode <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject/tmp2
build_ctx.bldnode <class 'waflib.Node.Nod3'> /Users/mb312/dev_trees/myproject/tmp2/build2
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/tmp2/build2'
'build' finished successfully (0.010s)
Iff you have a build context bldnode
, you can get the source node with
get_src
; if you have the build context srcnode
you can get the build
node with get_bld
:
# the whole wscript
from __future__ import print_function
top = 'tmp2'
out = 'tmp2/build2'
def configure(conf_ctx):
pass
def build(build_ctx):
print(repr(build_ctx.srcnode.get_bld()))
print(repr(build_ctx.bldnode.get_src()))
Output:
$ waf distclean configure build
'distclean' finished successfully (0.002s)
Setting top to : /Users/mb312/dev_trees/myproject/tmp2
Setting out to : /Users/mb312/dev_trees/myproject/tmp2/build2
Are you certain that you do not want to set top="." ?
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/tmp2/build2'
/Users/mb312/dev_trees/myproject/tmp2/build2
/Users/mb312/dev_trees/myproject/tmp2
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/tmp2/build2'
'build' finished successfully (0.005s)
Careful with these methods - if you call them on nodes other than bldnode
or
srcnode
, you may well not get what you expect.
Where’s my node - build or source?¶
During the build, we may want to create nodes that might refer to the build
tree or to the source tree. For example you could imagine a command
my_find_node_build_source(path)
which would look for path
in the
build tree, then in the source tree. There are a couple of node methods that
look both in the build tree and the source tree. These both only work for build
contexts:
.find_resource(path)
:look for an existing file node in the build node tree. Return that node if it exists.
look for corresponding file path in the source tree, make a node for the path and return it if the path exists.
Otherwise return None
The routine also returns None if
path
is a directory..find_or_declare(path)
:look for an existing node in the build node tree. Return that node if it exists.
look for corresponding path in the source tree, make a node for the path and return it if the path exists.
Make a new node in the build node tree for this path and return that node.
find_or_declare
will return a node for a directory.
Low-level API for tasks¶
As we saw above, the high-level API generates ‘task’ instances.
The low-level API creates task instances directly.
See the “Direct task declaration” in the waf book for details.
Here’s an example of the high level interface. Then we’ll do the same with the low-level interface:
# the whole wscript
def configure(conf_ctx):
pass
def build(build_ctx):
# create empty 'foo' file
build_ctx(rule='touch ${TGT}', target='foo')
# copy 'foo' to 'bar'
build_ctx(rule='cp ${SRC} ${TGT}', source='foo', target='bar')
Thence:
$ waf distclean configure build
'distclean' finished successfully (0.002s)
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
[1/2] foo: -> build/foo
[2/2] bar: build/foo -> build/bar
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.035s)
The low level interface defines the tasks and dependencies directly:
# the whole wscript
from waflib.Task import Task
def configure(conf_ctx):
pass
def build(build_ctx):
# Declare the tasks
class TouchFile(Task):
def run(self):
ret_code = self.exec_command(
'touch {0}'.format(self.outputs[0].abspath()))
return ret_code
class CopyFile(Task):
def run(self):
ret_code = self.exec_command(
'cp {0} {1}'.format(self.inputs[0].abspath(),
self.outputs[0].abspath()))
return ret_code
# Instantiate tasks
touch_task = TouchFile(env=build_ctx.env)
touch_task.set_outputs(build_ctx.path.find_or_declare('foo'))
cp_task = CopyFile(env=build_ctx.env)
cp_task.set_inputs(build_ctx.path.find_resource('foo'))
cp_task.set_outputs(build_ctx.path.find_or_declare('bar'))
# Add them to a run group to schedule them
build_ctx.add_to_group(touch_task)
build_ctx.add_to_group(cp_task)
# Build machinery will submit and run these defined tasks
giving output:
$ waf distclean configure build
'distclean' finished successfully (0.002s)
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
[1/2] TouchFile: -> build/foo
[2/2] CopyFile: build/foo -> build/bar
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.040s)
We can also define tasks using the slightly higher-level run_str
attribute
instead of the run
method. waf translates the run_str
attribute to a
run
method automatically on execution, to give the same result:
# the whole wscript
from waflib.Task import Task
def configure(conf_ctx):
pass
def build(build_ctx):
# Declare the tasks
class TouchFile(Task):
run_str = 'touch ${TGT}'
class CopyFile(Task):
run_str = 'cp ${SRC} ${TGT}'
# Instantiate tasks
touch_task = TouchFile(env=build_ctx.env)
touch_task.set_outputs(build_ctx.path.find_or_declare('foo'))
cp_task = CopyFile(env=build_ctx.env)
cp_task.set_inputs(build_ctx.path.find_resource('foo'))
cp_task.set_outputs(build_ctx.path.find_or_declare('bar'))
# Add them to a run group to schedule them
build_ctx.add_to_group(touch_task)
build_ctx.add_to_group(cp_task)
# Build machinery will submit and run these defined tasks
run_str Variable substitution¶
As we’ve seen, a Task can define a system command by defining a run
method,
or a run_str
, which will be compiled into a run
method.
If you pass a string using the rule=
keyword argument to the high-level API,
this also becomes the run_str
of the task.
As you can see from the examples above, the run_str has some extra rules of variable substitution.
For example, ${SRC}
and ${TGT}
get substituted for the source
and
target
filenames. Other variable names that work in this mode
(${VARIABLE_NAME}
) are any attribute of build_ctx.env
.
See Scriptlet expressions in the waf book for more substitutions. Here I’ve copied from the book:
1. If the value starts by env, gen, bld or tsk, a method call will be made
2. If the value starts by SRC[n] or TGT[n], a method call to the
input/output node n will be made
3. SRC represents the list of task inputs seen from the root of the build directory
4. TGT represents the list of task outputs seen from the root of the build directory
For example:
# whole wscript
def configure(conf_ctx):
conf_ctx.env.MYVAR = 'my variable'
def build(build_ctx):
build_ctx(rule='echo ${MYVAR}', always=True)
build_ctx(rule='echo ${bld.bldnode.abspath()}', always=True)
build_ctx(rule='echo ${bld.srcnode.abspath()}', always=True)
gives:
$ waf configure build
Setting top to : /Users/mb312/dev_trees/myproject
Setting out to : /Users/mb312/dev_trees/myproject/build
'configure' finished successfully (0.003s)
Waf: Entering directory `/Users/mb312/dev_trees/myproject/build'
[2/3] echo ${MYVAR}:
[2/3] echo ${bld.bldnode.abspath()}:
my variable
[3/3] echo ${bld.srcnode.abspath()}:
/Users/mb312/dev_trees/myproject/build
/Users/mb312/dev_trees/myproject
Waf: Leaving directory `/Users/mb312/dev_trees/myproject/build'
'build' finished successfully (0.030s)
IPython debugging in waf¶
As of the IPython development tree of Christmas 2013, this works to drop you into the IPython shell from waf:
# the whole wscript
def configure(conf_ctx):
pass
def build(build_ctx):
from IPython import embed
from IPython.core.interactiveshell import DummyMod
wscript_module = DummyMod()
wscript_module.__dict__ = locals()
wscript_module.__name__ = 'wscript'
embed(user_module=wscript_module)