<?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="execution"> <title>The scripting system</title> <section id="setup"> <title>Setting up a project</title> <para> The Waf projects are structured based on the following concepts: <itemizedlist> <listitem>Source directory: directory containing the source files that will be packaged and redistributed to other developers or to end users</listitem> <listitem>Build directory: directory containing the files generated by the project (configuration sets, build files, logs, etc)</listitem> <listitem>System files: files and folders which do not belong to the project (operating system files, etc)</listitem> </itemizedlist> </para> <para> When Waf is launched, it looks for the top-level project file, which are Python scripts. A valid Waf project requires at least a top-level Waf script file (named 'wscript' without any extension) containing the three following elements: <itemizedlist> <listitem>srcdir: string representing the source directory. In general, srcdir is set to '.', except for some proprietary projects where to wscript cannot be added to the top-level, srcdir may be set to '../..' or even some other folder such as '/checkout/perforce/project'</listitem> <listitem>blddir: 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 '..'</listitem> <listitem>configure: function called for setting up a project (also known as a 'Waf command')</listitem> </itemizedlist> The Waf script will always look for one valid top-level project file first before considering other project files. </para> <para> Now let us create a new Waf project in the folder <emphasis>/tmp/smallproject</emphasis>. The first step is to write a wscript file in <emphasis>/tmp/smallproject/wscript</emphasis> with the following contents: <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configuring the project') </programlisting> </para> <para> To use a Waf project for the first time, it is necessary to initialize it. Waf will then validate the project file, and create the cache files for later use (lock file, build directory, store the project options): <programlisting language="sh"> $ cd /tmp/smallproject <co id="init1-co" linkends="initl"/> $ tree . `-- wscript $ waf configure <co id="init2-co" linkends="init2"/> -> configuring the project 'configure' finished successfully (0.021s) $ tree . |-- build_directory/ <co id="init3-co" linkends="init3"/> | |-- c4che/ <co id="init4-co" linkends="init4"/> | | |-- build.config.py <co id="init5-co" linkends="init5"/> | | `-- default.cache.py <co id="init6-co" linkends="init6"/> | |-- config.log <co id="init7-co" linkends="init7"/> | `-- default/ <co id="init8-co" linkends="init8"/> |--.lock-wscript <co id="init9-co" linkends="init9"/> `-- wscript </programlisting> <calloutlist> <callout arearefs="init1-co" id="init1"><para>To configure the project, go to the folder containing the top-level project file</para></callout> <callout arearefs="init2-co" id="init2"><para>The execution is called by calling "waf configure"</para></callout> <callout arearefs="init3-co" id="init3"><para>The build directory was created</para></callout> <callout arearefs="init4-co" id="init4"><para>The configuration data is stored in the folder c4che/</para></callout> <callout arearefs="init5-co" id="init5"><para>The command-line options and environment variables in use are stored in this file</para></callout> <callout arearefs="init6-co" id="init6"><para>The user configuration set is stored in this file</para></callout> <callout arearefs="init7-co" id="init7"><para>Configuration log (duplicate of the output generated during the configuration)</para></callout> <callout arearefs="init8-co" id="init8"><para>Directory for the generated files (none so far)</para></callout> <callout arearefs="init9-co" id="init9"><para>Lock file pointing at the relevant project file and build directory</para></callout> </calloutlist> </para> </section> <section id="commands"> <title>Adding project commands</title> <para> In the last section, we have see the use of the command <emphasis>configure</emphasis> to initialize a project. Waf commands are special functions declared in the top-level project file and which may be called explicitely by <emphasis>waf commandname</emphasis>. Let us create two new commands <emphasis>print_ping</emphasis> and <emphasis>print_pong</emphasis> in the project created previously (/tmp/smallproject/) <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configuring the project') def print_ping(ctx): print(' ping!') def print_pong(ctx): print(' pong!') </programlisting> Waf commands always take a single parameter called the <emphasis>context</emphasis>. To execute the new commands: <programlisting language="sh"> $ cd /tmp/smallproject $ waf configure -> configuring the project 'configure' finished successfully (0.001s) $ waf print_ping ping! 'print_ping' finished successfully (0.000s) $ waf print_pong pong! 'print_pong' finished successfully (0.000s) </programlisting> Waf commands may also be executed at once by chaining them: <programlisting language="sh"> $ waf print_ping print_pong print_ping ping! 'print_ping' finished successfully (0.000s) pong! 'print_pong' finished successfully (0.000s) ping! 'print_ping' finished successfully (0.000s) </programlisting> Waf commands may be repeated several times too: <programlisting language="sh"> $ waf print_ping print_ping print_ping ping! 'print_ping' finished successfully (0.000s) pong! 'print_ping' finished successfully (0.000s) ping! 'print_ping' finished successfully (0.000s) </programlisting> </para> </section> <section id="distclean"> <title>Cleaning up a project</title> <para> Waf itself comes with a predefined command called <emphasis>distclean</emphasis> which removes the build directory and the lock file. After calling cleaning a project, it is necessary to configure it once again. <programlisting language="sh"> $ waf configure -> configuring the project 'configure' finished successfully (0.001s) $ waf print_ping ping! 'print_ping' finished successfully (0.000s) $ waf distclean 'distclean' finished successfully (0.001s) $ waf print_ping Project not configured (run 'waf configure' first) </programlisting> </para> <para> It is possible to override the behaviour of <emphasis>distclean</emphasis> by redefining it in the wscript file. For example, the following will cause it to avoid removing the build files. <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configuring the project') def distclean(ctx): print(' Not cleaning anything!') </programlisting> Upon execution: <programlisting language="sh"> $ waf distclean not cleaning anything! 'distclean' finished successfully (0.000s) </programlisting> </para> </section> <section id="dist"> <title>Packaging a project</title> <para> The command <emphasis>dist</emphasis> is another predefined utility which is used to create an archive of the project. By using the script presented previously: <programlisting language='python'> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configuring the project') </programlisting> Execute the command 'dist' to get: <programlisting language="sh"> $ waf configure -> configuring the project 'configure' finished successfully (0.001s) $ waf dist New archive created: noname-1.0.tar.bz2 (sha='c16c97a51b39c7e5bee35bb6d932a12e2952f2f8') 'dist' finished successfully (0.091s) </programlisting> </para> <para> 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: <programlisting language="python"> APPNAME='webe' VERSION='2.0' srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configuring the project') </programlisting> Because the project was configured once, it is not necessary to configure it once again: <programlisting language='sh'> $ waf dist New archive created: webe-2.0.tar.bz2 (sha='7ccc338e2ff99b46d97e5301793824e5941dd2be') 'dist' finished successfully (0.006s) </programlisting> <para> The default compression format is <ulink url="http://www.bzip.org/">bzip2</ulink>. It may be changed to <ulink url="http://www.gzip.org/">gzip</ulink> by using the following code: <programlisting language="python"> import Scripting Scripting.g_gz = 'gz' </programlisting> </para> </para> </section> <section id="recursion"> <title>Splitting a project into several files</title> <para> 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: <programlisting language="shell"> . |-- src | `-- wscript `-- wscript </programlisting> The commands in the top-level wscript will call the same commands from a subproject wscript file by calling a context method named <emphasis>recurse</emphasis>. <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configure from the top-level') ctx.recurse('src') def print_ping(ctx): print('-> ping from the top-level') ctx.recurse('src') </programlisting> Since src/wscript is not to point to a single project, it is not necessary to duplicate the srcdir and blddir variables. <programlisting language="python"> def configure(ctx): print('-> configure from src') def print_ping(ctx): print('-> ping from src') </programlisting> Upon execution, the results will be: <programlisting language="shell"> $ cd /tmp/smallproject $ waf configure print_ping -> configure from the top-level -> configure from src 'configure' finished successfully (0.080s) -> ping from the top-level -> ping from src 'print_ping' finished successfully (0.009s) </programlisting> </para> </section> <section id="build"> <title>Building, cleaning, installing and uninstalling a project</title> <para> The Waf command is used for building the actual software. Let's start again from the project file /tmp/smallfolder/wscript: <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): print('-> configure from the top-level') def build(ctx): print('building the software') </programlisting> Without surprize, the execution output will look like the following: <programlisting language="sh"> $ cd /tmp/smallproject $ waf Project not configured (run 'waf configure' first) $ waf configure -> configure from the top-level 'configure' finished successfully (0.001s) $ waf build Waf: Entering directory `/tmp/smallproject/build_directory' building the software Waf: Leaving directory `/tmp/smallproject/build_directory' 'build' finished successfully (0.004s) </programlisting> Since the command <emphasis>waf build</emphasis> is executed very often, a shortcut is provided to call it implicitely: <programlisting language="sh"> $ waf Waf: Entering directory `/tmp/smallproject/build_directory' building the software Waf: Leaving directory `/tmp/smallproject/build_directory' </programlisting> </para> <para> The Waf commands <emphasis>build, clean, install, uninstall</emphasis> are shortcuts to call <emphasis>build</emphasis> with different internal options. <programlisting language="sh"> $ waf build install uninstall clean Waf: Entering directory `/tmp/smallproject/build_directory' building the software Waf: Leaving directory `/tmp/smallproject/build_directory' 'build' finished successfully (0.004s) Waf: Entering directory `/tmp/smallproject/build_directory' building the software Waf: Leaving directory `/tmp/smallproject/build_directory' 'install' finished successfully (0.003s) Waf: Entering directory `/tmp/smallproject/build_directory' building the software Waf: Leaving directory `/tmp/smallproject/build_directory' 'uninstall' finished successfully (0.002s) building the software 'clean' finished successfully (0.002s) </programlisting> The meaning of the commands is the following: <itemizedlist> <listitem>build: process the source code to create the object files</listitem> <listitem>clean: remove the object files that were created during a build (unlike distclean, do not remove the configuration)</listitem> <listitem>install: check that all object files have been generated and copy them on the system (programs, libraries, data files, etc)</listitem> <listitem>uninstall: undo the installation, remove the object files from the system without touching the ones in the build directory</listitem> </itemizedlist> Object file creation and installation will be detailed in the next chapters. </para> </section> <section id="options"> <title>Customizing the command-line options</title> <para> The Waf script provides various default command-line options, which may be consulted by executing <emphasis>waf --help</emphasis>: <programlisting language="sh"> $ waf --help waf [command] [options] Main commands (example: ./waf build -j4) build : builds the project clean : removes the build files configure: configures the project dist : makes a tarball for redistributing the sources distcheck: checks if the sources compile (tarball from 'dist') distclean: removes the build directory install : installs the build files uninstall: removes the installed files Options: --version show program's version number and exit -h, --help show this help message and exit -j JOBS, --jobs=JOBS amount of parallel jobs (2) -k, --keep keep running happily on independent task groups -v, --verbose verbosity level -v -vv or -vvv [default: 0] --nocache ignore the WAFCACHE (if set) --zones=ZONES debugging zones (task_gen, deps, tasks, etc) -p, --progress -p: progress bar; -pp: ide output --targets=COMPILE_TARGETS build given task generators, e.g. "target1,target2" configuration options: --prefix=PREFIX installation prefix (configuration) [default: '/usr/local/'] installation options: --destdir=DESTDIR installation root [default: ''] -f, --force force file installation </programlisting> Accessing a command-line option is possible from any command. Here is how to access the value <emphasis>prefix</emphasis>: <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def configure(ctx): import Options print('-> prefix is ' + Options.options.prefix) </programlisting> Upon execution, the following will be observed: <programlisting language="sh"> $ waf configure -> prefix is /usr/local/ 'configure' finished successfully (0.001s) </programlisting> </para> <para> To define project command-line options, a special command named <emphasis>set_options</emphasis> may be defined in user scripts. This command will be called once before any other command executes. <programlisting language="python"> srcdir = '.' blddir = 'build_directory' def set_options(ctx): ctx.add_option('--foo', action='store', default=False, help='Silly test') def configure(ctx): import Options print('-> the value of foo is %r' % Options.options.foo) </programlisting> Upon execution, the following will be observed: <programlisting language="sh"> $ waf configure --foo=test -> the value of foo is 'test' 'configure' finished successfully (0.001s) </programlisting> The command context for set_options is a shortcut to access the optparse functionality. For more information on the optparse module, consult the <ulink url="http://docs.python.org/library/optparse.html">Python documentation</ulink>. </para> </section> </chapter>