Merge branch 'master' into cairocanvas
[ardour.git] / tools / autowaf.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Autowaf, useful waf utilities with support for recursive projects
5 # Copyright 2008-2011 David Robillard
6 #
7 # Licensed under the GNU GPL v2 or later, see COPYING file for details.
8
9 import glob
10 import os
11 import subprocess
12 import sys
13
14 from waflib import Configure, Context, Logs, Node, Options, Task, Utils
15 from waflib.TaskGen import feature, before, after
16
17 global g_is_child
18 g_is_child = False
19
20 # Only run autowaf hooks once (even if sub projects call several times)
21 global g_step
22 g_step = 0
23
24 # Compute dependencies globally
25 #import preproc
26 #preproc.go_absolute = True
27
28 @feature('c', 'cxx')
29 @after('apply_incpaths')
30 def include_config_h(self):
31     self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
32
33 def set_options(opt, debug_by_default=False):
34     "Add standard autowaf options if they havn't been added yet"
35     global g_step
36     if g_step > 0:
37         return
38
39     # Install directory options
40     dirs_options = opt.add_option_group('Installation directories', '')
41
42     # Move --prefix and --destdir to directory options group
43     for k in ('--prefix', '--destdir'):
44         option = opt.parser.get_option(k)
45         if option:
46             opt.parser.remove_option(k)
47             dirs_options.add_option(option)
48
49     # Standard directory options
50     dirs_options.add_option('--bindir', type='string',
51                             help="Executable programs [Default: PREFIX/bin]")
52     dirs_options.add_option('--configdir', type='string',
53                             help="Configuration data [Default: PREFIX/etc]")
54     dirs_options.add_option('--datadir', type='string',
55                             help="Shared data [Default: PREFIX/share]")
56     dirs_options.add_option('--includedir', type='string',
57                             help="Header files [Default: PREFIX/include]")
58     dirs_options.add_option('--libdir', type='string',
59                             help="Libraries [Default: PREFIX/lib]")
60     dirs_options.add_option('--mandir', type='string',
61                             help="Manual pages [Default: DATADIR/man]")
62     dirs_options.add_option('--docdir', type='string',
63                             help="HTML documentation [Default: DATADIR/doc]")
64
65     # Build options
66     if debug_by_default:
67         opt.add_option('--optimize', action='store_false', default=True, dest='debug',
68                        help="Build optimized binaries")
69     else:
70         opt.add_option('--debug', action='store_true', default=False, dest='debug',
71                        help="Build debuggable binaries")
72
73     opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
74                        help="Build parallel-installable debuggable libraries with D suffix")
75
76     opt.add_option('--grind', action='store_true', default=False, dest='grind',
77                    help="Run tests in valgrind")
78     opt.add_option('--strict', action='store_true', default=False, dest='strict',
79                    help="Use strict compiler flags and show all warnings")
80     opt.add_option('--ultra-strict', action='store_true', default=False, dest='ultra_strict',
81                    help="Use even stricter compiler flags (likely to trigger many warnings in library headers)")
82     opt.add_option('--docs', action='store_true', default=False, dest='docs',
83                    help="Build documentation - requires doxygen")
84
85     # LV2 options
86     opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
87                    help="Install LV2 bundles to user location")
88     opt.add_option('--lv2-system', action='store_true', default=False, dest='lv2_system',
89                    help="Install LV2 bundles to system location")
90     dirs_options.add_option('--lv2dir', type='string',
91                             help="LV2 bundles [Default: LIBDIR/lv2]")
92     g_step = 1
93
94 def check_header(conf, lang, name, define='', mandatory=True):
95     "Check for a header"
96     includes = '' # search default system include paths
97     if sys.platform == "darwin":
98         includes = '/opt/local/include'
99
100     if lang == 'c':
101         check_func = conf.check_cc
102     elif lang == 'cxx':
103         check_func = conf.check_cxx
104     else:
105         Logs.error("Unknown header language `%s'" % lang)
106         return
107
108     if define != '':
109         check_func(header_name=name, includes=includes,
110                    define_name=define, mandatory=mandatory)
111     else:
112         check_func(header_name=name, includes=includes, mandatory=mandatory)
113
114 def nameify(name):
115     return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
116
117 def define(conf, var_name, value):
118     conf.define(var_name, value)
119     conf.env[var_name] = value
120
121 def check_pkg(conf, name, **args):
122     "Check for a package iff it hasn't been checked for yet"
123     if args['uselib_store'].lower() in conf.env['AUTOWAF_LOCAL_LIBS']:
124         return
125     class CheckType:
126         OPTIONAL=1
127         MANDATORY=2
128     var_name = 'CHECKED_' + nameify(args['uselib_store'])
129     check = not var_name in conf.env
130     mandatory = not 'mandatory' in args or args['mandatory']
131     if not check and 'atleast_version' in args:
132         # Re-check if version is newer than previous check
133         checked_version = conf.env['VERSION_' + name]
134         if checked_version and checked_version < args['atleast_version']:
135             check = True;
136     if not check and mandatory and conf.env[var_name] == CheckType.OPTIONAL:
137         # Re-check if previous check was optional but this one is mandatory
138         check = True;
139     if check:
140         found = None
141         pkg_var_name = 'PKG_' + name.replace('-', '_')
142         pkg_name = name
143         if conf.env.PARDEBUG:
144             args['mandatory'] = False  # Smash mandatory arg
145             found = conf.check_cfg(package=pkg_name + 'D', args="--cflags --libs", **args)
146             if found:
147                 pkg_name += 'D'
148         if mandatory:
149             args['mandatory'] = True  # Unsmash mandatory arg
150         if not found:
151             found = conf.check_cfg(package=pkg_name, args="--cflags --libs", **args)
152         if found:
153             conf.env[pkg_var_name] = pkg_name
154         if 'atleast_version' in args:
155             conf.env['VERSION_' + name] = args['atleast_version']
156     if mandatory:
157         conf.env[var_name] = CheckType.MANDATORY
158     else:
159         conf.env[var_name] = CheckType.OPTIONAL
160
161
162 def normpath(path):
163     if sys.platform == 'win32':
164         return os.path.normpath(path).replace('\\', '/')
165     else:
166         return os.path.normpath(path)
167
168 def configure(conf):
169     global g_step
170     if g_step > 1:
171         return
172     def append_cxx_flags(flags):
173         conf.env.append_value('CFLAGS', flags)
174         conf.env.append_value('CXXFLAGS', flags)
175     print('')
176     display_header('Global Configuration')
177
178     if Options.options.docs:
179         conf.load('doxygen')
180
181     conf.env['DOCS'] = Options.options.docs
182     conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
183     conf.env['PARDEBUG'] = Options.options.pardebug
184     conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
185
186     def config_dir(var, opt, default):
187         if opt:
188             conf.env[var] = normpath(opt)
189         else:
190             conf.env[var] = normpath(default)
191
192     opts   = Options.options
193     prefix = conf.env['PREFIX']
194
195     config_dir('BINDIR',     opts.bindir,     os.path.join(prefix, 'bin'))
196     config_dir('SYSCONFDIR', opts.configdir,  os.path.join(prefix, 'etc'))
197     config_dir('DATADIR',    opts.datadir,    os.path.join(prefix, 'share'))
198     config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
199     config_dir('LIBDIR',     opts.libdir,     os.path.join(prefix, 'lib'))
200     config_dir('MANDIR',     opts.mandir,     os.path.join(conf.env['DATADIR'], 'man'))
201     config_dir('DOCDIR',     opts.docdir,     os.path.join(conf.env['DATADIR'], 'doc'))
202
203     if Options.options.lv2dir:
204         conf.env['LV2DIR'] = Options.options.lv2dir
205     elif Options.options.lv2_user:
206         if sys.platform == "darwin":
207             conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
208         elif sys.platform == "win32":
209             conf.env['LV2DIR'] = os.path.join(os.getenv('APPDATA'), 'LV2')
210         else:
211             conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
212     elif Options.options.lv2_system:
213         if sys.platform == "darwin":
214             conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
215         elif sys.platform == "win32":
216             conf.env['LV2DIR'] = os.path.join(os.getenv('COMMONPROGRAMFILES'), 'LV2')
217         else:
218             conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
219     else:
220         conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
221
222     conf.env['LV2DIR'] = normpath(conf.env['LV2DIR'])
223
224     if Options.options.docs:
225         doxygen = conf.find_program('doxygen')
226         if not doxygen:
227             conf.fatal("Doxygen is required to build with --docs")
228
229         dot = conf.find_program('dot')
230         if not dot:
231             conf.fatal("Graphviz (dot) is required to build with --docs")
232
233     if Options.options.debug:
234         if conf.env['MSVC_COMPILER']:
235             conf.env['CFLAGS']    = ['/Od', '/Zi', '/MTd']
236             conf.env['CXXFLAGS']  = ['/Od', '/Zi', '/MTd']
237             conf.env['LINKFLAGS'] = ['/DEBUG']
238         else:
239             conf.env['CFLAGS']   = ['-O0', '-g']
240             conf.env['CXXFLAGS'] = ['-O0',  '-g']
241     else:
242         if conf.env['MSVC_COMPILER']:
243             conf.env['CFLAGS']    = ['/MD']
244             conf.env['CXXFLAGS']  = ['/MD']
245         append_cxx_flags(['-DNDEBUG'])
246
247     if Options.options.ultra_strict:
248         Options.options.strict = True
249         conf.env.append_value('CFLAGS', ['-Wredundant-decls',
250                                          '-Wstrict-prototypes',
251                                          '-Wmissing-prototypes'])
252
253     if Options.options.strict:
254         conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
255         conf.env.append_value('CXXFLAGS', ['-ansi',
256                                            '-Wnon-virtual-dtor',
257                                            '-Woverloaded-virtual'])
258         append_cxx_flags(['-Wall',
259                           '-Wcast-align',
260                           '-Wextra',
261                           '-Wwrite-strings'])
262         if sys.platform != "darwin":
263             # this is really only to be avoid on OLD apple gcc, but not sure how to version check
264             append_cxx_flags(['-fstrict-overflow'])
265
266         if not conf.check_cc(fragment = '''
267 #ifndef __clang__
268 #error
269 #endif
270 int main() { return 0; }''',
271                          features  = 'c',
272                          mandatory = False,
273                          execute   = False,
274                          msg       = 'Checking for clang'):
275             if sys.platform != "darwin":
276                 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
277                 append_cxx_flags(['-Wunsafe-loop-optimizations'])
278                 # this is invalid (still) on Lion apple gcc
279                 append_cxx_flags(['-Wlogical-op'])
280             
281
282     if not conf.env['MSVC_COMPILER']:
283         append_cxx_flags(['-fshow-column'])
284
285     conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
286     conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
287
288     display_msg(conf, "Install prefix", conf.env['PREFIX'])
289     display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
290     display_msg(conf, "Build documentation", str(conf.env['DOCS']))
291     print('')
292
293     g_step = 2
294
295 def set_c99_mode(conf):
296     if conf.env.MSVC_COMPILER:
297         # MSVC has no hope or desire to compile C99, just compile as C++
298         conf.env.append_unique('CFLAGS', ['-TP'])
299     else:
300         conf.env.append_unique('CFLAGS', ['-std=c99'])
301
302 def set_local_lib(conf, name, has_objects):
303     var_name = 'HAVE_' + nameify(name.upper())
304     define(conf, var_name, 1)
305     if has_objects:
306         if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
307             conf.env['AUTOWAF_LOCAL_LIBS'] = {}
308         conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
309     else:
310         if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
311             conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
312         conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
313
314 def append_property(obj, key, val):
315     if hasattr(obj, key):
316         setattr(obj, key, getattr(obj, key) + val)
317     else:
318         setattr(obj, key, val)
319
320 def use_lib(bld, obj, libs):
321     abssrcdir = os.path.abspath('.')
322     libs_list = libs.split()
323     for l in libs_list:
324         in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
325         in_libs    = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
326         if in_libs:
327             append_property(obj, 'use', ' lib%s ' % l.lower())
328             append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
329         if in_headers or in_libs:
330             inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
331             for f in ['CFLAGS', 'CXXFLAGS']:
332                 if not inc_flag in bld.env[f]:
333                     bld.env.prepend_value(f, inc_flag)
334         else:
335             append_property(obj, 'uselib', ' ' + l)
336
337 @feature('c', 'cxx')
338 @before('apply_link')
339 def version_lib(self):
340     if sys.platform == 'win32':
341         self.vnum = None  # Prevent waf from automatically appending -0
342     if self.env['PARDEBUG']:
343         applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
344         if [x for x in applicable if x in self.features]:
345             self.target = self.target + 'D'
346
347 def set_lib_env(conf, name, version):
348     'Set up environment for local library as if found via pkg-config.'
349     NAME         = name.upper()
350     major_ver    = version.split('.')[0]
351     pkg_var_name = 'PKG_' + name.replace('-', '_')
352     lib_name     = '%s-%s' % (name, major_ver)
353     if conf.env.PARDEBUG:
354         lib_name += 'D'
355     conf.env[pkg_var_name]       = lib_name
356     conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
357     conf.env['LIBPATH_' + NAME]  = [conf.env.LIBDIR]
358     conf.env['LIB_' + NAME]      = [lib_name]
359
360 def display_header(title):
361     Logs.pprint('BOLD', title)
362
363 def display_msg(conf, msg, status = None, color = None):
364     color = 'CYAN'
365     if type(status) == bool and status or status == "True":
366         color = 'GREEN'
367     elif type(status) == bool and not status or status == "False":
368         color = 'YELLOW'
369     Logs.pprint('BOLD', " *", sep='')
370     Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
371     Logs.pprint('BOLD', ":", sep='')
372     Logs.pprint(color, status)
373
374 def link_flags(env, lib):
375     return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
376
377 def compile_flags(env, lib):
378     return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
379
380 def set_recursive():
381     global g_is_child
382     g_is_child = True
383
384 def is_child():
385     global g_is_child
386     return g_is_child
387
388 # Pkg-config file
389 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
390     '''Build a pkg-config file for a library.
391     name           -- uppercase variable name     (e.g. 'SOMENAME')
392     version        -- version string              (e.g. '1.2.3')
393     version_suffix -- name version suffix         (e.g. '2')
394     libs           -- string/list of dependencies (e.g. 'LIBFOO GLIB')
395     '''
396     pkg_prefix       = bld.env['PREFIX']
397     if pkg_prefix[-1] == '/':
398         pkg_prefix = pkg_prefix[:-1]
399
400     target = name.lower()
401     if version_suffix != '':
402         target += '-' + version_suffix
403
404     if bld.env['PARDEBUG']:
405         target += 'D'
406
407     target += '.pc'
408
409     libdir = bld.env['LIBDIR']
410     if libdir.startswith(pkg_prefix):
411         libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
412
413     includedir = bld.env['INCLUDEDIR']
414     if includedir.startswith(pkg_prefix):
415         includedir = includedir.replace(pkg_prefix, '${prefix}')
416
417     obj = bld(features     = 'subst',
418               source       = '%s.pc.in' % name.lower(),
419               target       = target,
420               install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
421               exec_prefix  = '${prefix}',
422               PREFIX       = pkg_prefix,
423               EXEC_PREFIX  = '${prefix}',
424               LIBDIR       = libdir,
425               INCLUDEDIR   = includedir)
426
427     if type(libs) != list:
428         libs = libs.split()
429
430     subst_dict[name + '_VERSION'] = version
431     subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
432     for i in libs:
433         subst_dict[i + '_LIBS']   = link_flags(bld.env, i)
434         lib_cflags = compile_flags(bld.env, i)
435         if lib_cflags == '':
436             lib_cflags = ' '
437         subst_dict[i + '_CFLAGS'] = lib_cflags
438
439     obj.__dict__.update(subst_dict)
440
441 def build_dir(name, subdir):
442     if is_child():
443         return os.path.join('build', name, subdir)
444     else:
445         return os.path.join('build', subdir)
446
447 # Clean up messy Doxygen documentation after it is built
448 def make_simple_dox(name):
449     name = name.lower()
450     NAME = name.upper()
451     try:
452         top = os.getcwd()
453         os.chdir(build_dir(name, 'doc/html'))
454         page = 'group__%s.html' % name
455         if not os.path.exists(page):
456             return
457         for i in [
458             ['%s_API ' % NAME, ''],
459             ['%s_DEPRECATED ' % NAME, ''],
460             ['group__%s.html' % name, ''],
461             ['&#160;', ''],
462             ['<script.*><\/script>', ''],
463             ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
464             ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
465              ''],
466             ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
467              'Doxygen']]:
468             os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
469         os.rename('group__%s.html' % name, 'index.html')
470         for i in (glob.glob('*.png') +
471                   glob.glob('*.html') +
472                   glob.glob('*.js') +
473                   glob.glob('*.css')):
474             if i != 'index.html' and i != 'style.css':
475                 os.remove(i)
476         os.chdir(top)
477         os.chdir(build_dir(name, 'doc/man/man3'))
478         for i in glob.glob('*.3'):
479             os.system("sed -i 's/%s_API //' %s" % (NAME, i))
480         for i in glob.glob('_*'):
481             os.remove(i)
482         os.chdir(top)
483     except Exception as e:
484         Logs.error("Failed to fix up %s documentation: %s" % (name, e))
485
486 # Doxygen API documentation
487 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
488     if not bld.env['DOCS']:
489         return
490
491     if is_child():
492         src_dir = os.path.join(srcdir, name.lower())
493         doc_dir = os.path.join(blddir, name.lower(), 'doc')
494     else:
495         src_dir = srcdir
496         doc_dir = os.path.join(blddir, 'doc')
497
498     subst_tg = bld(features     = 'subst',
499                    source       = 'doc/reference.doxygen.in',
500                    target       = 'doc/reference.doxygen',
501                    install_path = '',
502                    name         = 'doxyfile')
503
504     subst_dict = {
505         name + '_VERSION' : version,
506         name + '_SRCDIR'  : os.path.abspath(src_dir),
507         name + '_DOC_DIR' : os.path.abspath(doc_dir)
508         }
509
510     subst_tg.__dict__.update(subst_dict)
511
512     subst_tg.post()
513
514     docs = bld(features = 'doxygen',
515                doxyfile = 'doc/reference.doxygen')
516
517     docs.post()
518
519     major = int(version[0:version.find('.')])
520     bld.install_files(
521         os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
522         bld.path.get_bld().ant_glob('doc/html/*'))
523     for i in range(1, 8):
524         bld.install_files('${MANDIR}/man%d' % i,
525                           bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
526                                                       excl='**/_*'))
527
528 # Version code file generation
529 def build_version_files(header_path, source_path, domain, major, minor, micro):
530     header_path = os.path.abspath(header_path)
531     source_path = os.path.abspath(source_path)
532     text  = "int " + domain + "_major_version = " + str(major) + ";\n"
533     text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
534     text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
535     try:
536         o = open(source_path, 'w')
537         o.write(text)
538         o.close()
539     except IOError:
540         Logs.error('Failed to open %s for writing\n' % source_path)
541         sys.exit(-1)
542
543     text  = "#ifndef __" + domain + "_version_h__\n"
544     text += "#define __" + domain + "_version_h__\n"
545     text += " extern const char* " + domain + "_revision;\n"
546     text += " extern int " + domain + "_major_version;\n"
547     text += " extern int " + domain + "_minor_version;\n"
548     text += " extern int " + domain + "_micro_version;\n"
549     text += "#endif /* __" + domain + "_version_h__ */\n"
550     try:
551         o = open(header_path, 'w')
552         o.write(text)
553         o.close()
554     except IOError:
555         Logs.warn('Failed to open %s for writing\n' % header_path)
556         sys.exit(-1)
557
558     return None
559
560 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
561     Logs.info('Generating pot file from %s' % name)
562     pot_file = '%s.pot' % name
563
564     cmd = ['xgettext',
565             '--keyword=_',
566             '--keyword=N_',
567             '--keyword=S_',
568             '--keyword=P_:1,2',
569             '--from-code=UTF-8',
570             '-o', pot_file]
571
572     if copyright_holder:
573         cmd += ['--copyright-holder="%s"' % copyright_holder]
574
575     cmd += sources
576     Logs.info('Updating ' + pot_file)
577     subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
578
579 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
580     pwd = os.getcwd()
581     os.chdir(os.path.join(srcdir, dir))
582     pot_file = '%s.pot' % name
583     po_files = glob.glob('po/*.po')
584     for po_file in po_files:
585         cmd = ['msgmerge',
586                '--update',
587                '--no-fuzzy-matching',
588                po_file,
589                pot_file]
590         Logs.info('Updating ' + po_file)
591         subprocess.call(cmd)
592     os.chdir(pwd)
593
594 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
595     pwd = os.getcwd()
596     os.chdir(os.path.join(srcdir, dir))
597     pot_file = '%s.pot' % name
598     po_files = glob.glob('po/*.po')
599     for po_file in po_files:
600         mo_file = po_file.replace('.po', '.mo')
601         cmd = ['msgfmt',
602                '-c',
603                '-f',
604                '-o',
605                mo_file,
606                po_file]
607         Logs.info('Generating ' + po_file)
608         subprocess.call(cmd)
609     os.chdir(pwd)
610
611 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
612     build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
613     build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
614     build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
615
616 def cd_to_build_dir(ctx, appname):
617     orig_dir  = os.path.abspath(os.curdir)
618     top_level = (len(ctx.stack_path) > 1)
619     if top_level:
620         os.chdir(os.path.join('build', appname))
621     else:
622         os.chdir('build')
623     Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
624
625 def cd_to_orig_dir(ctx, child):
626     if child:
627         os.chdir(os.path.join('..', '..'))
628     else:
629         os.chdir('..')
630
631 def pre_test(ctx, appname, dirs=['src']):
632     diropts  = ''
633     for i in dirs:
634         diropts += ' -d ' + i
635     cd_to_build_dir(ctx, appname)
636     clear_log = open('lcov-clear.log', 'w')
637     try:
638         try:
639             # Clear coverage data
640             subprocess.call(('lcov %s -z' % diropts).split(),
641                             stdout=clear_log, stderr=clear_log)
642         except:
643             Logs.warn('Failed to run lcov, no coverage report will be generated')
644     finally:
645         clear_log.close()
646
647 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
648     diropts  = ''
649     for i in dirs:
650         diropts += ' -d ' + i
651     coverage_log           = open('lcov-coverage.log', 'w')
652     coverage_lcov          = open('coverage.lcov', 'w')
653     coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
654     try:
655         try:
656             base = '.'
657             if g_is_child:
658                 base = '..'
659
660             # Generate coverage data
661             subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
662                             stdout=coverage_lcov, stderr=coverage_log)
663     
664             # Strip unwanted stuff
665             subprocess.call(
666                 ['lcov', '--remove', 'coverage.lcov'] + remove,
667                 stdout=coverage_stripped_lcov, stderr=coverage_log)
668     
669             # Generate HTML coverage output
670             if not os.path.isdir('coverage'):
671                 os.makedirs('coverage')
672             subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
673                             stdout=coverage_log, stderr=coverage_log)
674     
675         except:
676             Logs.warn('Failed to run lcov, no coverage report will be generated')
677     finally:
678         coverage_stripped_lcov.close()
679         coverage_lcov.close()
680         coverage_log.close()
681
682         print('')
683         Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
684         top_level = (len(ctx.stack_path) > 1)
685         if top_level:
686             cd_to_orig_dir(ctx, top_level)
687
688     print('')
689     Logs.pprint('BOLD', 'Coverage:', sep='')
690     print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
691
692 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
693     failures = 0
694     diropts  = ''
695     for i in dirs:
696         diropts += ' -d ' + i
697
698     # Run all tests
699     for i in tests:
700         s = i
701         if type(i) == type([]):
702             s = ' '.join(i)
703         print('')
704         Logs.pprint('BOLD', '** Test', sep='')
705         Logs.pprint('NORMAL', '%s' % s)
706         cmd = i
707         if Options.options.grind:
708             cmd = 'valgrind ' + i
709         if subprocess.call(cmd, shell=True) == desired_status:
710             Logs.pprint('GREEN', '** Pass')
711         else:
712             failures += 1
713             Logs.pprint('RED', '** FAIL')
714
715     print('')
716     if failures == 0:
717         Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
718     else:
719         Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
720
721 def run_ldconfig(ctx):
722     if (ctx.cmd == 'install'
723         and not ctx.env['RAN_LDCONFIG']
724         and ctx.env['LIBDIR']
725         and not 'DESTDIR' in os.environ
726         and not Options.options.destdir):
727         try:
728             Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
729             subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
730             ctx.env['RAN_LDCONFIG'] = True
731         except:
732             pass
733
734 def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
735     import rdflib
736     import textwrap
737     from time import strftime, strptime
738
739     doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
740     dcs  = rdflib.Namespace('http://ontologi.es/doap-changeset#')
741     rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
742     foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
743     rdf  = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
744     m    = rdflib.ConjunctiveGraph()
745
746     try:
747         for i in in_files:
748             m.parse(i, format='n3')
749     except:
750         Logs.warn('Error parsing data, unable to generate NEWS')
751         return
752
753     proj = m.value(None, rdf.type, doap.Project)
754     for f in m.triples([proj, rdfs.seeAlso, None]):
755         if f[2].endswith('.ttl'):
756             m.parse(f[2], format='n3')
757
758     entries = {}
759     for r in m.triples([proj, doap.release, None]):
760         release   = r[2]
761         revision  = m.value(release, doap.revision, None)
762         date      = m.value(release, doap.created, None)
763         blamee    = m.value(release, dcs.blame, None)
764         changeset = m.value(release, dcs.changeset, None)
765         dist      = m.value(release, doap['file-release'], None)
766
767         if revision and date and blamee and changeset:
768             entry = '%s (%s) stable;\n' % (name, revision)
769
770             for i in m.triples([changeset, dcs.item, None]):
771                 item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
772                 entry += '\n  * ' + '\n    '.join(item)
773                 if dist and top_entries is not None:
774                     if not str(dist) in top_entries:
775                         top_entries[str(dist)] = []
776                     top_entries[str(dist)] += [
777                         '%s: %s' % (name, '\n    '.join(item))]
778
779             if extra_entries:
780                 for i in extra_entries[str(dist)]:
781                     entry += '\n  * ' + i
782
783             entry += '\n\n --'
784
785             blamee_name = m.value(blamee, foaf.name, None)
786             blamee_mbox = m.value(blamee, foaf.mbox, None)
787             if blamee_name and blamee_mbox:
788                 entry += ' %s <%s>' % (blamee_name,
789                                        blamee_mbox.replace('mailto:', ''))
790                 
791             entry += '  %s\n\n' % (
792                 strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
793
794             entries[revision] = entry
795         else:
796             Logs.warn('Ignored incomplete %s release description' % name)
797
798     if len(entries) > 0:
799         news = open(out_file, 'w')
800         for e in sorted(entries.keys(), reverse=True):
801             news.write(entries[e])
802         news.close()