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