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