visibility macros and flush() added to SrcFileSource; merge with master
[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 configure(conf):
187     global g_step
188     if g_step > 1:
189         return
190     def append_cxx_flags(flags):
191         conf.env.append_value('CFLAGS', flags)
192         conf.env.append_value('CXXFLAGS', flags)
193     print('')
194     display_header('Global Configuration')
195
196     if Options.options.docs:
197         conf.load('doxygen')
198
199     conf.env['DOCS'] = Options.options.docs
200     conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
201     conf.env['PARDEBUG'] = Options.options.pardebug
202     conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
203
204     def config_dir(var, opt, default):
205         if opt:
206             conf.env[var] = normpath(opt)
207         else:
208             conf.env[var] = normpath(default)
209
210     opts   = Options.options
211     prefix = conf.env['PREFIX']
212
213     config_dir('BINDIR',     opts.bindir,     os.path.join(prefix, 'bin'))
214     config_dir('SYSCONFDIR', opts.configdir,  os.path.join(prefix, 'etc'))
215     config_dir('DATADIR',    opts.datadir,    os.path.join(prefix, 'share'))
216     config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
217     config_dir('LIBDIR',     opts.libdir,     os.path.join(prefix, 'lib'))
218     config_dir('MANDIR',     opts.mandir,     os.path.join(conf.env['DATADIR'], 'man'))
219     config_dir('DOCDIR',     opts.docdir,     os.path.join(conf.env['DATADIR'], 'doc'))
220
221     if Options.options.docs:
222         doxygen = conf.find_program('doxygen')
223         if not doxygen:
224             conf.fatal("Doxygen is required to build with --docs")
225
226         dot = conf.find_program('dot')
227         if not dot:
228             conf.fatal("Graphviz (dot) is required to build with --docs")
229
230     if Options.options.debug:
231         if conf.env['MSVC_COMPILER']:
232             conf.env['CFLAGS']    = ['/Od', '/Zi', '/MTd']
233             conf.env['CXXFLAGS']  = ['/Od', '/Zi', '/MTd']
234             conf.env['LINKFLAGS'] = ['/DEBUG']
235         else:
236             conf.env['CFLAGS']   = ['-O0', '-g']
237             conf.env['CXXFLAGS'] = ['-O0',  '-g']
238     else:
239         if conf.env['MSVC_COMPILER']:
240             conf.env['CFLAGS']    = ['/MD']
241             conf.env['CXXFLAGS']  = ['/MD']
242         append_cxx_flags(['-DNDEBUG'])
243
244     if Options.options.ultra_strict:
245         Options.options.strict = True
246         conf.env.append_value('CFLAGS', ['-Wredundant-decls',
247                                          '-Wstrict-prototypes',
248                                          '-Wmissing-prototypes'])
249
250     if Options.options.strict:
251         conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
252         conf.env.append_value('CXXFLAGS', ['-ansi',
253                                            '-Wnon-virtual-dtor',
254                                            '-Woverloaded-virtual'])
255         append_cxx_flags(['-Wall',
256                           '-Wcast-align',
257                           '-Wextra',
258                           '-Wwrite-strings'])
259         if sys.platform != "darwin":
260             # this is really only to be avoid on OLD apple gcc, but not sure how to version check
261             append_cxx_flags(['-fstrict-overflow'])
262
263         if not conf.check_cc(fragment = '''
264 #ifndef __clang__
265 #error
266 #endif
267 int main() { return 0; }''',
268                          features  = 'c',
269                          mandatory = False,
270                          execute   = False,
271                          msg       = 'Checking for clang'):
272             if sys.platform != "darwin":
273                 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
274                 append_cxx_flags(['-Wunsafe-loop-optimizations'])
275                 # this is invalid (still) on Lion apple gcc
276                 append_cxx_flags(['-Wlogical-op'])
277             
278
279     if not conf.env['MSVC_COMPILER']:
280         append_cxx_flags(['-fshow-column'])
281
282     conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
283     conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
284
285     display_msg(conf, "Install prefix", conf.env['PREFIX'])
286     display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
287     display_msg(conf, "Build documentation", str(conf.env['DOCS']))
288     print('')
289
290     g_step = 2
291
292 def set_c99_mode(conf):
293     if conf.env.MSVC_COMPILER:
294         # MSVC has no hope or desire to compile C99, just compile as C++
295         conf.env.append_unique('CFLAGS', ['-TP'])
296     else:
297         conf.env.append_unique('CFLAGS', ['-std=c99'])
298
299 def set_local_lib(conf, name, has_objects):
300     var_name = 'HAVE_' + nameify(name.upper())
301     define(conf, var_name, 1)
302     if has_objects:
303         if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
304             conf.env['AUTOWAF_LOCAL_LIBS'] = {}
305         conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
306     else:
307         if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
308             conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
309         conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
310
311 def append_property(obj, key, val):
312     if hasattr(obj, key):
313         setattr(obj, key, getattr(obj, key) + val)
314     else:
315         setattr(obj, key, val)
316
317 def use_lib(bld, obj, libs):
318     abssrcdir = os.path.abspath('.')
319     libs_list = libs.split()
320     for l in libs_list:
321         in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
322         in_libs    = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
323         if in_libs:
324             append_property(obj, 'use', ' lib%s ' % l.lower())
325             append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
326         if in_headers or in_libs:
327             inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
328             for f in ['CFLAGS', 'CXXFLAGS']:
329                 if not inc_flag in bld.env[f]:
330                     bld.env.prepend_value(f, inc_flag)
331         else:
332             append_property(obj, 'uselib', ' ' + l)
333
334 @feature('c', 'cxx')
335 @before('apply_link')
336 def version_lib(self):
337     if sys.platform == 'win32':
338         self.vnum = None  # Prevent waf from automatically appending -0
339     if self.env['PARDEBUG']:
340         applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
341         if [x for x in applicable if x in self.features]:
342             self.target = self.target + 'D'
343
344 def set_lib_env(conf, name, version):
345     'Set up environment for local library as if found via pkg-config.'
346     NAME         = name.upper()
347     major_ver    = version.split('.')[0]
348     pkg_var_name = 'PKG_' + name.replace('-', '_')
349     lib_name     = '%s-%s' % (name, major_ver)
350     if conf.env.PARDEBUG:
351         lib_name += 'D'
352     conf.env[pkg_var_name]       = lib_name
353     conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
354     conf.env['LIBPATH_' + NAME]  = [conf.env.LIBDIR]
355     conf.env['LIB_' + NAME]      = [lib_name]
356
357 def display_header(title):
358     Logs.pprint('BOLD', title)
359
360 def display_msg(conf, msg, status = None, color = None):
361     color = 'CYAN'
362     if type(status) == bool and status or status == "True":
363         color = 'GREEN'
364     elif type(status) == bool and not status or status == "False":
365         color = 'YELLOW'
366     Logs.pprint('BOLD', " *", sep='')
367     Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
368     Logs.pprint('BOLD', ":", sep='')
369     Logs.pprint(color, status)
370
371 def link_flags(env, lib):
372     return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
373
374 def compile_flags(env, lib):
375     return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
376
377 def set_recursive():
378     global g_is_child
379     g_is_child = True
380
381 def is_child():
382     global g_is_child
383     return g_is_child
384
385 # Pkg-config file
386 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
387     '''Build a pkg-config file for a library.
388     name           -- uppercase variable name     (e.g. 'SOMENAME')
389     version        -- version string              (e.g. '1.2.3')
390     version_suffix -- name version suffix         (e.g. '2')
391     libs           -- string/list of dependencies (e.g. 'LIBFOO GLIB')
392     '''
393     pkg_prefix       = bld.env['PREFIX']
394     if pkg_prefix[-1] == '/':
395         pkg_prefix = pkg_prefix[:-1]
396
397     target = name.lower()
398     if version_suffix != '':
399         target += '-' + version_suffix
400
401     if bld.env['PARDEBUG']:
402         target += 'D'
403
404     target += '.pc'
405
406     libdir = bld.env['LIBDIR']
407     if libdir.startswith(pkg_prefix):
408         libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
409
410     includedir = bld.env['INCLUDEDIR']
411     if includedir.startswith(pkg_prefix):
412         includedir = includedir.replace(pkg_prefix, '${prefix}')
413
414     obj = bld(features     = 'subst',
415               source       = '%s.pc.in' % name.lower(),
416               target       = target,
417               install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
418               exec_prefix  = '${prefix}',
419               PREFIX       = pkg_prefix,
420               EXEC_PREFIX  = '${prefix}',
421               LIBDIR       = libdir,
422               INCLUDEDIR   = includedir)
423
424     if type(libs) != list:
425         libs = libs.split()
426
427     subst_dict[name + '_VERSION'] = version
428     subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
429     for i in libs:
430         subst_dict[i + '_LIBS']   = link_flags(bld.env, i)
431         lib_cflags = compile_flags(bld.env, i)
432         if lib_cflags == '':
433             lib_cflags = ' '
434         subst_dict[i + '_CFLAGS'] = lib_cflags
435
436     obj.__dict__.update(subst_dict)
437
438 def build_dir(name, subdir):
439     if is_child():
440         return os.path.join('build', name, subdir)
441     else:
442         return os.path.join('build', subdir)
443
444 # Clean up messy Doxygen documentation after it is built
445 def make_simple_dox(name):
446     name = name.lower()
447     NAME = name.upper()
448     try:
449         top = os.getcwd()
450         os.chdir(build_dir(name, 'doc/html'))
451         page = 'group__%s.html' % name
452         if not os.path.exists(page):
453             return
454         for i in [
455             ['%s_API ' % NAME, ''],
456             ['%s_DEPRECATED ' % NAME, ''],
457             ['group__%s.html' % name, ''],
458             ['&#160;', ''],
459             ['<script.*><\/script>', ''],
460             ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
461             ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
462              ''],
463             ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
464              'Doxygen']]:
465             os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
466         os.rename('group__%s.html' % name, 'index.html')
467         for i in (glob.glob('*.png') +
468                   glob.glob('*.html') +
469                   glob.glob('*.js') +
470                   glob.glob('*.css')):
471             if i != 'index.html' and i != 'style.css':
472                 os.remove(i)
473         os.chdir(top)
474         os.chdir(build_dir(name, 'doc/man/man3'))
475         for i in glob.glob('*.3'):
476             os.system("sed -i 's/%s_API //' %s" % (NAME, i))
477         for i in glob.glob('_*'):
478             os.remove(i)
479         os.chdir(top)
480     except Exception as e:
481         Logs.error("Failed to fix up %s documentation: %s" % (name, e))
482
483 # Doxygen API documentation
484 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
485     if not bld.env['DOCS']:
486         return
487
488     if is_child():
489         src_dir = os.path.join(srcdir, name.lower())
490         doc_dir = os.path.join(blddir, name.lower(), 'doc')
491     else:
492         src_dir = srcdir
493         doc_dir = os.path.join(blddir, 'doc')
494
495     subst_tg = bld(features     = 'subst',
496                    source       = 'doc/reference.doxygen.in',
497                    target       = 'doc/reference.doxygen',
498                    install_path = '',
499                    name         = 'doxyfile')
500
501     subst_dict = {
502         name + '_VERSION' : version,
503         name + '_SRCDIR'  : os.path.abspath(src_dir),
504         name + '_DOC_DIR' : os.path.abspath(doc_dir)
505         }
506
507     subst_tg.__dict__.update(subst_dict)
508
509     subst_tg.post()
510
511     docs = bld(features = 'doxygen',
512                doxyfile = 'doc/reference.doxygen')
513
514     docs.post()
515
516     major = int(version[0:version.find('.')])
517     bld.install_files(
518         os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
519         bld.path.get_bld().ant_glob('doc/html/*'))
520     for i in range(1, 8):
521         bld.install_files('${MANDIR}/man%d' % i,
522                           bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
523                                                       excl='**/_*'))
524
525 # Version code file generation
526 def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
527     header_path = os.path.abspath(header_path)
528     source_path = os.path.abspath(source_path)
529     text  = "int " + domain + "_major_version = " + str(major) + ";\n"
530     text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
531     text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
532     try:
533         o = open(source_path, 'w')
534         o.write(text)
535         o.close()
536     except IOError:
537         Logs.error('Failed to open %s for writing\n' % source_path)
538         sys.exit(-1)
539
540     text  = "#ifndef __" + domain + "_version_h__\n"
541     text += "#define __" + domain + "_version_h__\n"
542     if visheader != '':
543         text += "#include \"" + visheader + "\"\n"
544     text += exportname + " extern const char* " + domain + "_revision;\n"
545     text += exportname + " extern int " + domain + "_major_version;\n"
546     text += exportname + " extern int " + domain + "_minor_version;\n"
547     text += exportname + " extern int " + domain + "_micro_version;\n"
548     text += "#endif /* __" + domain + "_version_h__ */\n"
549     try:
550         o = open(header_path, 'w')
551         o.write(text)
552         o.close()
553     except IOError:
554         Logs.warn('Failed to open %s for writing\n' % header_path)
555         sys.exit(-1)
556
557     return None
558
559 # Internationalization with gettext
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