add export-vis version of autowaf.py (before removing those changes from master)
authorPaul Davis <paul@linuxaudiosystems.com>
Tue, 5 Nov 2013 16:16:32 +0000 (11:16 -0500)
committerPaul Davis <paul@linuxaudiosystems.com>
Tue, 5 Nov 2013 16:16:32 +0000 (11:16 -0500)
tools/autowaf.py [new file with mode: 0644]

diff --git a/tools/autowaf.py b/tools/autowaf.py
new file mode 100644 (file)
index 0000000..87e19d5
--- /dev/null
@@ -0,0 +1,804 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Autowaf, useful waf utilities with support for recursive projects
+# Copyright 2008-2011 David Robillard
+#
+# Licensed under the GNU GPL v2 or later, see COPYING file for details.
+
+import glob
+import os
+import subprocess
+import sys
+
+from waflib import Configure, Context, Logs, Node, Options, Task, Utils
+from waflib.TaskGen import feature, before, after
+
+global g_is_child
+g_is_child = False
+
+# Only run autowaf hooks once (even if sub projects call several times)
+global g_step
+g_step = 0
+
+# Compute dependencies globally
+#import preproc
+#preproc.go_absolute = True
+
+@feature('c', 'cxx')
+@after('apply_incpaths')
+def include_config_h(self):
+    self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
+
+def set_options(opt, debug_by_default=False):
+    "Add standard autowaf options if they havn't been added yet"
+    global g_step
+    if g_step > 0:
+        return
+
+    # Install directory options
+    dirs_options = opt.add_option_group('Installation directories', '')
+
+    # Move --prefix and --destdir to directory options group
+    for k in ('--prefix', '--destdir'):
+        option = opt.parser.get_option(k)
+        if option:
+            opt.parser.remove_option(k)
+            dirs_options.add_option(option)
+
+    # Standard directory options
+    dirs_options.add_option('--bindir', type='string',
+                            help="Executable programs [Default: PREFIX/bin]")
+    dirs_options.add_option('--configdir', type='string',
+                            help="Configuration data [Default: PREFIX/etc]")
+    dirs_options.add_option('--datadir', type='string',
+                            help="Shared data [Default: PREFIX/share]")
+    dirs_options.add_option('--includedir', type='string',
+                            help="Header files [Default: PREFIX/include]")
+    dirs_options.add_option('--libdir', type='string',
+                            help="Libraries [Default: PREFIX/lib]")
+    dirs_options.add_option('--mandir', type='string',
+                            help="Manual pages [Default: DATADIR/man]")
+    dirs_options.add_option('--docdir', type='string',
+                            help="HTML documentation [Default: DATADIR/doc]")
+
+    # Build options
+    if debug_by_default:
+        opt.add_option('--optimize', action='store_false', default=True, dest='debug',
+                       help="Build optimized binaries")
+    else:
+        opt.add_option('--debug', action='store_true', default=False, dest='debug',
+                       help="Build debuggable binaries")
+
+    opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
+                       help="Build parallel-installable debuggable libraries with D suffix")
+
+    opt.add_option('--grind', action='store_true', default=False, dest='grind',
+                   help="Run tests in valgrind")
+    opt.add_option('--strict', action='store_true', default=False, dest='strict',
+                   help="Use strict compiler flags and show all warnings")
+    opt.add_option('--ultra-strict', action='store_true', default=False, dest='ultra_strict',
+                   help="Use even stricter compiler flags (likely to trigger many warnings in library headers)")
+    opt.add_option('--docs', action='store_true', default=False, dest='docs',
+                   help="Build documentation - requires doxygen")
+
+    # LV2 options
+    opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
+                   help="Install LV2 bundles to user location")
+    opt.add_option('--lv2-system', action='store_true', default=False, dest='lv2_system',
+                   help="Install LV2 bundles to system location")
+    dirs_options.add_option('--lv2dir', type='string',
+                            help="LV2 bundles [Default: LIBDIR/lv2]")
+    g_step = 1
+
+def check_header(conf, lang, name, define='', mandatory=True):
+    "Check for a header"
+    includes = '' # search default system include paths
+    if sys.platform == "darwin":
+        includes = '/opt/local/include'
+
+    if lang == 'c':
+        check_func = conf.check_cc
+    elif lang == 'cxx':
+        check_func = conf.check_cxx
+    else:
+        Logs.error("Unknown header language `%s'" % lang)
+        return
+
+    if define != '':
+        check_func(header_name=name, includes=includes,
+                   define_name=define, mandatory=mandatory)
+    else:
+        check_func(header_name=name, includes=includes, mandatory=mandatory)
+
+def nameify(name):
+    return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
+
+def define(conf, var_name, value):
+    conf.define(var_name, value)
+    conf.env[var_name] = value
+
+def check_pkg(conf, name, **args):
+    "Check for a package iff it hasn't been checked for yet"
+    if args['uselib_store'].lower() in conf.env['AUTOWAF_LOCAL_LIBS']:
+        return
+    class CheckType:
+        OPTIONAL=1
+        MANDATORY=2
+    var_name = 'CHECKED_' + nameify(args['uselib_store'])
+    check = not var_name in conf.env
+    mandatory = not 'mandatory' in args or args['mandatory']
+    if not check and 'atleast_version' in args:
+        # Re-check if version is newer than previous check
+        checked_version = conf.env['VERSION_' + name]
+        if checked_version and checked_version < args['atleast_version']:
+            check = True;
+    if not check and mandatory and conf.env[var_name] == CheckType.OPTIONAL:
+        # Re-check if previous check was optional but this one is mandatory
+        check = True;
+    if check:
+        found = None
+        pkg_var_name = 'PKG_' + name.replace('-', '_')
+        pkg_name = name
+        if conf.env.PARDEBUG:
+            args['mandatory'] = False  # Smash mandatory arg
+            found = conf.check_cfg(package=pkg_name + 'D', args="--cflags --libs", **args)
+            if found:
+                pkg_name += 'D'
+        if mandatory:
+            args['mandatory'] = True  # Unsmash mandatory arg
+        if not found:
+            found = conf.check_cfg(package=pkg_name, args="--cflags --libs", **args)
+        if found:
+            conf.env[pkg_var_name] = pkg_name
+        if 'atleast_version' in args:
+            conf.env['VERSION_' + name] = args['atleast_version']
+    if mandatory:
+        conf.env[var_name] = CheckType.MANDATORY
+    else:
+        conf.env[var_name] = CheckType.OPTIONAL
+
+
+def normpath(path):
+    if sys.platform == 'win32':
+        return os.path.normpath(path).replace('\\', '/')
+    else:
+        return os.path.normpath(path)
+
+def configure(conf):
+    global g_step
+    if g_step > 1:
+        return
+    def append_cxx_flags(flags):
+        conf.env.append_value('CFLAGS', flags)
+        conf.env.append_value('CXXFLAGS', flags)
+    print('')
+    display_header('Global Configuration')
+
+    if Options.options.docs:
+        conf.load('doxygen')
+
+    conf.env['DOCS'] = Options.options.docs
+    conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
+    conf.env['PARDEBUG'] = Options.options.pardebug
+    conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
+
+    def config_dir(var, opt, default):
+        if opt:
+            conf.env[var] = normpath(opt)
+        else:
+            conf.env[var] = normpath(default)
+
+    opts   = Options.options
+    prefix = conf.env['PREFIX']
+
+    config_dir('BINDIR',     opts.bindir,     os.path.join(prefix, 'bin'))
+    config_dir('SYSCONFDIR', opts.configdir,  os.path.join(prefix, 'etc'))
+    config_dir('DATADIR',    opts.datadir,    os.path.join(prefix, 'share'))
+    config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
+    config_dir('LIBDIR',     opts.libdir,     os.path.join(prefix, 'lib'))
+    config_dir('MANDIR',     opts.mandir,     os.path.join(conf.env['DATADIR'], 'man'))
+    config_dir('DOCDIR',     opts.docdir,     os.path.join(conf.env['DATADIR'], 'doc'))
+
+    if Options.options.lv2dir:
+        conf.env['LV2DIR'] = Options.options.lv2dir
+    elif Options.options.lv2_user:
+        if sys.platform == "darwin":
+            conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
+        elif sys.platform == "win32":
+            conf.env['LV2DIR'] = os.path.join(os.getenv('APPDATA'), 'LV2')
+        else:
+            conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
+    elif Options.options.lv2_system:
+        if sys.platform == "darwin":
+            conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
+        elif sys.platform == "win32":
+            conf.env['LV2DIR'] = os.path.join(os.getenv('COMMONPROGRAMFILES'), 'LV2')
+        else:
+            conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
+    else:
+        conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
+
+    conf.env['LV2DIR'] = normpath(conf.env['LV2DIR'])
+
+    if Options.options.docs:
+        doxygen = conf.find_program('doxygen')
+        if not doxygen:
+            conf.fatal("Doxygen is required to build with --docs")
+
+        dot = conf.find_program('dot')
+        if not dot:
+            conf.fatal("Graphviz (dot) is required to build with --docs")
+
+    if Options.options.debug:
+        if conf.env['MSVC_COMPILER']:
+            conf.env['CFLAGS']    = ['/Od', '/Zi', '/MTd']
+            conf.env['CXXFLAGS']  = ['/Od', '/Zi', '/MTd']
+            conf.env['LINKFLAGS'] = ['/DEBUG']
+        else:
+            conf.env['CFLAGS']   = ['-O0', '-g']
+            conf.env['CXXFLAGS'] = ['-O0',  '-g']
+    else:
+        if conf.env['MSVC_COMPILER']:
+            conf.env['CFLAGS']    = ['/MD']
+            conf.env['CXXFLAGS']  = ['/MD']
+        append_cxx_flags(['-DNDEBUG'])
+
+    if Options.options.ultra_strict:
+        Options.options.strict = True
+        conf.env.append_value('CFLAGS', ['-Wredundant-decls',
+                                         '-Wstrict-prototypes',
+                                         '-Wmissing-prototypes'])
+
+    if Options.options.strict:
+        conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
+        conf.env.append_value('CXXFLAGS', ['-ansi',
+                                           '-Wnon-virtual-dtor',
+                                           '-Woverloaded-virtual'])
+        append_cxx_flags(['-Wall',
+                          '-Wcast-align',
+                          '-Wextra',
+                          '-Wwrite-strings'])
+        if sys.platform != "darwin":
+            # this is really only to be avoid on OLD apple gcc, but not sure how to version check
+            append_cxx_flags(['-fstrict-overflow'])
+
+        if not conf.check_cc(fragment = '''
+#ifndef __clang__
+#error
+#endif
+int main() { return 0; }''',
+                         features  = 'c',
+                         mandatory = False,
+                         execute   = False,
+                         msg       = 'Checking for clang'):
+            if sys.platform != "darwin":
+                # this is really only to be avoid on OLD apple gcc, but not sure how to version check
+                append_cxx_flags(['-Wunsafe-loop-optimizations'])
+                # this is invalid (still) on Lion apple gcc
+                append_cxx_flags(['-Wlogical-op'])
+            
+
+    if not conf.env['MSVC_COMPILER']:
+        append_cxx_flags(['-fshow-column'])
+
+    conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
+    conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
+
+    display_msg(conf, "Install prefix", conf.env['PREFIX'])
+    display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
+    display_msg(conf, "Build documentation", str(conf.env['DOCS']))
+    print('')
+
+    g_step = 2
+
+def set_c99_mode(conf):
+    if conf.env.MSVC_COMPILER:
+        # MSVC has no hope or desire to compile C99, just compile as C++
+        conf.env.append_unique('CFLAGS', ['-TP'])
+    else:
+        conf.env.append_unique('CFLAGS', ['-std=c99'])
+
+def set_local_lib(conf, name, has_objects):
+    var_name = 'HAVE_' + nameify(name.upper())
+    define(conf, var_name, 1)
+    if has_objects:
+        if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
+            conf.env['AUTOWAF_LOCAL_LIBS'] = {}
+        conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
+    else:
+        if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
+            conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
+        conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
+
+def append_property(obj, key, val):
+    if hasattr(obj, key):
+        setattr(obj, key, getattr(obj, key) + val)
+    else:
+        setattr(obj, key, val)
+
+def use_lib(bld, obj, libs):
+    abssrcdir = os.path.abspath('.')
+    libs_list = libs.split()
+    for l in libs_list:
+        in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
+        in_libs    = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
+        if in_libs:
+            append_property(obj, 'use', ' lib%s ' % l.lower())
+            append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
+        if in_headers or in_libs:
+            inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
+            for f in ['CFLAGS', 'CXXFLAGS']:
+                if not inc_flag in bld.env[f]:
+                    bld.env.prepend_value(f, inc_flag)
+        else:
+            append_property(obj, 'uselib', ' ' + l)
+
+@feature('c', 'cxx')
+@before('apply_link')
+def version_lib(self):
+    if sys.platform == 'win32':
+        self.vnum = None  # Prevent waf from automatically appending -0
+    if self.env['PARDEBUG']:
+        applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
+        if [x for x in applicable if x in self.features]:
+            self.target = self.target + 'D'
+
+def set_lib_env(conf, name, version):
+    'Set up environment for local library as if found via pkg-config.'
+    NAME         = name.upper()
+    major_ver    = version.split('.')[0]
+    pkg_var_name = 'PKG_' + name.replace('-', '_')
+    lib_name     = '%s-%s' % (name, major_ver)
+    if conf.env.PARDEBUG:
+        lib_name += 'D'
+    conf.env[pkg_var_name]       = lib_name
+    conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
+    conf.env['LIBPATH_' + NAME]  = [conf.env.LIBDIR]
+    conf.env['LIB_' + NAME]      = [lib_name]
+
+def display_header(title):
+    Logs.pprint('BOLD', title)
+
+def display_msg(conf, msg, status = None, color = None):
+    color = 'CYAN'
+    if type(status) == bool and status or status == "True":
+        color = 'GREEN'
+    elif type(status) == bool and not status or status == "False":
+        color = 'YELLOW'
+    Logs.pprint('BOLD', " *", sep='')
+    Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
+    Logs.pprint('BOLD', ":", sep='')
+    Logs.pprint(color, status)
+
+def link_flags(env, lib):
+    return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
+
+def compile_flags(env, lib):
+    return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
+
+def set_recursive():
+    global g_is_child
+    g_is_child = True
+
+def is_child():
+    global g_is_child
+    return g_is_child
+
+# Pkg-config file
+def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
+    '''Build a pkg-config file for a library.
+    name           -- uppercase variable name     (e.g. 'SOMENAME')
+    version        -- version string              (e.g. '1.2.3')
+    version_suffix -- name version suffix         (e.g. '2')
+    libs           -- string/list of dependencies (e.g. 'LIBFOO GLIB')
+    '''
+    pkg_prefix       = bld.env['PREFIX']
+    if pkg_prefix[-1] == '/':
+        pkg_prefix = pkg_prefix[:-1]
+
+    target = name.lower()
+    if version_suffix != '':
+        target += '-' + version_suffix
+
+    if bld.env['PARDEBUG']:
+        target += 'D'
+
+    target += '.pc'
+
+    libdir = bld.env['LIBDIR']
+    if libdir.startswith(pkg_prefix):
+        libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
+
+    includedir = bld.env['INCLUDEDIR']
+    if includedir.startswith(pkg_prefix):
+        includedir = includedir.replace(pkg_prefix, '${prefix}')
+
+    obj = bld(features     = 'subst',
+              source       = '%s.pc.in' % name.lower(),
+              target       = target,
+              install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
+              exec_prefix  = '${prefix}',
+              PREFIX       = pkg_prefix,
+              EXEC_PREFIX  = '${prefix}',
+              LIBDIR       = libdir,
+              INCLUDEDIR   = includedir)
+
+    if type(libs) != list:
+        libs = libs.split()
+
+    subst_dict[name + '_VERSION'] = version
+    subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
+    for i in libs:
+        subst_dict[i + '_LIBS']   = link_flags(bld.env, i)
+        lib_cflags = compile_flags(bld.env, i)
+        if lib_cflags == '':
+            lib_cflags = ' '
+        subst_dict[i + '_CFLAGS'] = lib_cflags
+
+    obj.__dict__.update(subst_dict)
+
+def build_dir(name, subdir):
+    if is_child():
+        return os.path.join('build', name, subdir)
+    else:
+        return os.path.join('build', subdir)
+
+# Clean up messy Doxygen documentation after it is built
+def make_simple_dox(name):
+    name = name.lower()
+    NAME = name.upper()
+    try:
+        top = os.getcwd()
+        os.chdir(build_dir(name, 'doc/html'))
+        page = 'group__%s.html' % name
+        if not os.path.exists(page):
+            return
+        for i in [
+            ['%s_API ' % NAME, ''],
+            ['%s_DEPRECATED ' % NAME, ''],
+            ['group__%s.html' % name, ''],
+            ['&#160;', ''],
+            ['<script.*><\/script>', ''],
+            ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
+            ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
+             ''],
+            ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
+             'Doxygen']]:
+            os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
+        os.rename('group__%s.html' % name, 'index.html')
+        for i in (glob.glob('*.png') +
+                  glob.glob('*.html') +
+                  glob.glob('*.js') +
+                  glob.glob('*.css')):
+            if i != 'index.html' and i != 'style.css':
+                os.remove(i)
+        os.chdir(top)
+        os.chdir(build_dir(name, 'doc/man/man3'))
+        for i in glob.glob('*.3'):
+            os.system("sed -i 's/%s_API //' %s" % (NAME, i))
+        for i in glob.glob('_*'):
+            os.remove(i)
+        os.chdir(top)
+    except Exception as e:
+        Logs.error("Failed to fix up %s documentation: %s" % (name, e))
+
+# Doxygen API documentation
+def build_dox(bld, name, version, srcdir, blddir, outdir=''):
+    if not bld.env['DOCS']:
+        return
+
+    if is_child():
+        src_dir = os.path.join(srcdir, name.lower())
+        doc_dir = os.path.join(blddir, name.lower(), 'doc')
+    else:
+        src_dir = srcdir
+        doc_dir = os.path.join(blddir, 'doc')
+
+    subst_tg = bld(features     = 'subst',
+                   source       = 'doc/reference.doxygen.in',
+                   target       = 'doc/reference.doxygen',
+                   install_path = '',
+                   name         = 'doxyfile')
+
+    subst_dict = {
+        name + '_VERSION' : version,
+        name + '_SRCDIR'  : os.path.abspath(src_dir),
+        name + '_DOC_DIR' : os.path.abspath(doc_dir)
+        }
+
+    subst_tg.__dict__.update(subst_dict)
+
+    subst_tg.post()
+
+    docs = bld(features = 'doxygen',
+               doxyfile = 'doc/reference.doxygen')
+
+    docs.post()
+
+    major = int(version[0:version.find('.')])
+    bld.install_files(
+        os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
+        bld.path.get_bld().ant_glob('doc/html/*'))
+    for i in range(1, 8):
+        bld.install_files('${MANDIR}/man%d' % i,
+                          bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
+                                                      excl='**/_*'))
+
+# Version code file generation
+def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
+    header_path = os.path.abspath(header_path)
+    source_path = os.path.abspath(source_path)
+    text  = "int " + domain + "_major_version = " + str(major) + ";\n"
+    text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
+    text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
+    try:
+        o = open(source_path, 'w')
+        o.write(text)
+        o.close()
+    except IOError:
+        Logs.error('Failed to open %s for writing\n' % source_path)
+        sys.exit(-1)
+
+    text  = "#ifndef __" + domain + "_version_h__\n"
+    text += "#define __" + domain + "_version_h__\n"
+    if visheader != '':
+        text += "#include \"" + visheader + "\"\n"
+    text += exportname + " extern const char* " + domain + "_revision;\n"
+    text += exportname + " extern int " + domain + "_major_version;\n"
+    text += exportname + " extern int " + domain + "_minor_version;\n"
+    text += exportname + " extern int " + domain + "_micro_version;\n"
+    text += "#endif /* __" + domain + "_version_h__ */\n"
+    try:
+        o = open(header_path, 'w')
+        o.write(text)
+        o.close()
+    except IOError:
+        Logs.warn('Failed to open %s for writing\n' % header_path)
+        sys.exit(-1)
+
+    return None
+
+def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
+    Logs.info('Generating pot file from %s' % name)
+    pot_file = '%s.pot' % name
+
+    cmd = ['xgettext',
+            '--keyword=_',
+            '--keyword=N_',
+            '--keyword=S_',
+            '--keyword=P_:1,2',
+            '--from-code=UTF-8',
+            '-o', pot_file]
+
+    if copyright_holder:
+        cmd += ['--copyright-holder="%s"' % copyright_holder]
+
+    cmd += sources
+    Logs.info('Updating ' + pot_file)
+    subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
+
+def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
+    pwd = os.getcwd()
+    os.chdir(os.path.join(srcdir, dir))
+    pot_file = '%s.pot' % name
+    po_files = glob.glob('po/*.po')
+    for po_file in po_files:
+        cmd = ['msgmerge',
+               '--update',
+               '--no-fuzzy-matching',
+               po_file,
+               pot_file]
+        Logs.info('Updating ' + po_file)
+        subprocess.call(cmd)
+    os.chdir(pwd)
+
+def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
+    pwd = os.getcwd()
+    os.chdir(os.path.join(srcdir, dir))
+    pot_file = '%s.pot' % name
+    po_files = glob.glob('po/*.po')
+    for po_file in po_files:
+        mo_file = po_file.replace('.po', '.mo')
+        cmd = ['msgfmt',
+               '-c',
+               '-f',
+               '-o',
+               mo_file,
+               po_file]
+        Logs.info('Generating ' + po_file)
+        subprocess.call(cmd)
+    os.chdir(pwd)
+
+def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
+    build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
+    build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
+    build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
+
+def cd_to_build_dir(ctx, appname):
+    orig_dir  = os.path.abspath(os.curdir)
+    top_level = (len(ctx.stack_path) > 1)
+    if top_level:
+        os.chdir(os.path.join('build', appname))
+    else:
+        os.chdir('build')
+    Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
+
+def cd_to_orig_dir(ctx, child):
+    if child:
+        os.chdir(os.path.join('..', '..'))
+    else:
+        os.chdir('..')
+
+def pre_test(ctx, appname, dirs=['src']):
+    diropts  = ''
+    for i in dirs:
+        diropts += ' -d ' + i
+    cd_to_build_dir(ctx, appname)
+    clear_log = open('lcov-clear.log', 'w')
+    try:
+        try:
+            # Clear coverage data
+            subprocess.call(('lcov %s -z' % diropts).split(),
+                            stdout=clear_log, stderr=clear_log)
+        except:
+            Logs.warn('Failed to run lcov, no coverage report will be generated')
+    finally:
+        clear_log.close()
+
+def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
+    diropts  = ''
+    for i in dirs:
+        diropts += ' -d ' + i
+    coverage_log           = open('lcov-coverage.log', 'w')
+    coverage_lcov          = open('coverage.lcov', 'w')
+    coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
+    try:
+        try:
+            base = '.'
+            if g_is_child:
+                base = '..'
+
+            # Generate coverage data
+            subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
+                            stdout=coverage_lcov, stderr=coverage_log)
+    
+            # Strip unwanted stuff
+            subprocess.call(
+                ['lcov', '--remove', 'coverage.lcov'] + remove,
+                stdout=coverage_stripped_lcov, stderr=coverage_log)
+    
+            # Generate HTML coverage output
+            if not os.path.isdir('coverage'):
+                os.makedirs('coverage')
+            subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
+                            stdout=coverage_log, stderr=coverage_log)
+    
+        except:
+            Logs.warn('Failed to run lcov, no coverage report will be generated')
+    finally:
+        coverage_stripped_lcov.close()
+        coverage_lcov.close()
+        coverage_log.close()
+
+        print('')
+        Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
+        top_level = (len(ctx.stack_path) > 1)
+        if top_level:
+            cd_to_orig_dir(ctx, top_level)
+
+    print('')
+    Logs.pprint('BOLD', 'Coverage:', sep='')
+    print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
+
+def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
+    failures = 0
+    diropts  = ''
+    for i in dirs:
+        diropts += ' -d ' + i
+
+    # Run all tests
+    for i in tests:
+        s = i
+        if type(i) == type([]):
+            s = ' '.join(i)
+        print('')
+        Logs.pprint('BOLD', '** Test', sep='')
+        Logs.pprint('NORMAL', '%s' % s)
+        cmd = i
+        if Options.options.grind:
+            cmd = 'valgrind ' + i
+        if subprocess.call(cmd, shell=True) == desired_status:
+            Logs.pprint('GREEN', '** Pass')
+        else:
+            failures += 1
+            Logs.pprint('RED', '** FAIL')
+
+    print('')
+    if failures == 0:
+        Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
+    else:
+        Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
+
+def run_ldconfig(ctx):
+    if (ctx.cmd == 'install'
+        and not ctx.env['RAN_LDCONFIG']
+        and ctx.env['LIBDIR']
+        and not 'DESTDIR' in os.environ
+        and not Options.options.destdir):
+        try:
+            Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
+            subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
+            ctx.env['RAN_LDCONFIG'] = True
+        except:
+            pass
+
+def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
+    import rdflib
+    import textwrap
+    from time import strftime, strptime
+
+    doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
+    dcs  = rdflib.Namespace('http://ontologi.es/doap-changeset#')
+    rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
+    foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
+    rdf  = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+    m    = rdflib.ConjunctiveGraph()
+
+    try:
+        for i in in_files:
+            m.parse(i, format='n3')
+    except:
+        Logs.warn('Error parsing data, unable to generate NEWS')
+        return
+
+    proj = m.value(None, rdf.type, doap.Project)
+    for f in m.triples([proj, rdfs.seeAlso, None]):
+        if f[2].endswith('.ttl'):
+            m.parse(f[2], format='n3')
+
+    entries = {}
+    for r in m.triples([proj, doap.release, None]):
+        release   = r[2]
+        revision  = m.value(release, doap.revision, None)
+        date      = m.value(release, doap.created, None)
+        blamee    = m.value(release, dcs.blame, None)
+        changeset = m.value(release, dcs.changeset, None)
+        dist      = m.value(release, doap['file-release'], None)
+
+        if revision and date and blamee and changeset:
+            entry = '%s (%s) stable;\n' % (name, revision)
+
+            for i in m.triples([changeset, dcs.item, None]):
+                item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
+                entry += '\n  * ' + '\n    '.join(item)
+                if dist and top_entries is not None:
+                    if not str(dist) in top_entries:
+                        top_entries[str(dist)] = []
+                    top_entries[str(dist)] += [
+                        '%s: %s' % (name, '\n    '.join(item))]
+
+            if extra_entries:
+                for i in extra_entries[str(dist)]:
+                    entry += '\n  * ' + i
+
+            entry += '\n\n --'
+
+            blamee_name = m.value(blamee, foaf.name, None)
+            blamee_mbox = m.value(blamee, foaf.mbox, None)
+            if blamee_name and blamee_mbox:
+                entry += ' %s <%s>' % (blamee_name,
+                                       blamee_mbox.replace('mailto:', ''))
+                
+            entry += '  %s\n\n' % (
+                strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
+
+            entries[revision] = entry
+        else:
+            Logs.warn('Ignored incomplete %s release description' % name)
+
+    if len(entries) > 0:
+        news = open(out_file, 'w')
+        for e in sorted(entries.keys(), reverse=True):
+            news.write(entries[e])
+        news.close()