2 # -*- coding: utf-8 -*-
4 # Autowaf, useful waf utilities with support for recursive projects
5 # Copyright 2008-2011 David Robillard
7 # Licensed under the GNU GPL v2 or later, see COPYING file for details.
15 from waflib import Configure, Context, Logs, Node, Options, Task, Utils
16 from waflib.TaskGen import feature, before, after
21 # Only run autowaf hooks once (even if sub projects call several times)
25 # Compute dependencies globally
27 #preproc.go_absolute = True
30 @after('apply_incpaths')
31 def include_config_h(self):
32 self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
34 def set_options(opt, debug_by_default=False):
35 "Add standard autowaf options if they havn't been added yet"
40 # Install directory options
41 dirs_options = opt.add_option_group('Installation directories', '')
43 # Move --prefix and --destdir to directory options group
44 for k in ('--prefix', '--destdir'):
45 option = opt.parser.get_option(k)
47 opt.parser.remove_option(k)
48 dirs_options.add_option(option)
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]")
68 opt.add_option('--optimize', action='store_false', default=True, dest='debug',
69 help="Build optimized binaries")
71 opt.add_option('--debug', action='store_true', default=False, dest='debug',
72 help="Build debuggable binaries")
74 opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
75 help="Build parallel-installable debuggable libraries with D suffix")
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")
89 # a cross-platform utility for copying files as part of tasks
90 src = task.inputs[0].abspath()
91 tgt = task.outputs[0].abspath()
92 shutil.copy2 (src, tgt)
94 def check_header(conf, lang, name, define='', mandatory=True):
96 includes = '' # search default system include paths
97 if sys.platform == "darwin":
98 includes = '/opt/local/include'
101 check_func = conf.check_cc
103 check_func = conf.check_cxx
105 Logs.error("Unknown header language `%s'" % lang)
109 check_func(header_name=name, includes=includes,
110 define_name=define, mandatory=mandatory)
112 check_func(header_name=name, includes=includes, mandatory=mandatory)
115 return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
117 def define(conf, var_name, value):
118 conf.define(var_name, value)
119 conf.env[var_name] = value
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']:
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']:
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
141 pkg_var_name = 'PKG_' + name.replace('-', '_')
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)
149 args['mandatory'] = True # Unsmash mandatory arg
151 found = conf.check_cfg(package=pkg_name, args="--cflags --libs", **args)
153 conf.env[pkg_var_name] = pkg_name
154 if 'atleast_version' in args:
155 conf.env['VERSION_' + name] = args['atleast_version']
157 conf.env[var_name] = CheckType.MANDATORY
159 conf.env[var_name] = CheckType.OPTIONAL
163 if sys.platform == 'win32':
164 return os.path.normpath(path).replace('\\', '/')
166 return os.path.normpath(path)
168 def ensure_visible_symbols(bld, visible):
169 if bld.env['MSVC_COMPILER']:
171 print '*** WARNING: MSVC does not allow symbols to be visible/exported by default while building ' + bld.name
175 if not hasattr (bld,'cxxflags'):
177 if not hasattr (bld,'cflags'):
180 bld.cxxflags += [ '-fvisibility=default' ]
181 bld.cflags += [ '-fvisibility=default' ]
183 bld.cxxflags += [ '-fvisibility=hidden' ]
184 bld.cflags += [ '-fvisibility=hidden' ]
190 def append_cxx_flags(flags):
191 conf.env.append_value('CFLAGS', flags)
192 conf.env.append_value('CXXFLAGS', flags)
194 display_header('Global Configuration')
196 if Options.options.docs:
199 conf.env['DOCS'] = Options.options.docs
200 conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
201 conf.env['PARDEBUG'] = Options.options.pardebug
202 conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
204 def config_dir(var, opt, default):
206 conf.env[var] = normpath(opt)
208 conf.env[var] = normpath(default)
210 opts = Options.options
211 prefix = conf.env['PREFIX']
213 config_dir('BINDIR', opts.bindir, os.path.join(prefix, 'bin'))
214 config_dir('SYSCONFDIR', opts.configdir, os.path.join(prefix, 'etc'))
215 config_dir('DATADIR', opts.datadir, os.path.join(prefix, 'share'))
216 config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
217 config_dir('LIBDIR', opts.libdir, os.path.join(prefix, 'lib'))
218 config_dir('MANDIR', opts.mandir, os.path.join(conf.env['DATADIR'], 'man'))
219 config_dir('DOCDIR', opts.docdir, os.path.join(conf.env['DATADIR'], 'doc'))
221 if Options.options.docs:
222 doxygen = conf.find_program('doxygen')
224 conf.fatal("Doxygen is required to build with --docs")
226 dot = conf.find_program('dot')
228 conf.fatal("Graphviz (dot) is required to build with --docs")
230 if Options.options.debug:
231 if conf.env['MSVC_COMPILER']:
232 conf.env['CFLAGS'] = ['/Od', '/Zi', '/MTd']
233 conf.env['CXXFLAGS'] = ['/Od', '/Zi', '/MTd']
234 conf.env['LINKFLAGS'] = ['/DEBUG']
236 conf.env['CFLAGS'] = ['-O0', '-g']
237 conf.env['CXXFLAGS'] = ['-O0', '-g']
239 if conf.env['MSVC_COMPILER']:
240 conf.env['CFLAGS'] = ['/MD']
241 conf.env['CXXFLAGS'] = ['/MD']
242 append_cxx_flags(['-DNDEBUG'])
244 if Options.options.ultra_strict:
245 Options.options.strict = True
246 conf.env.append_value('CFLAGS', ['-Wredundant-decls',
247 '-Wstrict-prototypes',
248 '-Wmissing-prototypes'])
250 if Options.options.strict:
251 conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
252 conf.env.append_value('CXXFLAGS', ['-ansi',
253 '-Wnon-virtual-dtor',
254 '-Woverloaded-virtual'])
255 append_cxx_flags(['-Wall',
259 if sys.platform != "darwin":
260 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
261 append_cxx_flags(['-fstrict-overflow'])
263 if not conf.check_cc(fragment = '''
267 int main() { return 0; }''',
271 msg = 'Checking for clang'):
272 if sys.platform != "darwin":
273 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
274 append_cxx_flags(['-Wunsafe-loop-optimizations'])
275 # this is invalid (still) on Lion apple gcc
276 append_cxx_flags(['-Wlogical-op'])
279 if not conf.env['MSVC_COMPILER']:
280 append_cxx_flags(['-fshow-column'])
282 conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
283 conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
285 display_msg(conf, "Install prefix", conf.env['PREFIX'])
286 display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
287 display_msg(conf, "Build documentation", str(conf.env['DOCS']))
292 def set_c99_mode(conf):
293 if conf.env.MSVC_COMPILER:
294 # MSVC has no hope or desire to compile C99, just compile as C++
295 conf.env.append_unique('CFLAGS', ['-TP'])
297 conf.env.append_unique('CFLAGS', ['-std=c99'])
299 def set_local_lib(conf, name, has_objects):
300 var_name = 'HAVE_' + nameify(name.upper())
301 define(conf, var_name, 1)
303 if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
304 conf.env['AUTOWAF_LOCAL_LIBS'] = {}
305 conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
307 if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
308 conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
309 conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
311 def append_property(obj, key, val):
312 if hasattr(obj, key):
313 setattr(obj, key, getattr(obj, key) + val)
315 setattr(obj, key, val)
317 def use_lib(bld, obj, libs):
318 abssrcdir = os.path.abspath('.')
319 libs_list = libs.split()
321 in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
322 in_libs = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
324 append_property(obj, 'use', ' lib%s ' % l.lower())
325 append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
326 if in_headers or in_libs:
327 inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
328 for f in ['CFLAGS', 'CXXFLAGS']:
329 if not inc_flag in bld.env[f]:
330 bld.env.prepend_value(f, inc_flag)
332 append_property(obj, 'uselib', ' ' + l)
335 @before('apply_link')
336 def version_lib(self):
337 if sys.platform == 'win32':
338 self.vnum = None # Prevent waf from automatically appending -0
339 if self.env['PARDEBUG']:
340 applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
341 if [x for x in applicable if x in self.features]:
342 self.target = self.target + 'D'
344 def set_lib_env(conf, name, version):
345 'Set up environment for local library as if found via pkg-config.'
347 major_ver = version.split('.')[0]
348 pkg_var_name = 'PKG_' + name.replace('-', '_')
349 lib_name = '%s-%s' % (name, major_ver)
350 if conf.env.PARDEBUG:
352 conf.env[pkg_var_name] = lib_name
353 conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
354 conf.env['LIBPATH_' + NAME] = [conf.env.LIBDIR]
355 conf.env['LIB_' + NAME] = [lib_name]
357 def display_header(title):
358 Logs.pprint('BOLD', title)
360 def display_msg(conf, msg, status = None, color = None):
362 if type(status) == bool and status or status == "True":
364 elif type(status) == bool and not status or status == "False":
366 Logs.pprint('BOLD', " *", sep='')
367 Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
368 Logs.pprint('BOLD', ":", sep='')
369 Logs.pprint(color, status)
371 def link_flags(env, lib):
372 return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
374 def compile_flags(env, lib):
375 return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
386 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
387 '''Build a pkg-config file for a library.
388 name -- uppercase variable name (e.g. 'SOMENAME')
389 version -- version string (e.g. '1.2.3')
390 version_suffix -- name version suffix (e.g. '2')
391 libs -- string/list of dependencies (e.g. 'LIBFOO GLIB')
393 pkg_prefix = bld.env['PREFIX']
394 if pkg_prefix[-1] == '/':
395 pkg_prefix = pkg_prefix[:-1]
397 target = name.lower()
398 if version_suffix != '':
399 target += '-' + version_suffix
401 if bld.env['PARDEBUG']:
406 libdir = bld.env['LIBDIR']
407 if libdir.startswith(pkg_prefix):
408 libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
410 includedir = bld.env['INCLUDEDIR']
411 if includedir.startswith(pkg_prefix):
412 includedir = includedir.replace(pkg_prefix, '${prefix}')
414 obj = bld(features = 'subst',
415 source = '%s.pc.in' % name.lower(),
417 install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
418 exec_prefix = '${prefix}',
420 EXEC_PREFIX = '${prefix}',
422 INCLUDEDIR = includedir)
424 if type(libs) != list:
427 subst_dict[name + '_VERSION'] = version
428 subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
430 subst_dict[i + '_LIBS'] = link_flags(bld.env, i)
431 lib_cflags = compile_flags(bld.env, i)
434 subst_dict[i + '_CFLAGS'] = lib_cflags
436 obj.__dict__.update(subst_dict)
438 def build_dir(name, subdir):
440 return os.path.join('build', name, subdir)
442 return os.path.join('build', subdir)
444 # Clean up messy Doxygen documentation after it is built
445 def make_simple_dox(name):
450 os.chdir(build_dir(name, 'doc/html'))
451 page = 'group__%s.html' % name
452 if not os.path.exists(page):
455 ['%s_API ' % NAME, ''],
456 ['%s_DEPRECATED ' % NAME, ''],
457 ['group__%s.html' % name, ''],
459 ['<script.*><\/script>', ''],
460 ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
461 ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
463 ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
465 os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
466 os.rename('group__%s.html' % name, 'index.html')
467 for i in (glob.glob('*.png') +
468 glob.glob('*.html') +
471 if i != 'index.html' and i != 'style.css':
474 os.chdir(build_dir(name, 'doc/man/man3'))
475 for i in glob.glob('*.3'):
476 os.system("sed -i 's/%s_API //' %s" % (NAME, i))
477 for i in glob.glob('_*'):
480 except Exception as e:
481 Logs.error("Failed to fix up %s documentation: %s" % (name, e))
483 # Doxygen API documentation
484 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
485 if not bld.env['DOCS']:
489 src_dir = os.path.join(srcdir, name.lower())
490 doc_dir = os.path.join(blddir, name.lower(), 'doc')
493 doc_dir = os.path.join(blddir, 'doc')
495 subst_tg = bld(features = 'subst',
496 source = 'doc/reference.doxygen.in',
497 target = 'doc/reference.doxygen',
502 name + '_VERSION' : version,
503 name + '_SRCDIR' : os.path.abspath(src_dir),
504 name + '_DOC_DIR' : os.path.abspath(doc_dir)
507 subst_tg.__dict__.update(subst_dict)
511 docs = bld(features = 'doxygen',
512 doxyfile = 'doc/reference.doxygen')
516 major = int(version[0:version.find('.')])
518 os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
519 bld.path.get_bld().ant_glob('doc/html/*'))
520 for i in range(1, 8):
521 bld.install_files('${MANDIR}/man%d' % i,
522 bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
525 # Version code file generation
526 def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
527 header_path = os.path.abspath(header_path)
528 source_path = os.path.abspath(source_path)
529 text = "int " + domain + "_major_version = " + str(major) + ";\n"
530 text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
531 text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
533 o = open(source_path, 'w')
537 Logs.error('Failed to open %s for writing\n' % source_path)
540 text = "#ifndef __" + domain + "_version_h__\n"
541 text += "#define __" + domain + "_version_h__\n"
543 text += "#include \"" + visheader + "\"\n"
544 text += exportname + " extern const char* " + domain + "_revision;\n"
545 text += exportname + " extern int " + domain + "_major_version;\n"
546 text += exportname + " extern int " + domain + "_minor_version;\n"
547 text += exportname + " extern int " + domain + "_micro_version;\n"
548 text += "#endif /* __" + domain + "_version_h__ */\n"
550 o = open(header_path, 'w')
554 Logs.warn('Failed to open %s for writing\n' % header_path)
559 # Internationalization with gettext
560 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
561 Logs.info('Generating pot file from %s' % name)
562 pot_file = '%s.pot' % name
573 cmd += ['--copyright-holder="%s"' % copyright_holder]
576 Logs.info('Updating ' + pot_file)
577 subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
579 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
581 os.chdir(os.path.join(srcdir, dir))
582 pot_file = '%s.pot' % name
583 po_files = glob.glob('po/*.po')
584 for po_file in po_files:
587 '--no-fuzzy-matching',
590 Logs.info('Updating ' + po_file)
594 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
596 os.chdir(os.path.join(srcdir, dir))
597 pot_file = '%s.pot' % name
598 po_files = glob.glob('po/*.po')
599 for po_file in po_files:
600 mo_file = po_file.replace('.po', '.mo')
607 Logs.info('Generating ' + po_file)
611 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
612 build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
613 build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
614 build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
616 def cd_to_build_dir(ctx, appname):
617 orig_dir = os.path.abspath(os.curdir)
618 top_level = (len(ctx.stack_path) > 1)
620 os.chdir(os.path.join('build', appname))
623 Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
625 def cd_to_orig_dir(ctx, child):
627 os.chdir(os.path.join('..', '..'))
631 def pre_test(ctx, appname, dirs=['src']):
634 diropts += ' -d ' + i
635 cd_to_build_dir(ctx, appname)
636 clear_log = open('lcov-clear.log', 'w')
639 # Clear coverage data
640 subprocess.call(('lcov %s -z' % diropts).split(),
641 stdout=clear_log, stderr=clear_log)
643 Logs.warn('Failed to run lcov, no coverage report will be generated')
647 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
650 diropts += ' -d ' + i
651 coverage_log = open('lcov-coverage.log', 'w')
652 coverage_lcov = open('coverage.lcov', 'w')
653 coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
660 # Generate coverage data
661 subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
662 stdout=coverage_lcov, stderr=coverage_log)
664 # Strip unwanted stuff
666 ['lcov', '--remove', 'coverage.lcov'] + remove,
667 stdout=coverage_stripped_lcov, stderr=coverage_log)
669 # Generate HTML coverage output
670 if not os.path.isdir('coverage'):
671 os.makedirs('coverage')
672 subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
673 stdout=coverage_log, stderr=coverage_log)
676 Logs.warn('Failed to run lcov, no coverage report will be generated')
678 coverage_stripped_lcov.close()
679 coverage_lcov.close()
683 Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
684 top_level = (len(ctx.stack_path) > 1)
686 cd_to_orig_dir(ctx, top_level)
689 Logs.pprint('BOLD', 'Coverage:', sep='')
690 print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
692 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
696 diropts += ' -d ' + i
701 if type(i) == type([]):
704 Logs.pprint('BOLD', '** Test', sep='')
705 Logs.pprint('NORMAL', '%s' % s)
707 if Options.options.grind:
708 cmd = 'valgrind ' + i
709 if subprocess.call(cmd, shell=True) == desired_status:
710 Logs.pprint('GREEN', '** Pass')
713 Logs.pprint('RED', '** FAIL')
717 Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
719 Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))