replace ::cast_dynamic() with relevant ActionManager::get_*_action() calls
[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 import shutil
14
15 from waflib import Configure, Context, Logs, Node, Options, Task, Utils
16 from waflib.TaskGen import feature, before, after
17
18 global g_is_child
19 g_is_child = False
20
21 # Only run autowaf hooks once (even if sub projects call several times)
22 global g_step
23 g_step = 0
24
25 # Compute dependencies globally
26 #import preproc
27 #preproc.go_absolute = True
28
29 @feature('c', 'cxx')
30 @after('apply_incpaths')
31 def include_config_h(self):
32     self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
33
34 def set_options(opt, debug_by_default=False):
35     "Add standard autowaf options if they havn't been added yet"
36     global g_step
37     if g_step > 0:
38         return
39
40     # Install directory options
41     dirs_options = opt.add_option_group('Installation directories', '')
42
43     # Move --prefix and --destdir to directory options group
44     for k in ('--prefix', '--destdir'):
45         option = opt.parser.get_option(k)
46         if option:
47             opt.parser.remove_option(k)
48             dirs_options.add_option(option)
49
50     # Standard directory options
51     dirs_options.add_option('--bindir', type='string',
52                             help="Executable programs [Default: PREFIX/bin]")
53     dirs_options.add_option('--configdir', type='string',
54                             help="Configuration data [Default: PREFIX/etc]")
55     dirs_options.add_option('--datadir', type='string',
56                             help="Shared data [Default: PREFIX/share]")
57     dirs_options.add_option('--includedir', type='string',
58                             help="Header files [Default: PREFIX/include]")
59     dirs_options.add_option('--libdir', type='string',
60                             help="Libraries [Default: PREFIX/lib]")
61     dirs_options.add_option('--mandir', type='string',
62                             help="Manual pages [Default: DATADIR/man]")
63     dirs_options.add_option('--docdir', type='string',
64                             help="HTML documentation [Default: DATADIR/doc]")
65
66     # Build options
67     if debug_by_default:
68         opt.add_option('--optimize', action='store_false', default=True, dest='debug',
69                        help="Build optimized binaries")
70     else:
71         opt.add_option('--debug', action='store_true', default=False, dest='debug',
72                        help="Build debuggable binaries")
73
74     opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
75                        help="Build parallel-installable debuggable libraries with D suffix")
76
77     opt.add_option('--grind', action='store_true', default=False, dest='grind',
78                    help="Run tests in valgrind")
79     opt.add_option('--strict', action='store_true', default=False, dest='strict',
80                    help="Use strict compiler flags and show all warnings")
81     opt.add_option('--ultra-strict', action='store_true', default=False, dest='ultra_strict',
82                    help="Use even stricter compiler flags (likely to trigger many warnings in library headers)")
83     opt.add_option('--docs', action='store_true', default=False, dest='docs',
84                    help="Build documentation - requires doxygen")
85
86     g_step = 1
87
88 def copyfile (task):
89     # a cross-platform utility for copying files as part of tasks
90     src = task.inputs[0].abspath()
91     tgt = task.outputs[0].abspath()
92     shutil.copy2 (src, tgt)
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 ensure_visible_symbols(bld, visible):
169     if bld.env['MSVC_COMPILER']:
170         if visible:
171             print ('*** WARNING: MSVC does not allow symbols to be visible/exported by default while building ' + bld.name)
172         else:
173             pass
174     else:
175         if not hasattr (bld,'cxxflags'):
176             bld.cxxflags = []
177         if not hasattr (bld,'cflags'):
178             bld.cflags = []
179         if visible:
180             bld.cxxflags += [ '-fvisibility=default' ]
181             bld.cflags += [ '-fvisibility=default' ]
182         else:
183             bld.cxxflags += [ '-fvisibility=hidden' ]
184             bld.cflags += [ '-fvisibility=hidden' ]
185
186 def set_basic_compiler_flags(conf, flag_dict):
187     if Options.options.debug:
188         conf.env.append_value('CFLAGS', flag_dict['debuggable'])
189         conf.env.append_value('CXXFLAGS', flag_dict['debuggable'])
190         conf.env.append_value('LINKFLAGS', flag_dict['linker-debuggable'])
191     else:
192         conf.env.append_value('CFLAGS', flag_dict['nondebuggable'])
193         conf.env.append_value('CXXFLAGS', flag_dict['nondebuggable'])
194
195     if Options.options.ultra_strict:
196         Options.options.strict = True
197         conf.env.append_value('CFLAGS', flag_dict['ultra-strict'])
198
199     if Options.options.strict:
200         conf.env.append_value('CFLAGS', flag_dict['c-strict'])
201         conf.env.append_value('CXXFLAGS', flag_dict['cxx-strict'])
202         conf.env.append_value('CFLAGS', flag_dict['strict'])
203         conf.env.append_value('CXXFLAGS', flag_dict['strict'])
204
205     conf.env.append_value('CFLAGS', flag_dict['show-column'])
206     conf.env.append_value('CXXFLAGS', flag_dict['show-column'])
207
208 def configure(conf):
209     global g_step
210     if g_step > 1:
211         return
212     print('')
213     display_header('Global Configuration')
214
215     if Options.options.docs:
216         conf.load('doxygen')
217
218     conf.env['DOCS'] = Options.options.docs
219     conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
220     conf.env['PARDEBUG'] = Options.options.pardebug
221     conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
222
223     def config_dir(var, opt, default):
224         if opt:
225             conf.env[var] = normpath(opt)
226         else:
227             conf.env[var] = normpath(default)
228
229     opts   = Options.options
230     prefix = conf.env['PREFIX']
231
232     config_dir('BINDIR',     opts.bindir,     os.path.join(prefix, 'bin'))
233     config_dir('SYSCONFDIR', opts.configdir,  os.path.join(prefix, 'etc'))
234     config_dir('DATADIR',    opts.datadir,    os.path.join(prefix, 'share'))
235     config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
236     config_dir('LIBDIR',     opts.libdir,     os.path.join(prefix, 'lib'))
237     config_dir('MANDIR',     opts.mandir,     os.path.join(conf.env['DATADIR'], 'man'))
238     config_dir('DOCDIR',     opts.docdir,     os.path.join(conf.env['DATADIR'], 'doc'))
239
240     if Options.options.docs:
241         doxygen = conf.find_program('doxygen')
242         if not doxygen:
243             conf.fatal("Doxygen is required to build with --docs")
244
245         dot = conf.find_program('dot')
246         if not dot:
247             conf.fatal("Graphviz (dot) is required to build with --docs")
248
249     conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
250     conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
251
252     display_msg(conf, "Install prefix", conf.env['PREFIX'])
253     display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
254     display_msg(conf, "Build documentation", str(conf.env['DOCS']))
255     print('')
256
257     g_step = 2
258
259 def set_local_lib(conf, name, has_objects):
260     var_name = 'HAVE_' + nameify(name.upper())
261     define(conf, var_name, 1)
262     if has_objects:
263         if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
264             conf.env['AUTOWAF_LOCAL_LIBS'] = {}
265         conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
266     else:
267         if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
268             conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
269         conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
270
271 def append_property(obj, key, val):
272     if hasattr(obj, key):
273         setattr(obj, key, getattr(obj, key) + val)
274     else:
275         setattr(obj, key, val)
276
277 def use_lib(bld, obj, libs):
278     abssrcdir = os.path.abspath('.')
279     libs_list = libs.split()
280     for l in libs_list:
281         in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
282         in_libs    = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
283         if in_libs:
284             append_property(obj, 'use', ' lib%s ' % l.lower())
285             append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
286         if in_headers or in_libs:
287             inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
288             for f in ['CFLAGS', 'CXXFLAGS']:
289                 if not inc_flag in bld.env[f]:
290                     bld.env.prepend_value(f, inc_flag)
291         else:
292             append_property(obj, 'uselib', ' ' + l)
293
294 @feature('c', 'cxx')
295 @before('apply_link')
296 def version_lib(self):
297     if sys.platform == 'win32':
298         self.vnum = None  # Prevent waf from automatically appending -0
299     if self.env['PARDEBUG']:
300         applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
301         if [x for x in applicable if x in self.features]:
302             self.target = self.target + 'D'
303
304 def set_lib_env(conf, name, version):
305     'Set up environment for local library as if found via pkg-config.'
306     NAME         = name.upper()
307     major_ver    = version.split('.')[0]
308     pkg_var_name = 'PKG_' + name.replace('-', '_')
309     lib_name     = '%s-%s' % (name, major_ver)
310     if conf.env.PARDEBUG:
311         lib_name += 'D'
312     conf.env[pkg_var_name]       = lib_name
313     conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
314     conf.env['LIBPATH_' + NAME]  = [conf.env.LIBDIR]
315     conf.env['LIB_' + NAME]      = [lib_name]
316
317 def display_header(title):
318     Logs.pprint('BOLD', title)
319
320 def display_msg(conf, msg, status = None, color = None):
321     color = 'CYAN'
322     if type(status) == bool and status or status == "True":
323         color = 'GREEN'
324     elif type(status) == bool and not status or status == "False":
325         color = 'YELLOW'
326     Logs.pprint('BOLD', " *", sep='')
327     Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
328     Logs.pprint('BOLD', ":", sep='')
329     Logs.pprint(color, status)
330
331 def link_flags(env, lib):
332     return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
333
334 def compile_flags(env, lib):
335     return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
336
337 def set_recursive():
338     global g_is_child
339     g_is_child = True
340
341 def is_child():
342     global g_is_child
343     return g_is_child
344
345 # Pkg-config file
346 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
347     '''Build a pkg-config file for a library.
348     name           -- uppercase variable name     (e.g. 'SOMENAME')
349     version        -- version string              (e.g. '1.2.3')
350     version_suffix -- name version suffix         (e.g. '2')
351     libs           -- string/list of dependencies (e.g. 'LIBFOO GLIB')
352     '''
353     pkg_prefix       = bld.env['PREFIX']
354     if pkg_prefix[-1] == '/':
355         pkg_prefix = pkg_prefix[:-1]
356
357     target = name.lower()
358     if version_suffix != '':
359         target += '-' + version_suffix
360
361     if bld.env['PARDEBUG']:
362         target += 'D'
363
364     target += '.pc'
365
366     libdir = bld.env['LIBDIR']
367     if libdir.startswith(pkg_prefix):
368         libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
369
370     includedir = bld.env['INCLUDEDIR']
371     if includedir.startswith(pkg_prefix):
372         includedir = includedir.replace(pkg_prefix, '${prefix}')
373
374     obj = bld(features     = 'subst',
375               source       = '%s.pc.in' % name.lower(),
376               target       = target,
377               install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
378               exec_prefix  = '${prefix}',
379               PREFIX       = pkg_prefix,
380               EXEC_PREFIX  = '${prefix}',
381               LIBDIR       = libdir,
382               INCLUDEDIR   = includedir)
383
384     if type(libs) != list:
385         libs = libs.split()
386
387     subst_dict[name + '_VERSION'] = version
388     subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
389     for i in libs:
390         subst_dict[i + '_LIBS']   = link_flags(bld.env, i)
391         lib_cflags = compile_flags(bld.env, i)
392         if lib_cflags == '':
393             lib_cflags = ' '
394         subst_dict[i + '_CFLAGS'] = lib_cflags
395
396     obj.__dict__.update(subst_dict)
397
398 def build_dir(name, subdir):
399     if is_child():
400         return os.path.join('build', name, subdir)
401     else:
402         return os.path.join('build', subdir)
403
404 # Clean up messy Doxygen documentation after it is built
405 def make_simple_dox(name):
406     name = name.lower()
407     NAME = name.upper()
408     try:
409         top = os.getcwd()
410         os.chdir(build_dir(name, 'doc/html'))
411         page = 'group__%s.html' % name
412         if not os.path.exists(page):
413             return
414         for i in [
415             ['%s_API ' % NAME, ''],
416             ['%s_DEPRECATED ' % NAME, ''],
417             ['group__%s.html' % name, ''],
418             ['&#160;', ''],
419             ['<script.*><\/script>', ''],
420             ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
421             ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
422              ''],
423             ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
424              'Doxygen']]:
425             os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
426         os.rename('group__%s.html' % name, 'index.html')
427         for i in (glob.glob('*.png') +
428                   glob.glob('*.html') +
429                   glob.glob('*.js') +
430                   glob.glob('*.css')):
431             if i != 'index.html' and i != 'style.css':
432                 os.remove(i)
433         os.chdir(top)
434         os.chdir(build_dir(name, 'doc/man/man3'))
435         for i in glob.glob('*.3'):
436             os.system("sed -i 's/%s_API //' %s" % (NAME, i))
437         for i in glob.glob('_*'):
438             os.remove(i)
439         os.chdir(top)
440     except Exception as e:
441         Logs.error("Failed to fix up %s documentation: %s" % (name, e))
442
443 # Doxygen API documentation
444 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
445     if not bld.env['DOCS']:
446         return
447
448     if is_child():
449         src_dir = os.path.join(srcdir, name.lower())
450         doc_dir = os.path.join(blddir, name.lower(), 'doc')
451     else:
452         src_dir = srcdir
453         doc_dir = os.path.join(blddir, 'doc')
454
455     subst_tg = bld(features     = 'subst',
456                    source       = 'doc/reference.doxygen.in',
457                    target       = 'doc/reference.doxygen',
458                    install_path = '',
459                    name         = 'doxyfile')
460
461     subst_dict = {
462         name + '_VERSION' : version,
463         name + '_SRCDIR'  : os.path.abspath(src_dir),
464         name + '_DOC_DIR' : os.path.abspath(doc_dir)
465         }
466
467     subst_tg.__dict__.update(subst_dict)
468
469     subst_tg.post()
470
471     docs = bld(features = 'doxygen',
472                doxyfile = 'doc/reference.doxygen')
473
474     docs.post()
475
476     major = int(version[0:version.find('.')])
477     bld.install_files(
478         os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
479         bld.path.get_bld().ant_glob('doc/html/*'))
480     for i in range(1, 8):
481         bld.install_files('${MANDIR}/man%d' % i,
482                           bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
483                                                       excl='**/_*'))
484
485 # Version code file generation
486 def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
487     header_path = os.path.abspath(header_path)
488     source_path = os.path.abspath(source_path)
489     text  = "int " + domain + "_major_version = " + str(major) + ";\n"
490     text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
491     text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
492     try:
493         o = open(source_path, 'w')
494         o.write(text)
495         o.close()
496     except IOError:
497         Logs.error('Failed to open %s for writing\n' % source_path)
498         sys.exit(-1)
499
500     text  = "#ifndef __" + domain + "_version_h__\n"
501     text += "#define __" + domain + "_version_h__\n"
502     if visheader != '':
503         text += "#include \"" + visheader + "\"\n"
504     text += exportname + " extern const char* " + domain + "_revision;\n"
505     text += exportname + " extern int " + domain + "_major_version;\n"
506     text += exportname + " extern int " + domain + "_minor_version;\n"
507     text += exportname + " extern int " + domain + "_micro_version;\n"
508     text += "#endif /* __" + domain + "_version_h__ */\n"
509     try:
510         o = open(header_path, 'w')
511         o.write(text)
512         o.close()
513     except IOError:
514         Logs.warn('Failed to open %s for writing\n' % header_path)
515         sys.exit(-1)
516
517     return None
518
519 # Internationalization with gettext
520 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
521     Logs.info('Generating pot file from %s' % name)
522     pot_file = '%s.pot' % name
523
524     cmd = ['xgettext',
525             '--keyword=_',
526             '--keyword=N_',
527             '--keyword=S_',
528             '--keyword=P_:1,2',
529             '--from-code=UTF-8',
530             '-o', pot_file]
531
532     if copyright_holder:
533         cmd += ['--copyright-holder="%s"' % copyright_holder]
534
535     cmd += sources
536     Logs.info('Updating ' + pot_file)
537     subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
538
539 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
540     pwd = os.getcwd()
541     os.chdir(os.path.join(srcdir, dir))
542     pot_file = '%s.pot' % name
543     po_files = glob.glob('po/*.po')
544     for po_file in po_files:
545         cmd = ['msgmerge',
546                '--update',
547                '--no-fuzzy-matching',
548                po_file,
549                pot_file]
550         Logs.info('Updating ' + po_file)
551         subprocess.call(cmd)
552     os.chdir(pwd)
553
554 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
555     pwd = os.getcwd()
556     os.chdir(os.path.join(srcdir, dir))
557     pot_file = '%s.pot' % name
558     po_files = glob.glob('po/*.po')
559     for po_file in po_files:
560         mo_file = po_file.replace('.po', '.mo')
561         cmd = ['msgfmt',
562                '-c',
563                '-f',
564                '-o',
565                mo_file,
566                po_file]
567         Logs.info('Generating ' + po_file)
568         subprocess.call(cmd)
569     os.chdir(pwd)
570
571 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
572     build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
573     build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
574     build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
575
576 def cd_to_build_dir(ctx, appname):
577     orig_dir  = os.path.abspath(os.curdir)
578     top_level = (len(ctx.stack_path) > 1)
579     if top_level:
580         os.chdir(os.path.join('build', appname))
581     else:
582         os.chdir('build')
583     Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
584
585 def cd_to_orig_dir(ctx, child):
586     if child:
587         os.chdir(os.path.join('..', '..'))
588     else:
589         os.chdir('..')
590
591 def pre_test(ctx, appname, dirs=['src']):
592     diropts  = ''
593     for i in dirs:
594         diropts += ' -d ' + i
595     cd_to_build_dir(ctx, appname)
596     clear_log = open('lcov-clear.log', 'w')
597     try:
598         try:
599             # Clear coverage data
600             subprocess.call(('lcov %s -z' % diropts).split(),
601                             stdout=clear_log, stderr=clear_log)
602         except:
603             Logs.warn('Failed to run lcov, no coverage report will be generated')
604     finally:
605         clear_log.close()
606
607 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
608     diropts  = ''
609     for i in dirs:
610         diropts += ' -d ' + i
611     coverage_log           = open('lcov-coverage.log', 'w')
612     coverage_lcov          = open('coverage.lcov', 'w')
613     coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
614     try:
615         try:
616             base = '.'
617             if g_is_child:
618                 base = '..'
619
620             # Generate coverage data
621             subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
622                             stdout=coverage_lcov, stderr=coverage_log)
623
624             # Strip unwanted stuff
625             subprocess.call(
626                 ['lcov', '--remove', 'coverage.lcov'] + remove,
627                 stdout=coverage_stripped_lcov, stderr=coverage_log)
628
629             # Generate HTML coverage output
630             if not os.path.isdir('coverage'):
631                 os.makedirs('coverage')
632             subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
633                             stdout=coverage_log, stderr=coverage_log)
634
635         except:
636             Logs.warn('Failed to run lcov, no coverage report will be generated')
637     finally:
638         coverage_stripped_lcov.close()
639         coverage_lcov.close()
640         coverage_log.close()
641
642         print('')
643         Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
644         top_level = (len(ctx.stack_path) > 1)
645         if top_level:
646             cd_to_orig_dir(ctx, top_level)
647
648     print('')
649     Logs.pprint('BOLD', 'Coverage:', sep='')
650     print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
651
652 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
653     failures = 0
654     diropts  = ''
655     for i in dirs:
656         diropts += ' -d ' + i
657
658     # Run all tests
659     for i in tests:
660         s = i
661         if type(i) == type([]):
662             s = ' '.join(i)
663         print('')
664         Logs.pprint('BOLD', '** Test', sep='')
665         Logs.pprint('NORMAL', '%s' % s)
666         cmd = i
667         if Options.options.grind:
668             cmd = 'valgrind ' + i
669         if subprocess.call(cmd, shell=True) == desired_status:
670             Logs.pprint('GREEN', '** Pass')
671         else:
672             failures += 1
673             Logs.pprint('RED', '** FAIL')
674
675     print('')
676     if failures == 0:
677         Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
678     else:
679         Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))