Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > 2053a0d9eaaf755b990f80ce4df504a7 > files > 11

waf-1.5.9-1mdv2010.0.noarch.rpm

<?xml version='1.0'?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
>
<chapter id="c_projects">
	<title>C and C++ projects</title>
	<section id="common_c">
		<title>Common script for C/C++ applications</title>
		<para>
			The c/c++ builds consist in transforming source files into object files, and to assemble the object files at the end. In theory a single programming language should be sufficient for writing any application, but in practice slight variations exist:
			<itemizedlist>
				<listitem>Applications may be divided in dynamic or static libraries</listitem>
				<listitem>Additional files may enter in the link step (libraries, object files)</listitem>
				<listitem>Source files may be generated by other compilers</listitem>
			</itemizedlist>
		The construction of c/c++ applications can be quite complicated, and several measures must be taken to ensure coherent interaction with new compilation rules. The canonical code for a task generator building a c/c++ application is the following:
			<programlisting language="python">
def build(bld):
	t = bld.new_task_gen(
		features     = 'cc cprogram', <co id="taskgen-co" linkends="al"/>
		source       = 'main.c', <co id="src-co" linkends="a2"/>
		target       = 'appname', <co id="tgt-co" linkends="a3"/>
		includes     = '.', <co id="incl-co" linkends="a4"/>
		install_path = '${SOME_PATH}/bin', <co id="inst-co" linkends="a5"/>
		defines      = ['LINUX=1', 'BIDULE'], <co id="defi-co" linkends="a6"/>
		ccflags      = ['-O2', '-Wall'], <co id="ccflags-co" linkends="a7"/>
		lib          = ['m'], <co id="shlib-co" linkends="a8"/>
		libpath      = ['/usr/lib'],
		linkflags    = ['-g'],
		vnum         = '1.2.3', <co id="vnum-co" linkends="a88"/>
	)
	t.rpath      = '/usr/lib', <co id="rpath-co" linkends="a9"/>
			</programlisting>

			<calloutlist>
				<callout arearefs="taskgen-co" id="a1">
					<para>Task generator declaration; each element in the list represent a feature; it is possible to add several languages at once (<emphasis>ocaml and c++</emphasis> for example), but the one of <emphasis>cstaticlib, cshlib or cprogram</emphasis> must be chosen.</para>
				</callout>
				<callout arearefs="src-co" id="a2">
					<para>List of source, it may be either a python list, or a string containing the file names separated with spaces. This list may contain file names of different extensions to make hybrid applications.</para>
				</callout>
				<callout arearefs="tgt-co" id="a3">
					<para>Target name, it is concerted to the name of the binary <emphasis>name.so</emphasis> or <emphasis>name.exe</emphasis> depending on the platform and the features.</para>
				</callout>
				<callout arearefs="incl-co" id="a4">
					<para>List of include paths, it may be either a python list, or a string containing the paths separated by spaces. The paths are used for both the command-line and for finding the implicit dependencies (headers). In general, include paths must be relative to the wscript file and given explicitly. See <xref linkend="include_sys"/>.</para>
				</callout>
				<callout arearefs="inst-co" id="a5">
					<para>Installation directory, this is where to install the library or program produced. The <emphasis>${}</emphasis> expression is a reference to a variable to be extracted from <emphasis>tgen.env</emphasis>. By default it is set to ${PREFIX}/bin for programs and ${PREFIX}/lib for libraries. To disable the installation, set it to <emphasis>None</emphasis>.</para>
				</callout>
				<callout arearefs="defi-co" id="a6">
					<para>Command-line defines: list of defines to add to the command-line with the <emphasis>-D</emphasis> prefix. To reduce the size of the command-line, it is possible to use a configuration header, see the following section for more details.</para>
				</callout>
				<callout arearefs="ccflags-co" id="a7">
					<para>Command-line compilation flags, for the c++ language the attribute is called <emphasis>cxxflags</emphasis></para>
				</callout>
				<callout arearefs="shlib-co" id="a8">
					<para>Shared libraries may be given directly (use <emphasis>staticlib</emphasis> and <emphasis>staticlibpath</emphasis> for static libraries)</para>
				</callout>
				<callout arearefs="vnum-co" id="a88">
					<para>Version number for shared libraries</para>
				</callout>

				<callout arearefs="rpath-co" id="a9">
					<para>Additional pararameters may be added from a task generator reference. The next section describes a technique to gather the conditions into the configuration section.</para>
				</callout>
			</calloutlist>
		</para>
	</section>

	<section id="uselib">
		<title>Library interaction (uselib)</title>
		<para>
			To link a library against another one created in the same Waf project, the attribute <emphasis>uselib_local</emphasis> may be used. The include paths, the link path and the library name are automatically exported, and the dependent binary is recompiled when the library changes:
			<programlisting language="python">
def build(bld):
	staticlib = bld.new_task_gen(
		features       = 'cc cstaticlib', <co id="lib-co" linkends="b1"/>
		source         = 'test_staticlib.c',
		target         = 'teststaticlib',
		export_incdirs = '.') <co id="uselib-co" linkends="b2"/>

	main = bld.new_task_gen(
		features       = 'cc cprogram', <co id="prog-co" linkends="b3"/>
		source         = 'main.c',
		target         = 'test_c_program',
		includes       = '.',
		uselib_local   = 'teststaticlib') <co id="uselib2-co" linkends="b4"/>
			</programlisting>

			<calloutlist>
				<callout arearefs="lib-co" id="b1">
					<para>A static library</para>
				</callout>
				<callout arearefs="uselib-co" id="b2">
					<para>Include paths to export for use with uselib_local (include paths are not added automatically). These folders are taken relatively to the current target.</para>
				</callout>
				<callout arearefs="prog-co" id="b3">
					<para>A program using the static library declared previously</para>
				</callout>
				<callout arearefs="uselib2-co" id="b4">
					<para>A list of references to existing libraries declared in the project (either a python list or a string containing the names space-separated)</para>
				</callout>
			</calloutlist>
		</para>
		<para>
			To link an application against various <emphasis>system libraries</emphasis>, several compilation flags and link flags must be given at once. To reduce the maintenance, a system called <emphasis>uselib</emphasis> can be used to give all the flags at the same time:
			<programlisting language="python">
def configure(conf):
	conf.env.CPPPATH_TEST = ['/usr/include'] <co id="uselib-cpppath-co" linkends="c1"/>

	if sys.platform != win32: <co id="uselib-check-co" linkends="c2"/>
		conf.env.CCDEFINES_TEST = ['-DTEST']
		conf.env.CCFLAGS_TEST   = ['-O0'] <co id="uselib-names-co" linkends="c3"/>
		conf.env.LIB_TEST       = 'test'
		conf.env.LIBPATH_TEST   = ['/usr/lib'] <co id="uselib-list-co" linkends="c4"/>
		conf.env.LINKFLAGS_TEST = ['-g']

def build(bld):
	mylib = bld.new_task_gen(
		features = 'cc cstaticlib',
		source   = 'test_staticlib.c',
		target   = 'teststaticlib',
		uselib   = 'TEST') <co id="uselib-map-co" linkends="c5"/>

	if mylib.env.CC_NAME == 'gcc':
		mylib.cxxflags = ['-O2'] <co id="uselib-dontuse-co" linkends="c6"/>
			</programlisting>
			<calloutlist>
				<callout arearefs="uselib-cpppath-co" id="c1">
					<para>For portability reasons, it is recommended to use CPPPATH instead of giving flags of the form -I/include. Note that the CPPPATH use used by both c and c++</para>
				</callout>
				<callout arearefs="uselib-check-co" id="c2">
					<para>Variables may be left undefined in platform-specific settings, yet the build scripts will remain identical.</para>
				</callout>
				<callout arearefs="uselib-names-co" id="c3">
					<para>Declare a few variables during the configuration, the variables follow the convention VAR_NAME</para>
				</callout>
				<callout arearefs="uselib-list-co" id="c4">
					<para>Values should be declared as lists excepts for LIB and STATICLIB</para>
				</callout>
				<callout arearefs="uselib-map-co" id="c5">
					<para>Add all the VAR_NAME corresponding to the uselib NAME, which is 'TEST' in this example</para>
				</callout>
				<callout arearefs="uselib-dontuse-co" id="c6">
					<para><emphasis>Model to avoid</emphasis>: setting the flags and checking for the configuration must be performed in the configuration section</para>
				</callout>
			</calloutlist>
			The variables used for c/c++ are the following: STATICLIB, LIB, LIBPATH, LINKFLAGS, RPATH, CXXFLAGS, CCFLAGS, CPPPATH, CPPFLAGS, CXXDEFINES, FRAMEWORK, FRAMEWORKPATH, CXXDEPS. The variables may be left empty for later use, and will not cause errors. During the development, the configuration cache files (for example, default.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.
		</para>
	</section>

	<section id="add_objects">
		<title>Customizing object files</title>
		<para>
			In some cases, it is necessary to re-use object files generated by another task generator to avoid recompilations. This is similar to copy-pasting code, so it is discouraged in general. Another use for this is to enable some compilation flags for specific files. The attribute "add_objects" can be used, like in the following example:
			<programlisting language="python">
def build(bld):
	some_objects = bld.new_task_gen(
		features       = 'cc', <co id="uobj-co1" linkends="uobj1"/>
		source         = 'test.c',
		ccflags        = '-O3',
		target         = 'my_objs')

	main = bld.new_task_gen(
		features       = 'cc cprogram',
		source         = 'main.c',
		ccflags        = '-O2', <co id="uobj-co2" linkends="uobj2"/>
		target         = 'test_c_program',
		add_objects    = 'my_objs') <co id="uobj-co3" linkends="uobj3"/>
			</programlisting>
			<calloutlist>
				<callout arearefs="uobj-co1" id="uobj1">
					<para>Files will be compiled in c mode, but no program or library will be produced</para>
				</callout>
				<callout arearefs="uobj-co1" id="uobj2">
					<para>Different compilation flags may be used</para>
				</callout>
				<callout arearefs="uobj-co3" id="uobj3">
					<para>The objects will be added automatically in the link stage</para>
				</callout>
			</calloutlist>
		</para>

	</section>

	<section id="config_headers">
		<title>Using configuration headers</title>
		<para>
			Adding lots of command-line define values increases the size of the command-line and conceals the useful information (differences). Some projects use headers which are generated during the configuration, they are not modified during the build and they are not installed or redistributed. This system is useful for huge projects, and has been made popular by autoconf-based projects.
		</para>
		<para>
			Writing configuration headers can be performed using the following methods:
			<programlisting language="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')
			</programlisting>
			The code snipped will produce the following <emphasis>config.h</emphasis> in the build directory:
			<programlisting language="sh">
build/
|-- c4che
|   |-- build.config.py
|   `-- default.cache.py
|-- config.log
`-- default
    `-- config.h
			</programlisting>
			The contents of the config.h for this example are
			<programlisting language="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 */
			</programlisting>
		</para>
	</section>

	<section id="include_sys">
		<title>The include system</title>
		<sect2>
			<title>The Waf preprocessor</title>
		<para>
			Include paths are used by the c/c++ compilers for finding the headers. When one header changes, the files are recompiled automatically. Includes must be given in the following form:
			<programlisting language="python">
def build(bld):
	bld.new_task_gen(
		features = 'cxx cprogram',
		source   = 'main.cpp',
		target   = 'test',
		includes = '. .. ../../')
			</programlisting>
			Local folders are given relative to the folder of the current script. The equivalent build directory folder is then added automatically for headers or source files produced in the build directory.
		</para>
		<para>
			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:
		<programlisting language="c">
#define mymacro "foo.h"
#include mymacro
		</programlisting>
			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 <emphasis>.moc</emphasis> extension must be detected by the build system and produced ahead of time. The c compiler could not parse such files.
		<programlisting language="c">
#include "foo.moc"
		</programlisting>
			Using the Waf preprocessor also makes the scripts strongly compiler-independent.
		</para>

		</sect2>

		<sect2>
			<title>Portability recommandations</title>
			<para>
				By default, the preprocessing does not climb to system headers. This may lead to missed depdendencies in the cases similar to the following:
				<programlisting language="c">
#if SOMEMACRO
	/* an include in the project */
	#include "foo.h"
#endif
				</programlisting>
				To write portable code and to ease debugging, it is strongly recommended to put all the conditions used in the project into a <emphasis>config.h</emphasis> file.
				<programlisting language="python">
def configure(conf):
	conf.check(
		fragment='int main() { return 0; }\n',
		define_name='FOO',
		mandatory=1)
	conf.write_config_header('config.h')
				</programlisting>
			</para>

			<para>Although it is discouraged, it is possible to enable the preprocessor to look into system headers by using the following code:
				<programlisting language="python">
import preproc
preproc.go_absolute = True
				</programlisting>
			</para>
		</sect2>

		<sect2>
			<title>Debugging dependencies</title>
			<para>
				The Waf preprocessor contains a specific debugging zone:
				<programlisting language="shell">
$ waf --zones=preproc
				</programlisting>
				To display the dependencies obtained or missed, use the following:
				<programlisting language="shell">
$ waf --zones=deps
				</programlisting>
				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.
			</para>
		</sect2>
	</section>

	<section id="config_helpers_c">
		<title>Configuration helpers</title>
		<para>
			The methods <emphasis>check_cc</emphasis> and <emphasis>check_cxx</emphasis> are used to detect parameters using a small build project. The main parameters are the following
			<itemizedlist>
				<listitem>msg: title of the test to execute</listitem>
				<listitem>okmsg: message to display when the test succeeds</listitem>
				<listitem>errmsg: message to display when the test fails</listitem>
				<listitem>mandatory: when true, raise a configuration exception if the test fails</listitem>
				<listitem>env: environment to use for the build (conf.env is used by default)</listitem>
				<listitem>define_name: add a define for the configuration header when the test succeeds (in most cases it is calculated automatically)</listitem>
			</itemizedlist>
			Here is a concrete example:
			<programlisting language="python">
def configure(conf):

	conf.check_cc(header_name='time.h') <co id="cc1-co" linkends="ccl"/>
	conf.check_cc(function_name='printf', header_name="stdio.h", mandatory=1) <co id="cc2-co" linkends="cc2"/>
	conf.check_cc(fragment='int main() {2+2==4;}\n', define_name="boobah") <co id="cc3-co" linkends="cc3"/>
	conf.check_cc(lib='m', ccflags='-Wall', defines=['var=foo', 'x=y'], uselib_store='M') <co id="cc4-co" linkends="cc4"/>
	conf.check_cc(lib='linux', uselib='M') <co id="cc5-co" linkends="cc5"/>

	conf.check_cc(fragment='''
			#include &lt;stdio.h&gt;
			int main() { printf("4"); return 0; } ''',
		define_name="booeah",
		execute="1",
		define_ret="1",
		msg="Checking for something") <co id="cc6-co" linkends="cc6"/>

	conf.write_config_header('config.h') <co id="cc7-co" linkends="cc7"/>
			</programlisting>
			<calloutlist>
				<callout arearefs="cc1-co" id="cc1">
					<para>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</para>
				</callout>
				<callout arearefs="cc2-co" id="cc2">
					<para>Try to compile a program with the function printf, adding the header stdio.h (the header_name may be a list of additional headers). The parameter mandatory will make the test raise an exception if it fails.</para>
				</callout>
				<callout arearefs="cc3-co" id="cc3">
					<para>Try to compile a piece of code, and if the test is successful, define the name boobah</para>
				</callout>
				<callout arearefs="cc4-co" id="cc4">
					<para>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 uselib variables LIB_M, CCFLAGS_M and DEFINE_M and the flag values are added to the configuration environment.</para>
				</callout>
				<callout arearefs="cc5-co" id="cc5">
					<para>Try to compile a simple c program against a library called 'linux', and reuse the previous parameters for libm (uselib)</para>
				</callout>
				<callout arearefs="cc6-co" id="cc6">
					<para>Execute a simple program, collect the output, and put it in a define when successful</para>
				</callout>
				<callout arearefs="cc7-co" id="cc7">
					<para>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.</para>
				</callout>
			</calloutlist>
			Here is an example of a <emphasis>config.h</emphasis> produced with the previous test code:
			<programlisting language="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 */
			</programlisting>
			The file <filename>default.cache.py</filename> will contain the following variables:
			<programlisting language="python">
CCDEFINES_M = ['var=foo', 'x=y']
CXXDEFINES_M = ['var=foo', 'x=y']
CXXFLAGS_M = ['-Wall']
CCFLAGS_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/default/config.h']
			</programlisting>
		</para>
	</section>

	<section id="pkg_config">
		<title>Pkg-config</title>
		<para>
			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 <emphasis>-config</emphasis>): <ulink url="http://pkg-config.freedesktop.org/wiki/">pkg-config</ulink>, wx-config, sdl-config, etc.
		</para>
		<para>
			Waf provides the method <emphasis>check_cfg</emphasis> for querying config parameters:
			<programlisting language="python">
def configure(conf):
	conf.check_cfg(atleast_pkgconfig_version='0.0.0') <co id="pk1-co" linkends="pk1"/>
	conf.check_cfg(package='pango', atleast_version='0.0.0') <co id="pk2-co" linkends="pk2"/>
	conf.check_cfg(package='pango', exact_version='0.21')
	conf.check_cfg(package='pango', max_version='9.0.0')
	conf.check_cfg(package='pango', args='--cflags --libs') <co id="pk3-co" linkends="pk3"/>
	pango_version = conf.check_cfg(modversion='pango') <co id="pk4-co" linkends="pk4"/>
	conf.check_cfg(path='sdl-config', args='--cflags --libs', package='', uselib_store='SDL') <co id="pk5-co" linkends="pk5"/>
			</programlisting>
			<calloutlist>
				<callout arearefs="pk1-co" id="pk1">
					<para>Check for the pkg-config version</para>
				</callout>
				<callout arearefs="pk2-co" id="pk2">
					<para>Check for a module version</para>
				</callout>
				<callout arearefs="pk3-co" id="pk3">
					<para>Obtain the flags for a package and assign them to the uselib PANGO (calculated automatically from the package name, can be overridden with the attribute "uselib_store='MYPANGO'")</para>
				</callout>
				<callout arearefs="pk4-co" id="pk4">
					<para>Retrieve the module version for a package. The returned object is a string containing the version number or an empty string in case of any errors. If there were no errors, 'PANGO_VERSION' is defined, can be overridden with the attribute "uselib_store='MYPANGO'".</para>
				</callout>
				<callout arearefs="pk5-co" id="pk5">
					<para>Obtain the flags for a different configuration program (sdl-config). The example is applicable for other configuration programs such as wx-config, pcre-config, etc</para>
				</callout>
			</calloutlist>
		</para>
		<para>
			Due to the amount of flags, the lack of standards between config applications, and to the output changing for different operating systems (-I for gcc, /I for msvc), the output of pkg-config is parsed, and the variables for the corresponding uselib are set in a go. The function <emphasis>parse_flags(line, uselib, env)</emphasis> in the Waf module config_c.py performs the output analysis.
		</para>
	</section>

</chapter>