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, exportname, visheader):
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     if visheader != '':
546         text += "#include \"" + visheader + "\"\n"
547     text += exportname + " extern const char* " + domain + "_revision;\n"
548     text += exportname + " extern int " + domain + "_major_version;\n"
549     text += exportname + " extern int " + domain + "_minor_version;\n"
550     text += exportname + " extern int " + domain + "_micro_version;\n"
551     text += "#endif /* __" + domain + "_version_h__ */\n"
552     try:
553         o = open(header_path, 'w')
554         o.write(text)
555         o.close()
556     except IOError:
557         Logs.warn('Failed to open %s for writing\n' % header_path)
558         sys.exit(-1)
559
560     return None
561
562 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
563     Logs.info('Generating pot file from %s' % name)
564     pot_file = '%s.pot' % name
565
566     cmd = ['xgettext',
567             '--keyword=_',
568             '--keyword=N_',
569             '--keyword=S_',
570             '--keyword=P_:1,2',
571             '--from-code=UTF-8',
572             '-o', pot_file]
573
574     if copyright_holder:
575         cmd += ['--copyright-holder="%s"' % copyright_holder]
576
577     cmd += sources
578     Logs.info('Updating ' + pot_file)
579     subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
580
581 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
582     pwd = os.getcwd()
583     os.chdir(os.path.join(srcdir, dir))
584     pot_file = '%s.pot' % name
585     po_files = glob.glob('po/*.po')
586     for po_file in po_files:
587         cmd = ['msgmerge',
588                '--update',
589                '--no-fuzzy-matching',
590                po_file,
591                pot_file]
592         Logs.info('Updating ' + po_file)
593         subprocess.call(cmd)
594     os.chdir(pwd)
595
596 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
597     pwd = os.getcwd()
598     os.chdir(os.path.join(srcdir, dir))
599     pot_file = '%s.pot' % name
600     po_files = glob.glob('po/*.po')
601     for po_file in po_files:
602         mo_file = po_file.replace('.po', '.mo')
603         cmd = ['msgfmt',
604                '-c',
605                '-f',
606                '-o',
607                mo_file,
608                po_file]
609         Logs.info('Generating ' + po_file)
610         subprocess.call(cmd)
611     os.chdir(pwd)
612
613 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
614     build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
615     build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
616     build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
617
618 def cd_to_build_dir(ctx, appname):
619     orig_dir  = os.path.abspath(os.curdir)
620     top_level = (len(ctx.stack_path) > 1)
621     if top_level:
622         os.chdir(os.path.join('build', appname))
623     else:
624         os.chdir('build')
625     Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
626
627 def cd_to_orig_dir(ctx, child):
628     if child:
629         os.chdir(os.path.join('..', '..'))
630     else:
631         os.chdir('..')
632
633 def pre_test(ctx, appname, dirs=['src']):
634     diropts  = ''
635     for i in dirs:
636         diropts += ' -d ' + i
637     cd_to_build_dir(ctx, appname)
638     clear_log = open('lcov-clear.log', 'w')
639     try:
640         try:
641             # Clear coverage data
642             subprocess.call(('lcov %s -z' % diropts).split(),
643                             stdout=clear_log, stderr=clear_log)
644         except:
645             Logs.warn('Failed to run lcov, no coverage report will be generated')
646     finally:
647         clear_log.close()
648
649 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
650     diropts  = ''
651     for i in dirs:
652         diropts += ' -d ' + i
653     coverage_log           = open('lcov-coverage.log', 'w')
654     coverage_lcov          = open('coverage.lcov', 'w')
655     coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
656     try:
657         try:
658             base = '.'
659             if g_is_child:
660                 base = '..'
661
662             # Generate coverage data
663             subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
664                             stdout=coverage_lcov, stderr=coverage_log)
665     
666             # Strip unwanted stuff
667             subprocess.call(
668                 ['lcov', '--remove', 'coverage.lcov'] + remove,
669                 stdout=coverage_stripped_lcov, stderr=coverage_log)
670     
671             # Generate HTML coverage output
672             if not os.path.isdir('coverage'):
673                 os.makedirs('coverage')
674             subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
675                             stdout=coverage_log, stderr=coverage_log)
676     
677         except:
678             Logs.warn('Failed to run lcov, no coverage report will be generated')
679     finally:
680         coverage_stripped_lcov.close()
681         coverage_lcov.close()
682         coverage_log.close()
683
684         print('')
685         Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
686         top_level = (len(ctx.stack_path) > 1)
687         if top_level:
688             cd_to_orig_dir(ctx, top_level)
689
690     print('')
691     Logs.pprint('BOLD', 'Coverage:', sep='')
692     print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
693
694 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
695     failures = 0
696     diropts  = ''
697     for i in dirs:
698         diropts += ' -d ' + i
699
700     # Run all tests
701     for i in tests:
702         s = i
703         if type(i) == type([]):
704             s = ' '.join(i)
705         print('')
706         Logs.pprint('BOLD', '** Test', sep='')
707         Logs.pprint('NORMAL', '%s' % s)
708         cmd = i
709         if Options.options.grind:
710             cmd = 'valgrind ' + i
711         if subprocess.call(cmd, shell=True) == desired_status:
712             Logs.pprint('GREEN', '** Pass')
713         else:
714             failures += 1
715             Logs.pprint('RED', '** FAIL')
716
717     print('')
718     if failures == 0:
719         Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
720     else:
721         Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
722
723 def run_ldconfig(ctx):
724     if (ctx.cmd == 'install'
725         and not ctx.env['RAN_LDCONFIG']
726         and ctx.env['LIBDIR']
727         and not 'DESTDIR' in os.environ
728         and not Options.options.destdir):
729         try:
730             Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
731             subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
732             ctx.env['RAN_LDCONFIG'] = True
733         except:
734             pass
735
736 def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
737     import rdflib
738     import textwrap
739     from time import strftime, strptime
740
741     doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
742     dcs  = rdflib.Namespace('http://ontologi.es/doap-changeset#')
743     rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
744     foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
745     rdf  = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
746     m    = rdflib.ConjunctiveGraph()
747
748     try:
749         for i in in_files:
750             m.parse(i, format='n3')
751     except:
752         Logs.warn('Error parsing data, unable to generate NEWS')
753         return
754
755     proj = m.value(None, rdf.type, doap.Project)
756     for f in m.triples([proj, rdfs.seeAlso, None]):
757         if f[2].endswith('.ttl'):
758             m.parse(f[2], format='n3')
759
760     entries = {}
761     for r in m.triples([proj, doap.release, None]):
762         release   = r[2]
763         revision  = m.value(release, doap.revision, None)
764         date      = m.value(release, doap.created, None)
765         blamee    = m.value(release, dcs.blame, None)
766         changeset = m.value(release, dcs.changeset, None)
767         dist      = m.value(release, doap['file-release'], None)
768
769         if revision and date and blamee and changeset:
770             entry = '%s (%s) stable;\n' % (name, revision)
771
772             for i in m.triples([changeset, dcs.item, None]):
773                 item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
774                 entry += '\n  * ' + '\n    '.join(item)
775                 if dist and top_entries is not None:
776                     if not str(dist) in top_entries:
777                         top_entries[str(dist)] = []
778                     top_entries[str(dist)] += [
779                         '%s: %s' % (name, '\n    '.join(item))]
780
781             if extra_entries:
782                 for i in extra_entries[str(dist)]:
783                     entry += '\n  * ' + i
784
785             entry += '\n\n --'
786
787             blamee_name = m.value(blamee, foaf.name, None)
788             blamee_mbox = m.value(blamee, foaf.mbox, None)
789             if blamee_name and blamee_mbox:
790                 entry += ' %s <%s>' % (blamee_name,
791                                        blamee_mbox.replace('mailto:', ''))
792                 
793             entry += '  %s\n\n' % (
794                 strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
795
796             entries[revision] = entry
797         else:
798             Logs.warn('Ignored incomplete %s release description' % name)
799
800     if len(entries) > 0:
801         news = open(out_file, 'w')
802         for e in sorted(entries.keys(), reverse=True):
803             news.write(entries[e])
804         news.close()