mirror of
https://gitlab.com/ita1024/waf.git
synced 2024-11-25 11:19:52 +01:00
762 lines
29 KiB
Plaintext
762 lines
29 KiB
Plaintext
== C and C++ projects
|
|
|
|
Although Waf is language neutral, it is used very often for C and C++ projects. This chapter describes the Waf tools and functions used for these languages.
|
|
|
|
=== Common script for C, C++ and D applications
|
|
|
|
==== Predefined task generators
|
|
|
|
The C/C++ builds consist in transforming (compiling) source files into object files, and to assemble (link) the object files at the end. In theory a single programming language should be sufficient for writing any application, but the situation is usually more complicated:
|
|
|
|
. Source files may be created by other compilers in other languages (IDL, ASN1, etc)
|
|
. Additional files may enter in the link step (libraries, object files) and applications may be divided in dynamic or static libraries
|
|
. Different platforms may require different processing rules (manifest files on MS-Windows, etc)
|
|
|
|
To conceal the implementation details and the portability concerns, each target (program, library) can be wrapped as single task generator object as in the following example:
|
|
|
|
// cprog_wrappers
|
|
[source,python]
|
|
---------------
|
|
def options(opt):
|
|
opt.load('compiler_c')
|
|
|
|
def configure(conf):
|
|
conf.load('compiler_c') <1>
|
|
|
|
def build(bld):
|
|
bld.program(source='main.c', target='app', use='myshlib mystlib') <2>
|
|
bld.stlib(source='a.c', target='mystlib') <3>
|
|
bld.shlib(source='b.c', target='myshlib', use='myobjects') <4>
|
|
bld.objects(source='c.c', target='myobjects')
|
|
---------------
|
|
|
|
<1> Use compiler_c to load the c routines and to find a compiler (for c++ use 'compiler_cxx' and 'compiler_d' for d)
|
|
<2> Declare a program built from _main.c_ and using two other libraries
|
|
<3> Declare a static library
|
|
<4> Declare a shared library, using the objects from 'myobjects'
|
|
|
|
The targets will have different extensions and names depending on the platform. For example on Linux, the contents of the build directory will be:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
$ tree build
|
|
build/
|
|
|-- c4che
|
|
| |-- build.config.py
|
|
| `-- _cache.py
|
|
|-- a.c.1.o
|
|
|-- app <1>
|
|
|-- b.c.2.o
|
|
|-- c.c.3.o
|
|
|-- config.log
|
|
|-- libmyshlib.so <2>
|
|
|-- libmystlib.a
|
|
`-- main.c.0.o <3>
|
|
---------------
|
|
|
|
<1> Programs have no extension on Linux but will have '.exe' on Windows
|
|
<2> The '.so' extension for shared libraries on Linux will be '.dll' on Windows
|
|
<3> The '.o' object files use the original file name and an index to avoid errors in multiple compilations
|
|
|
|
The build context methods _program_, _shlib_, _stlib_ and _objects_ return a single task generator with the appropriate features detected from the source list. For example, for a program having _.c_ files in the source attribute, the features added will be _"c cprogram"_, for a _d_ static library, _"d dstlib"_.
|
|
|
|
==== Additional attributes
|
|
|
|
The methods described previously can process many more attributes than just 'use'. Here is an advanced example:
|
|
|
|
[source,python]
|
|
---------------
|
|
def options(opt):
|
|
opt.load('compiler_c')
|
|
|
|
def configure(conf):
|
|
conf.load('compiler_c')
|
|
|
|
def build(bld):
|
|
bld.program(
|
|
source = 'main.c', <1>
|
|
target = 'appname', <2>
|
|
features = ['more', 'features'], <3>
|
|
|
|
includes = ['.'], <4>
|
|
defines = ['LINUX=1', 'BIDULE'],
|
|
|
|
lib = ['m'], <5>
|
|
libpath = ['/usr/lib'],
|
|
stlib = ['dl'], <6>
|
|
stlibpath = ['/usr/local/lib'],
|
|
linkflags = ['-g'], <7>
|
|
rpath = ['/opt/kde/lib'] <8>
|
|
vnum = '1.2.3',
|
|
|
|
install_path = '${SOME_PATH}/bin', <9>
|
|
cflags = ['-O2', '-Wall'], <10>
|
|
cxxflags = ['-O3'],
|
|
dflags = ['-g'],
|
|
)
|
|
---------------
|
|
|
|
<1> Source file list
|
|
<2> Target, converted automatically to +target.exe+ or +libtarget.so+, depending on the platform and type
|
|
<3> Additional features to add (for a program consisting in c files, the default will be _'c cprogram'_)
|
|
<4> Includes and defines
|
|
<5> Shared libraries and shared libraries link paths
|
|
<6> Static libraries and link paths
|
|
<7> Use linkflags for specific link flags (not for passing libraries)
|
|
<8> rpath and vnum, ignored on platforms that do not support them
|
|
<9> Programs and shared libraries are installed by default. To disable the installation, set None.
|
|
<10> Miscalleneous flags, applied to the source files that support them (if present)
|
|
|
|
=== Include processing
|
|
|
|
==== Execution path and flags
|
|
|
|
Include paths are used by the C/C++ compilers for finding headers. When one header changes, the files are recompiled automatically. For example on a project having the following structure:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
$ tree
|
|
.
|
|
|-- foo.h
|
|
|-- src
|
|
| |-- main.c
|
|
| `-- wscript
|
|
`-- wscript
|
|
---------------
|
|
|
|
The file 'src/wscript' will contain the following code:
|
|
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.program(
|
|
source = 'main.c',
|
|
target = 'myapp',
|
|
includes = '.. .')
|
|
---------------
|
|
|
|
The command-line (output by `waf -v`) will have the following form:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
cc -I. -I.. -Isrc -I../src ../src/main.c -c -o src/main_1.o
|
|
---------------
|
|
|
|
Because commands are executed from the build directory, the folders have been converted to include flags in the following way:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
.. -> -I.. -I.
|
|
. -> -I../src -Isrc
|
|
---------------
|
|
|
|
There are the important points to remember:
|
|
|
|
. The includes are always given relative to the directory containing the wscript file
|
|
. The includes add both the source directory and the corresponding build directory for the task generator variant
|
|
. Commands are executed from the build directory, so the include paths must be converted
|
|
. System include paths should be defined during the configuration and added to INCLUDES variables (uselib)
|
|
|
|
==== The Waf preprocessor
|
|
|
|
Waf uses a preprocessor written in Python for adding the dependencies on the headers. A simple parser looking at #include statements would miss constructs such as:
|
|
|
|
[source,c]
|
|
---------------
|
|
#define mymacro "foo.h"
|
|
#include mymacro
|
|
---------------
|
|
|
|
Using the compiler for finding the dependencies would not work for applications requiring file preprocessing such as Qt. For Qt, special include files having the '.moc' extension must be detected by the build system and produced ahead of time. The c compiler could not parse such files.
|
|
|
|
[source,c]
|
|
---------------
|
|
#include "foo.moc"
|
|
---------------
|
|
|
|
Since system headers are not tracked by default, the waf preprocessor may miss dependencies written in the following form:
|
|
|
|
[source,c]
|
|
---------------
|
|
#if SOMEMACRO
|
|
/* an include in the project */
|
|
#include "foo.h"
|
|
#endif
|
|
---------------
|
|
|
|
To write portable code and to ease debugging, it is strongly recommended to put all the conditions used within a project into a 'config.h' file.
|
|
|
|
[source,python]
|
|
---------------
|
|
def configure(conf):
|
|
conf.check(
|
|
fragment = 'int main() { return 0; }\n',
|
|
define_name = 'FOO',
|
|
mandatory = True)
|
|
conf.write_config_header('config.h')
|
|
---------------
|
|
|
|
For performance reasons, the implicit dependency on the system headers is ignored by default. The following code may be used to enable this behaviour:
|
|
|
|
[source,python]
|
|
---------------
|
|
from waflib import c_preproc
|
|
c_preproc.go_absolute = True
|
|
---------------
|
|
|
|
Additional tools such as https://github.com/waf-project/waf/blob/master/waflib/extras/gccdeps.py[gccdeps] or https://github.com/waf-project/waf/blob/master/waflib/extras//dumbpreproc.py[dumbpreproc] provide alternate dependency scanners that can be faster in certain cases (boost).
|
|
|
|
NOTE: The Waf engine will detect if tasks generate headers necessary for the compilation and compute the build order accordingly. It may sometimes improve the performance of the scanner if the tasks creating headers provide the hint 'ext_out=[".h"]'.
|
|
|
|
==== Dependency debugging
|
|
|
|
The Waf preprocessor contains a specific debugging zone:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
$ waf --zones=preproc
|
|
---------------
|
|
|
|
To display the dependencies obtained or missed, use the following:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
$ waf --zones=deps
|
|
|
|
23:53:21 deps deps for src:///comp/waf/demos/qt4/src/window.cpp: <1>
|
|
[src:///comp/waf/demos/qt4/src/window.h, bld:///comp/waf/demos/qt4/src/window.moc]; <2>
|
|
unresolved ['QtGui', 'QGLWidget', 'QWidget'] <3>
|
|
---------------
|
|
|
|
<1> File being preprocessed
|
|
<2> Headers found
|
|
<3> System headers discarded
|
|
|
|
The dependency computation is performed only when the files are not up-to-date, so these commands will display something only when there is a file to compile.
|
|
|
|
NOTE: The scanner is only called when C files or dependencies change. In the rare case of adding headers after a successful compilation, then it may be necessary to run 'waf clean build' to force a full scanning.
|
|
|
|
=== Library interaction (use)
|
|
|
|
==== Local libraries
|
|
|
|
The attribute 'use' enables the link against libraries (static or shared), or the inclusion of object files when the task generator referenced is not a library.
|
|
|
|
// cprog_use
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.stlib(
|
|
source = 'test_staticlib.c',
|
|
target = 'mylib',
|
|
name = 'stlib1') <1>
|
|
|
|
bld.program(
|
|
source = 'main.c',
|
|
target = 'app',
|
|
includes = '.',
|
|
use = ['stlib1']) <2>
|
|
---------------
|
|
|
|
<1> The name attribute must point at exactly one task generator
|
|
<2> The attribute 'use' contains the task generator names to use
|
|
|
|
In this example, the file 'app' will be re-created whenever 'mylib' changes (order and dependency). By using task generator names, the programs and libraries declarations may appear in any order and across scripts. For convenience, the name does not have to be defined, and will be pre-set from the target name:
|
|
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.stlib(
|
|
source = 'test_staticlib.c',
|
|
target = 'mylib')
|
|
|
|
bld.program(
|
|
source = 'main.c',
|
|
target = 'app',
|
|
includes = '.',
|
|
use = ['mylib'])
|
|
---------------
|
|
|
|
The 'use' processing also exhibits a recursive behaviour. Let's illustrate it by the following example:
|
|
|
|
// cprog_propagation
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.shlib(
|
|
source = 'a.c', <1>
|
|
target = 'lib1')
|
|
|
|
bld.stlib(
|
|
source = 'b.c',
|
|
use = 'cshlib', <2>
|
|
target = 'lib2')
|
|
|
|
bld.shlib(
|
|
source = 'c.c',
|
|
target = 'lib3',
|
|
use = 'lib1 lib2') <3>
|
|
|
|
bld.program( <4>
|
|
source = 'main.c',
|
|
target = 'app',
|
|
use = 'lib3')
|
|
---------------
|
|
|
|
<1> A simple shared library
|
|
<2> The 'cshlib' flags will be propagated to both the library and the program.
|
|
<3> 'lib3' uses both a shared library and a static library
|
|
<4> A program using 'lib3'
|
|
|
|
Because of the shared library dependency 'lib1' → 'lib2', the program 'app' should link against both 'lib1' and 'lib3', but not against 'lib2':
|
|
|
|
[source,shishell]
|
|
---------------
|
|
$ waf -v
|
|
'clean' finished successfully (0.004s)
|
|
Waf: Entering directory `/tmp/cprog_propagation/build'
|
|
[1/8] c: a.c -> build/a.c.0.o
|
|
12:36:17 runner ['/usr/bin/gcc', '-fPIC', '../a.c', '-c', '-o', 'a.c.0.o']
|
|
[2/8] c: b.c -> build/b.c.1.o
|
|
12:36:17 runner ['/usr/bin/gcc', '../b.c', '-c', '-o', 'b.c.1.o']
|
|
[3/8] c: c.c -> build/c.c.2.o
|
|
12:36:17 runner ['/usr/bin/gcc', '-fPIC', '../c.c', '-c', '-o', 'c.c.2.o']
|
|
[4/8] c: main.c -> build/main.c.3.o
|
|
12:36:17 runner ['/usr/bin/gcc', '../main.c', '-c', '-o', 'main.c.3.o']
|
|
[5/8] cstlib: build/b.c.1.o -> build/liblib2.a
|
|
12:36:17 runner ['/usr/bin/ar', 'rcs', 'liblib2.a', 'b.c.1.o']
|
|
[6/8] cshlib: build/a.c.0.o -> build/liblib1.so
|
|
12:36:17 runner ['/usr/bin/gcc', 'a.c.0.o', '-o', 'liblib1.so', '-shared']
|
|
[7/8] cshlib: build/c.c.2.o -> build/liblib3.so
|
|
12:36:17 runner ['/usr/bin/gcc', 'c.c.2.o', '-o', 'liblib3.so', '-Wl,-Bstatic', '-L.', '-llib2', '-Wl,-Bdynamic', '-L.', '-llib1', '-shared']
|
|
[8/8] cprogram: build/main.c.3.o -> build/app
|
|
12:36:17 runner ['/usr/bin/gcc', 'main.c.3.o', '-o', 'app', '-Wl,-Bdynamic', '-L.', '-llib1', '-llib3']
|
|
Waf: Leaving directory `/tmp/cprog_propagation/build'
|
|
'build' finished successfully (0.144s)
|
|
---------------
|
|
|
|
To sum up the two most important aspects of the 'use' attribute:
|
|
|
|
. The task generators may be created in any order and in different files, but must provide a unique name for the 'use' attribute
|
|
. The 'use' processing will iterate recursively over all the task generators involved, but the flags added depend on the target kind (shared/static libraries)
|
|
|
|
==== Special local libraries
|
|
|
|
===== Includes folders
|
|
|
|
The use keywork may point at special libraries that do not actually declare a target. For example, header-only libraries are commonly used to add specific include paths to several targets:
|
|
|
|
// cprog_incdirs
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld(
|
|
includes = '. src',
|
|
export_includes = 'src', <1>
|
|
name = 'com_includes')
|
|
|
|
bld.stlib(
|
|
source = 'a.c',
|
|
target = 'shlib1',
|
|
use = 'com_includes') <2>
|
|
|
|
bld.program(
|
|
source = 'main.c',
|
|
target = 'app',
|
|
use = 'shlib1', <3>
|
|
)
|
|
---------------
|
|
|
|
<1> The 'includes' attribute is private, but 'export_includes' will be used by other task generators
|
|
<2> The paths added are relative to the other task generator
|
|
<3> The 'export_includes' will be propagated to other task generators
|
|
|
|
===== Object files
|
|
|
|
Here is how to enable specific compilation flags for particular files:
|
|
|
|
// cprog_objects
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.objects( <1>
|
|
source = 'test.c',
|
|
cflags = '-O3',
|
|
target = 'my_objs')
|
|
|
|
bld.shlib(
|
|
source = 'a.c',
|
|
cflags = '-O2', <2>
|
|
target = 'lib1',
|
|
use = 'my_objs') <3>
|
|
|
|
bld.program(
|
|
source = 'main.c',
|
|
target = 'test_c_program',
|
|
use = 'lib1') <4>
|
|
---------------
|
|
|
|
<1> Files will be compiled in c mode, but no program or library will be produced
|
|
<2> Different compilation flags may be used
|
|
<3> The objects will be added automatically in the link stage
|
|
<4> There is no object propagation to other programs or libraries to avoid duplicate symbol errors
|
|
|
|
WARNING: Like static libraries, object files are often abused to copy-paste binary code. Try to minimize the executables size by using shared libraries whenever possible.
|
|
|
|
===== Fake libraries
|
|
|
|
Local libraries will trigger a recompilation whenever they change. The methods 'read_shlib' and 'read_stlib' can be used to add this behaviour to external libraries or to binary files present in the project.
|
|
|
|
// cprog_fakelibs
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
bld.read_shlib('m', paths=['.', '/usr/lib64'])
|
|
bld.program(source='main.c', target='app', use='m')
|
|
---------------
|
|
|
|
The methods will try to find files such as 'libm.so' or 'libm.dll' in the specified paths to compute the required paths and dependencies. In this example, the target 'app' will be re-created whenever '/usr/lib64/libm.so' changes. These libraries are propagated between task generators just like shared or static libraries declared locally.
|
|
|
|
==== Foreign libraries and flags
|
|
|
|
When an element in the attribute 'use' does not match a local library, it is assumed that it represents a system library, and the the required flags are present in the configuration set 'env'. This system enables the addition of several compilation and link flags at once, as in the following example:
|
|
|
|
// cprog_system
|
|
[source,python]
|
|
---------------
|
|
import sys
|
|
|
|
def options(opt):
|
|
opt.load('compiler_c')
|
|
|
|
def configure(conf):
|
|
conf.load('compiler_c')
|
|
conf.env.INCLUDES_TEST = ['/usr/include'] <1>
|
|
|
|
if sys.platform != 'win32': <2>
|
|
conf.env.DEFINES_TEST = ['TEST']
|
|
conf.env.CFLAGS_TEST = ['-O0'] <3>
|
|
conf.env.LIB_TEST = ['m']
|
|
conf.env.LIBPATH_TEST = ['/usr/lib']
|
|
conf.env.LINKFLAGS_TEST = ['-g']
|
|
conf.env.INCLUDES_TEST = ['/opt/gnome/include']
|
|
|
|
def build(bld):
|
|
mylib = bld.stlib(
|
|
source = 'test_staticlib.c',
|
|
target = 'teststaticlib',
|
|
use = 'TEST') <4>
|
|
|
|
if mylib.env.CC_NAME == 'gcc':
|
|
mylib.cxxflags = ['-O2'] <5>
|
|
---------------
|
|
|
|
<1> For portability reasons, it is recommended to use INCLUDES instead of giving flags of the form -I/include. Note that the INCLUDES use used by both c and c++
|
|
<2> Variables may be left undefined in platform-specific settings, yet the build scripts will remain identical.
|
|
<3> Declare a few variables during the configuration, the variables follow the convention VAR_NAME
|
|
<4> Add all the VAR_NAME corresponding to the _use variable_ NAME, which is 'TEST' in this example
|
|
<5> 'Model to avoid': setting the flags and checking for the configuration should be performed in the configuration section
|
|
|
|
The variables used for C/C++ are the following:
|
|
|
|
.Use variables and task generator attributes for C/C++
|
|
[options="header",cols="1,1,3"]
|
|
|=================
|
|
|Uselib variable | Attribute | Usage
|
|
|LIB |lib | list of sharedlibrary names to use, without prefix or extension
|
|
|LIBPATH |libpath | list of search path for shared libraries
|
|
|STLIB |stlib | list of static library names to use, without prefix or extension
|
|
|STLIBPATH|stlibpath| list of search path for static libraries
|
|
|LINKFLAGS|linkflags| list of link flags (use other variables whenever possible)
|
|
|RPATH |rpath | list of paths to hard-code into the binary during linking time
|
|
|CFLAGS |cflags | list of compilation flags for c files
|
|
|CXXFLAGS |cxxflags | list of compilation flags for c++ files
|
|
|DFLAGS |dflags | list of compilation flags for d files
|
|
|INCLUDES |includes | include paths
|
|
|CXXDEPS | | a variable/list to trigger c++ file recompilations when it changes
|
|
|CCDEPS | | same as above, for c
|
|
|LINKDEPS | | same as above, for the link tasks
|
|
|DEFINES |defines | list of defines in the form [`key=value', ...]
|
|
|FRAMEWORK|framework| list of frameworks to use
|
|
|FRAMEWORKPATH|frameworkpath| list of framework paths to use
|
|
|ARCH |arch | list of architectures in the form ['ppc', 'x86']
|
|
|=================
|
|
|
|
The variables may be left empty for later use, and will not cause errors. During the development, the configuration cache files (for example, _cache.py) may be modified from a text editor to try different configurations without forcing a whole project reconfiguration. The files affected will be rebuilt however.
|
|
|
|
=== Configuration helpers
|
|
|
|
==== Configuration tests
|
|
|
|
The method 'check' is used to detect parameters using a small build project. The main parameters are the following
|
|
|
|
. msg: title of the test to execute
|
|
. okmsg: message to display when the test succeeds
|
|
. errmsg: message to display when the test fails
|
|
. env: environment to use for the build (conf.env is used by default)
|
|
. compile_mode: 'cc' or 'cxx'
|
|
. define_name: sets a define of the form *define_name=x* when the test succeeds (a default value for define_name is usually calculated automatically)
|
|
. global_define: sets *define_name=X* to *env.DEFINES* when True and to *env.DEFINES_X* when False (X represents value of the uselib_store variable)
|
|
. uselib_store: stores arguments of the form lib,cflag,cxxflags to LIB_X,CFLAG_X,CXXFLAGS_X respectively (X represents the value of the uselib_store variable)
|
|
|
|
The errors raised are instances of 'waflib.Errors.ConfigurationError'. There are no return codes.
|
|
|
|
Besides the main parameters, the attributes from c/c++ task generators may be used. Here are a few examples:
|
|
|
|
// cprog_conf
|
|
[source,python]
|
|
---------------
|
|
def configure(conf):
|
|
|
|
conf.check(header_name='time.h', features='c cprogram') <1>
|
|
conf.check_cc(function_name='printf', header_name="stdio.h", mandatory=False) <2>
|
|
conf.check_cc(fragment='int main() {2+2==4;}\n', define_name="boobah") <3>
|
|
conf.check_cc(lib='m', cflags='-Wall', defines=['var=foo', 'x=y'],
|
|
uselib_store='M') <4>
|
|
conf.check_cxx(lib='linux', use='M', cxxflags='-O2') <5>
|
|
|
|
conf.check_cc(fragment='''
|
|
#include <stdio.h>
|
|
int main() { printf("4"); return 0; } ''',
|
|
define_name = "booeah",
|
|
execute = True,
|
|
define_ret = True,
|
|
msg = "Checking for something") <6>
|
|
|
|
conf.check(features='c', fragment='int main(){return 0;}') <7>
|
|
|
|
conf.write_config_header('config.h') <8>
|
|
---------------
|
|
|
|
<1> Try to compile a program using the configuration header time.h, if present on the system, if the test is successful, the define HAVE_TIME_H will be added
|
|
<2> Try to compile a program with the function printf, adding the header stdio.h (the header_name may be a list of additional headers). All configuration tests are required by default (@conf methods) and will raise configuration exceptions. To conceal them, set the attribute 'mandatory' to False.
|
|
<3> Try to compile a piece of code, and if the test is successful, define the name boobah
|
|
<4> Modifications made to the task generator environment are not stored. When the test is successful and when the attribute uselib_store is provided, the names lib, cflags and defines will be converted into _use variables_ LIB_M, CFLAGS_M and DEFINES_M and the flag values are added to the configuration environment.
|
|
<5> Try to compile a simple c program against a library called 'linux', and reuse the previous parameters for libm by _use_
|
|
<6> Execute a simple program, collect the output, and put it in a define when successful
|
|
<7> The tests create a build with a single task generator. By passing the 'features' attribute directly it is possible to disable the compilation or to create more complicated configuration tests.
|
|
<8> After all the tests are executed, write a configuration header in the build directory (optional). The configuration header is used to limit the size of the command-line.
|
|
|
|
Here is an example of a +config.h+ produced with the previous test code:
|
|
|
|
[source,c]
|
|
---------------
|
|
/* Configuration header created by Waf - do not edit */
|
|
#ifndef _CONFIG_H_WAF
|
|
#define _CONFIG_H_WAF
|
|
|
|
#define HAVE_PRINTF 1
|
|
#define HAVE_TIME_H 1
|
|
#define boobah 1
|
|
#define booeah "4"
|
|
|
|
#endif /* _CONFIG_H_WAF */
|
|
---------------
|
|
|
|
The file +_cache.py+ will contain the following variables:
|
|
|
|
[source,python]
|
|
---------------
|
|
DEFINES_M = ['var=foo', 'x=y']
|
|
CXXFLAGS_M = ['-Wall']
|
|
CFLAGS_M = ['-Wall']
|
|
LIB_M = ['m']
|
|
boobah = 1
|
|
booeah = '4'
|
|
defines = {'booeah': '"4"', 'boobah': 1, 'HAVE_TIME_H': 1, 'HAVE_PRINTF': 1}
|
|
dep_files = ['config.h']
|
|
waf_config_files = ['/compilation/waf/demos/adv/build/config.h']
|
|
---------------
|
|
|
|
==== Advanced tests
|
|
|
|
The methods 'conf.check' create a build context and a task generator internally. This means that the attributes 'includes', 'defines', 'cxxflags' may be used (not all shown here). Yet some tests can be require several targets at once. In order to faciliate this, a custom build function can be passed directly, for example:
|
|
|
|
[source,python]
|
|
---------------
|
|
def build(bld):
|
|
lib_node = bld.srcnode.make_node('libdir/iotest.c')
|
|
lib_node.parent.mkdir()
|
|
lib_node.write('#include <stdio.h>\nint lib_func(void) { FILE *f = fopen("foo", "r");}\n', 'w')
|
|
bld(features='c cshlib', source=[lib_node], linkflags=['-g3'], target='mylib')
|
|
conf.check(build_fun=build, msg='Custom test')
|
|
---------------
|
|
|
|
As a result, tests can also be written for other programming languages and compilers. Here is for example a custom test for LaTeX packages:
|
|
|
|
[source,python]
|
|
---------------
|
|
def build_latex_test(bld):
|
|
def write_tex(tsk):
|
|
tsk.outputs[0].write(r'''\documentclass[a4paper,12pt]{article} \usepackage{ucs} \begin{document} test \end{document} ''')
|
|
bld(rule=write_tex, target='main.tex')
|
|
bld(features='tex', type='pdflatex', source='main.tex', prompt=0)
|
|
conf.test(build_fun=build_latex_test, msg='Checking for UCS', okmsg='ok', errmsg='ucs.sty is missing install latex-extras')
|
|
---------------
|
|
|
|
==== Configuration headers
|
|
|
|
Adding lots of command-line define values increases the size of the command-line and makes it harder to review the flags when errors occur. Besides that, the defines passed on the command-line may fail unexpectedly with different compilers and command execution contexts. For example, define values containing quotes may be misinterpreted in Visual Studio response files. It is therefore a best practice to use configuration headers whenever possible.
|
|
|
|
Writing configuration headers can be performed using the following methods:
|
|
|
|
[source,python]
|
|
---------------
|
|
def configure(conf):
|
|
conf.define('NOLIBF', 1)
|
|
conf.undefine('NOLIBF')
|
|
conf.define('LIBF', 1)
|
|
conf.define('LIBF_VERSION', '1.0.2')
|
|
conf.write_config_header('config.h')
|
|
---------------
|
|
|
|
The code snipped will produce the following 'config.h' in the build directory:
|
|
|
|
[source,shishell]
|
|
---------------
|
|
build/
|
|
|-- c4che
|
|
| |-- build.config.py
|
|
| `-- _cache.py
|
|
|-- config.log
|
|
`-- config.h
|
|
---------------
|
|
|
|
The contents of the config.h for this example are:
|
|
|
|
[source,c]
|
|
---------------
|
|
/* Configuration header created by Waf - do not edit */
|
|
#ifndef _CONFIG_H_WAF
|
|
#define _CONFIG_H_WAF
|
|
|
|
/* #undef NOLIBF */
|
|
#define LIBF 1
|
|
#define LIBF_VERSION "1.0.2"
|
|
|
|
#endif /* _CONFIG_H_WAF */
|
|
---------------
|
|
|
|
NOTE: By default, the defines are moved from the command-line into the configuration header. This means that the attribute _conf.env.DEFINE_ is cleared by this operation. To prevent this behaviour, use 'conf.write_config_header(remove=False)'
|
|
|
|
==== Pkg-config
|
|
|
|
Instead of duplicating the configuration detection in all dependent projects, configuration files may be written when libraries are installed. To ease the interaction with build systems based on Make (cannot query databases or apis), small applications have been created for reading the cache files and to interpret the parameters (with names traditionally ending in '-config'): http://pkg-config.freedesktop.org/wiki/[pkg-config], wx-config, sdl-config, etc.
|
|
|
|
The method 'check_cfg' is provided to ease the interaction with these applications. Here are a few examples:
|
|
|
|
// cprog_pkgconfig
|
|
[source,python]
|
|
---------------
|
|
def options(opt):
|
|
opt.load('compiler_c')
|
|
|
|
def configure(conf):
|
|
conf.load('compiler_c')
|
|
|
|
conf.check_cfg(atleast_pkgconfig_version='0.0.0') <1>
|
|
pango_version = conf.check_cfg(modversion='pango') <2>
|
|
|
|
conf.check_cfg(package='pango') <3>
|
|
conf.check_cfg(package='pango', uselib_store='MYPANGO',
|
|
args=['--cflags', '--libs']) <4>
|
|
|
|
conf.check_cfg(package='pango', <5>
|
|
args=['pango >= 0.1.0', 'pango < 9.9.9', '--cflags', '--libs'],
|
|
msg="Checking for 'pango 0.1.0'") <6>
|
|
|
|
conf.check_cfg(path='sdl-config', args='--cflags --libs',
|
|
package='', uselib_store='SDL') <7>
|
|
conf.check_cfg(path='mpicc', args='--showme:compile --showme:link',
|
|
package='', uselib_store='OPEN_MPI', mandatory=False) <8>
|
|
---------------
|
|
|
|
<1> Check for the pkg-config version
|
|
<2> Retrieve the module version for a package as a string. If there were no errors, 'PANGO_VERSION' is defined. It can be overridden with the attribute _uselib_store='MYPANGO'_.
|
|
<3> Check if the pango package is present, and define _HAVE_PANGO_ (calculated automatically from the package name)
|
|
<4> Beside defining _HAVE_MYPANGO_, extract and store the relevant flags to the _use variable_ MYPANGO (_LIB_MYPANGO_, _LIBPATH_MYPANGO_, etc)
|
|
<5> Like the previous test, but with pkg-config clauses to enforce a particular version number
|
|
<6> Display a custom message on the output. The attributes 'okmsg' and 'errmsg' represent the messages to display in case of success and error respectively
|
|
<7> Obtain the flags for sdl-config. The example is applicable for other configuration programs such as wx-config, pcre-config, etc
|
|
<8> Suppress the configuration error which is raised whenever the program to execute is not found or returns a non-zero exit status
|
|
|
|
Due to the amount of flags, the lack of standards between config applications, and to the compiler-dependent flags (-I for gcc, /I for msvc), the pkg-config output is parsed before setting the corresponding _use variables_ in a go. The function 'parse_flags(line, uselib, env)' in the Waf module c_config.py performs the flag extraction.
|
|
|
|
The outputs are written in the build directory into the file 'config.log':
|
|
|
|
[source,shishell]
|
|
------------------
|
|
# project configured on Tue Aug 31 17:30:21 2010 by
|
|
# waf {version} (abi 98, python 20605f0 on linux2)
|
|
# using /home/waf/bin/waf configure
|
|
#
|
|
---
|
|
Setting top to
|
|
/disk/comp/waf/docs/book/examples/cprog_pkgconfig
|
|
---
|
|
Setting out to
|
|
/disk/comp/waf/docs/book/examples/cprog_pkgconfig/build
|
|
---
|
|
Checking for program pkg-config
|
|
/usr/bin/pkg-config
|
|
find program=['pkg-config'] paths=['/usr/local/bin', '/usr/bin'] var='PKGCONFIG' -> '/usr/bin/pkg-config'
|
|
---
|
|
Checking for pkg-config version >= 0.0.0
|
|
['/usr/bin/pkg-config', '--atleast-pkgconfig-version=0.0.0']
|
|
yes
|
|
['/usr/bin/pkg-config', '--modversion', 'pango']
|
|
out: 1.28.0
|
|
|
|
---
|
|
Checking for pango
|
|
['/usr/bin/pkg-config', 'pango']
|
|
yes
|
|
---
|
|
Checking for pango
|
|
['/usr/bin/pkg-config', 'pango']
|
|
yes
|
|
---
|
|
Checking for pango 0.1.0
|
|
['/usr/bin/pkg-config', 'pango >= 0.1.0', 'pango < 9.9.9', '--cflags', '--libs', 'pango']
|
|
out: -pthread -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include
|
|
-pthread -lpango-1.0 -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lrt -lglib-2.0
|
|
|
|
yes
|
|
---
|
|
Checking for sdl-config
|
|
['sdl-config', '--cflags', '--libs']
|
|
out: -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT
|
|
-L/usr/lib64 -lSDL -lpthread
|
|
|
|
yes
|
|
---
|
|
Checking for mpicc
|
|
['mpicc', '--showme:compile', '--showme:link']
|
|
out: -pthread libtool: link: -pthread -L/usr/lib64 -llammpio -llamf77mpi -lmpi -llam -lutil -ldl
|
|
------------------
|
|
|
|
After such a configuration, the configuration set contents will be similar to the following:
|
|
|
|
[source,python]
|
|
---------------
|
|
'CFLAGS_OPEN_MPI' ['-pthread']
|
|
'CFLAGS_PANGO' ['-pthread']
|
|
'CXXFLAGS_OPEN_MPI' ['-pthread']
|
|
'CXXFLAGS_PANGO' ['-pthread']
|
|
'DEFINES' ['HAVE_PANGO=1', 'HAVE_MYPANGO=1', 'HAVE_SDL=1', 'HAVE_OPEN_MPI=1']
|
|
'DEFINES_SDL' ['_GNU_SOURCE=1', '_REENTRANT']
|
|
'INCLUDES_PANGO' ['/usr/include/pango-1.0', '/usr/include/glib-2.0', '/usr/lib64/glib-2.0/include']
|
|
'INCLUDES_SDL' ['/usr/include/SDL']
|
|
'LIBPATH_OPEN_MPI' ['/usr/lib64']
|
|
'LIBPATH_SDL' ['/usr/lib64']
|
|
'LIB_OPEN_MPI' ['lammpio', 'lamf77mpi', 'mpi', 'lam', 'util', 'dl']
|
|
'LIB_PANGO' ['pango-1.0', 'gobject-2.0', 'gmodule-2.0', 'gthread-2.0', 'rt', 'glib-2.0']
|
|
'LIB_SDL' ['SDL', 'pthread']
|
|
'LINKFLAGS_OPEN_MPI' ['-pthread']
|
|
'LINKFLAGS_PANGO' ['-pthread']
|
|
'PKGCONFIG' '/usr/bin/pkg-config'
|
|
'PREFIX' '/usr/local'
|
|
'define_key' ['HAVE_PANGO', 'HAVE_MYPANGO', 'HAVE_SDL', 'HAVE_OPEN_MPI']
|
|
---------------
|
|
|