1import glob 2import os 3import subprocess 4import sys 5import time 6 7from waflib import Configure, ConfigSet, Build, Context, Logs, Options, Utils 8from waflib.TaskGen import feature, before, after, after_method 9 10NONEMPTY = -10 11 12if sys.platform == 'win32': 13 lib_path_name = 'PATH' 14elif sys.platform == 'darwin': 15 lib_path_name = 'DYLD_LIBRARY_PATH' 16else: 17 lib_path_name = 'LD_LIBRARY_PATH' 18 19# Compute dependencies globally 20# import preproc 21# preproc.go_absolute = True 22 23 24@feature('c', 'cxx') 25@after('apply_incpaths') 26def include_config_h(self): 27 self.env.append_value('INCPATHS', self.bld.bldnode.abspath()) 28 29 30def _set_system_headers(self, varname): 31 if 'AUTOWAF_SYSTEM_PKGS' in self.env and not self.env.MSVC_COMPILER: 32 for lib in self.uselib: 33 if lib in self.env.AUTOWAF_SYSTEM_PKGS: 34 for include in self.env['INCLUDES_' + lib]: 35 self.env.append_unique(varname, ['-isystem%s' % include]) 36 37 38@feature('c') 39@after_method('apply_incpaths') 40def set_system_headers_c(self): 41 _set_system_headers(self, 'CFLAGS') 42 43 44@feature('cxx') 45@after_method('apply_incpaths') 46def set_system_headers_cxx(self): 47 _set_system_headers(self, 'CXXFLAGS') 48 49 50class OptionsContext(Options.OptionsContext): 51 def __init__(self, **kwargs): 52 super(OptionsContext, self).__init__(**kwargs) 53 set_options(self) 54 55 def configuration_options(self): 56 return self.get_option_group('Configuration options') 57 58 def add_flags(self, group, flags): 59 """Tersely add flags (a dictionary of longname:desc) to a group""" 60 for name, desc in flags.items(): 61 group.add_option('--' + name, action='store_true', 62 dest=name.replace('-', '_'), help=desc) 63 64 65def set_options(opt): 66 "Add standard autowaf options" 67 opts = opt.get_option_group('Configuration options') 68 69 # Standard directory options 70 opts.add_option('--bindir', type='string', 71 help="executable programs [default: PREFIX/bin]") 72 opts.add_option('--configdir', type='string', 73 help="configuration data [default: PREFIX/etc]") 74 opts.add_option('--datadir', type='string', 75 help="shared data [default: PREFIX/share]") 76 opts.add_option('--includedir', type='string', 77 help="header files [default: PREFIX/include]") 78 opts.add_option('--libdir', type='string', 79 help="libraries [default: PREFIX/lib]") 80 opts.add_option('--mandir', type='string', 81 help="manual pages [default: DATADIR/man]") 82 opts.add_option('--docdir', type='string', 83 help="HTML documentation [default: DATADIR/doc]") 84 85 # Build options 86 opts.add_option('-d', '--debug', action='store_true', default=False, 87 dest='debug', help="build debuggable binaries") 88 opts.add_option('--pardebug', action='store_true', default=False, 89 dest='pardebug', 90 help="build debug libraries with D suffix") 91 92 opts.add_option('-s', '--strict', action='store_true', default=False, 93 dest='strict', 94 help="use strict compiler flags and show all warnings") 95 opts.add_option('-S', '--ultra-strict', action='store_true', default=False, 96 dest='ultra_strict', 97 help="use extremely strict compiler flags (likely noisy)") 98 opts.add_option('--docs', action='store_true', default=False, dest='docs', 99 help="build documentation (requires doxygen)") 100 opts.add_option('-w', '--werror', action='store_true', dest='werror', 101 help="Treat warnings as errors") 102 103 # Test options 104 if hasattr(Context.g_module, 'test'): 105 test_opts = opt.add_option_group('Test options', '') 106 opts.add_option('-T', '--test', action='store_true', 107 dest='build_tests', help='build unit tests') 108 opts.add_option('--no-coverage', action='store_true', 109 dest='no_coverage', 110 help='do not instrument code for test coverage') 111 test_opts.add_option('--test-filter', type='string', 112 dest='test_filter', 113 help='regular expression for tests to run') 114 115 # Run options 116 run_opts = opt.add_option_group('Run options') 117 run_opts.add_option('--cmd', type='string', dest='cmd', 118 help='command to run from build directory') 119 run_opts.add_option('--wrapper', type='string', 120 dest='wrapper', 121 help='command prefix for running executables') 122 123 124class ConfigureContext(Configure.ConfigurationContext): 125 """configures the project""" 126 127 def __init__(self, **kwargs): 128 self.line_just = 45 129 if hasattr(Context.g_module, 'line_just'): 130 self.line_just = Context.g_module.line_just 131 132 super(ConfigureContext, self).__init__(**kwargs) 133 self.run_env = ConfigSet.ConfigSet() 134 135 def pre_recurse(self, node): 136 if len(self.stack_path) == 1: 137 Logs.pprint('BOLD', 'Configuring %s' % node.parent.srcpath()) 138 super(ConfigureContext, self).pre_recurse(node) 139 140 def store(self): 141 self.env.AUTOWAF_RUN_ENV = self.run_env.get_merged_dict() 142 super(ConfigureContext, self).store() 143 144 def check_pkg(self, *args, **kwargs): 145 return check_pkg(self, *args, **kwargs) 146 147 def check_function(self, *args, **kwargs): 148 return check_function(self, *args, **kwargs) 149 150 def build_path(self, path='.'): 151 """Return `path` within the build directory""" 152 return str(self.path.get_bld().make_node(path)) 153 154 155def get_check_func(conf, lang): 156 if lang == 'c': 157 return conf.check_cc 158 elif lang == 'cxx': 159 return conf.check_cxx 160 else: 161 Logs.error("Unknown header language `%s'" % lang) 162 163 164def check_header(conf, lang, name, define='', mandatory=True): 165 "Check for a header" 166 check_func = get_check_func(conf, lang) 167 if define != '': 168 check_func(header_name=name, 169 define_name=define, 170 mandatory=mandatory) 171 else: 172 check_func(header_name=name, mandatory=mandatory) 173 174 175def check_function(conf, lang, name, **args): 176 "Check for a function" 177 header_names = Utils.to_list(args['header_name']) 178 includes = ''.join(['#include <%s>\n' % x for x in header_names]) 179 return_type = args['return_type'] if 'return_type' in args else 'int' 180 arg_types = args['arg_types'] if 'arg_types' in args else '' 181 182 fragment = ''' 183%s 184 185typedef %s (*Func)(%s); 186 187int main(void) { 188 static const Func ptr = %s; 189 (void)ptr; 190 return 0; 191} 192''' % (includes, return_type, arg_types, name) 193 194 check_func = get_check_func(conf, lang) 195 args['msg'] = 'Checking for %s' % name 196 if lang + 'flags' not in args: 197 args[lang + 'flags'] = check_flags(conf, conf.env.CFLAGS) 198 199 check_func(fragment=fragment, **args) 200 201 202def nameify(name): 203 return (name.replace('/', '_').replace('++', 'PP') 204 .replace('-', '_').replace('.', '_')) 205 206 207def check_pkg(conf, spec, **kwargs): 208 "Check for a package iff it hasn't been checked for yet" 209 210 uselib_store = kwargs['uselib_store'] 211 is_local = (uselib_store.lower() in conf.env['AUTOWAF_LOCAL_LIBS'] or 212 uselib_store.lower() in conf.env['AUTOWAF_LOCAL_HEADERS']) 213 214 if is_local: 215 return 216 217 import re 218 match = re.match(r'([^ ]*) >= [0-9\.]*', spec) 219 args = [] 220 if match: 221 name = match.group(1) 222 args = [spec] 223 elif spec.find(' ') == -1: 224 name = spec 225 else: 226 Logs.error("Invalid package spec: %s" % spec) 227 228 found = None 229 pkg_name = name 230 args += kwargs.get('args', []) 231 232 if conf.env.PARDEBUG: 233 kwargs['mandatory'] = False # Smash mandatory arg 234 found = conf.check_cfg(package=pkg_name + 'D', 235 args=args + ['--cflags', '--libs']) 236 if found: 237 pkg_name += 'D' 238 239 args['mandatory'] = kwargs['mandatory'] # Unsmash mandatory arg 240 241 if not found: 242 found = conf.check_cfg(package=spec, 243 args=args + ['--cflags', '--libs'], 244 **kwargs) 245 246 if not conf.env.MSVC_COMPILER and 'system' in kwargs and kwargs['system']: 247 conf.env.append_unique('AUTOWAF_SYSTEM_PKGS', uselib_store) 248 249 250def normpath(path): 251 if sys.platform == 'win32': 252 return os.path.normpath(path).replace('\\', '/') 253 else: 254 return os.path.normpath(path) 255 256 257# Almost all GCC warnings common to C and C++ 258gcc_common_warnings = [ 259 # '-Waggregate-return', # Pretty esoteric, and not in clang 260 '-Waggressive-loop-optimizations', 261 '-Wall', 262 '-Walloc-zero', 263 '-Walloca', 264 # '-Walloca-larger-than=', 265 '-Wattribute-alias', 266 '-Wattributes', 267 '-Wbuiltin-declaration-mismatch', 268 '-Wbuiltin-macro-redefined', 269 '-Wcast-align', 270 '-Wcast-align=strict', 271 '-Wcast-qual', 272 '-Wconversion', 273 '-Wcoverage-mismatch', 274 '-Wcpp', 275 '-Wdate-time', 276 '-Wdeprecated', 277 '-Wdeprecated-declarations', 278 '-Wdisabled-optimization', 279 '-Wdiv-by-zero', 280 '-Wdouble-promotion', 281 '-Wduplicated-branches', 282 '-Wduplicated-cond', 283 '-Wextra', 284 '-Wfloat-equal', 285 '-Wformat-signedness', 286 '-Wnormalized', 287 # '-Wframe-larger-than=', 288 '-Wfree-nonheap-object', 289 '-Whsa', 290 '-Wif-not-aligned', 291 '-Wignored-attributes', 292 '-Winline', 293 '-Wint-to-pointer-cast', 294 '-Winvalid-memory-model', 295 '-Winvalid-pch', 296 # '-Wlarger-than=', 297 '-Wlogical-op', 298 '-Wlto-type-mismatch', 299 '-Wmissing-declarations', 300 '-Wmissing-include-dirs', 301 '-Wmultichar', 302 '-Wnull-dereference', 303 '-Wodr', 304 '-Woverflow', 305 '-Wpacked', 306 '-Wpacked-bitfield-compat', 307 '-Wpadded', 308 '-Wpedantic', 309 '-Wpointer-compare', 310 '-Wpragmas', 311 '-Wredundant-decls', 312 '-Wreturn-local-addr', 313 '-Wscalar-storage-order', 314 '-Wshadow', 315 '-Wshift-count-negative', 316 '-Wshift-count-overflow', 317 '-Wshift-negative-value', 318 '-Wshift-overflow=2', 319 '-Wsizeof-array-argument', 320 '-Wstack-protector', 321 # '-Wstack-usage=', 322 '-Wstrict-aliasing', 323 '-Wstrict-overflow', 324 '-Wsuggest-attribute=cold', 325 '-Wsuggest-attribute=const', 326 '-Wsuggest-attribute=format', 327 '-Wsuggest-attribute=malloc', 328 '-Wsuggest-attribute=noreturn', 329 '-Wsuggest-attribute=pure', 330 '-Wswitch-bool', 331 '-Wnormalized', 332 # '-Wswitch-default', # Redundant with Wswitch and not in clang 333 '-Wswitch-enum', 334 '-Wswitch-unreachable', 335 '-Wsync-nand', 336 # '-Wsystem-headers', 337 '-Wtrampolines', 338 '-Wundef', 339 '-Wunused-macros', 340 '-Wunused-result', 341 '-Wvarargs', 342 '-Wvector-operation-performance', 343 '-Wvla', 344 # '-Wvla-larger-than=', 345 '-Wwrite-strings', 346] 347 348# Almost all C-specific GCC warnings, except those for ancient (pre-C99) C 349gcc_c_warnings = [ 350 '-Wbad-function-cast', 351 '-Wc++-compat', 352 # '-Wc90-c99-compat', 353 '-Wc99-c11-compat', 354 # '-Wdeclaration-after-statement', 355 '-Wdesignated-init', 356 '-Wdiscarded-array-qualifiers', 357 '-Wdiscarded-qualifiers', 358 '-Wincompatible-pointer-types', 359 '-Wint-conversion', 360 '-Wjump-misses-init', 361 '-Wmissing-prototypes', 362 '-Wnested-externs', 363 '-Wold-style-definition', 364 '-Woverride-init-side-effects', 365 '-Wpointer-to-int-cast', 366 '-Wstrict-prototypes', 367 # '-Wtraditional', 368 # '-Wtraditional-conversion', 369 # '-Wunsuffixed-float-constants', 370] 371 372# Almost all C++-specific GCC warnings, except those about common feature use 373gcc_cxx_warnings = [ 374 '-Wconditionally-supported', 375 '-Wconversion-null', 376 '-Wctor-dtor-privacy', 377 '-Wdelete-incomplete', 378 '-Weffc++', 379 '-Wextra-semi', 380 '-Winherited-variadic-ctor', 381 '-Winvalid-offsetof', 382 '-Wliteral-suffix', 383 '-Wmultiple-inheritance', 384 # '-Wnamespaces', 385 '-Wnoexcept', 386 '-Wnon-template-friend', 387 '-Wnon-virtual-dtor', 388 '-Wold-style-cast', 389 '-Woverloaded-virtual', 390 '-Wplacement-new=2', 391 '-Wpmf-conversions', 392 '-Wregister', 393 '-Wsign-promo', 394 '-Wstrict-null-sentinel', 395 '-Wsubobject-linkage', 396 '-Wsuggest-final-methods', 397 '-Wsuggest-final-types', 398 '-Wsuggest-override', 399 '-Wsynth', 400 # '-Wtemplates', 401 '-Wterminate', 402 '-Wuseless-cast', 403 '-Wvirtual-inheritance', 404 '-Wvirtual-move-assign', 405 '-Wzero-as-null-pointer-constant', 406] 407 408 409def remove_all_warning_flags(env): 410 """Removes all warning flags except Werror or equivalent""" 411 if 'CC' in env: 412 if 'clang' in env.CC_NAME or 'gcc' in env.CC_NAME: 413 env['CFLAGS'] = [f for f in env['CFLAGS'] 414 if not (f.startswith('-W') and f != '-Werror')] 415 elif 'msvc' in env.CC_NAME: 416 env['CFLAGS'] = [f for f in env['CFLAGS'] 417 if not (f.startswith('/W') and f != '/WX')] 418 419 if 'CXX' in env: 420 if 'clang' in env.CXX_NAME or 'gcc' in env.CXX_NAME: 421 env['CXXFLAGS'] = [f for f in env['CXXFLAGS'] 422 if not (f.startswith('-W') and f != '-Werror')] 423 elif 'msvc' in env.CXX_NAME: 424 env['CXXFLAGS'] = [f for f in env['CXXFLAGS'] 425 if not (f.startswith('/W') and f != '/WX')] 426 427 428def enable_all_warnings(env): 429 """Enables all known warnings""" 430 if 'CC' in env: 431 if 'clang' in env.CC_NAME: 432 env.append_unique('CFLAGS', ['-Weverything']) 433 elif 'gcc' in env.CC_NAME: 434 env.append_unique('CFLAGS', gcc_common_warnings) 435 env.append_unique('CFLAGS', gcc_c_warnings) 436 elif env.MSVC_COMPILER: 437 env.append_unique('CFLAGS', ['/Wall']) 438 else: 439 Logs.warn('Unknown compiler "%s", not enabling warnings' % env.CC_NAME) 440 441 if 'CXX' in env: 442 if 'clang' in env.CXX_NAME: 443 env.append_unique('CXXFLAGS', ['-Weverything', 444 '-Wno-c++98-compat', 445 '-Wno-c++98-compat-pedantic']) 446 elif 'gcc' in env.CXX_NAME: 447 env.append_unique('CXXFLAGS', gcc_common_warnings) 448 env.append_unique('CXXFLAGS', gcc_cxx_warnings) 449 elif env.MSVC_COMPILER: 450 env.append_unique('CXXFLAGS', ['/Wall']) 451 else: 452 Logs.warn('Unknown compiler "%s", not enabling warnings' % env.CXX_NAME) 453 454 455def set_warnings_as_errors(env): 456 if 'CC' in env: 457 if 'clang' in env.CC_NAME or 'gcc' in env.CC_NAME: 458 env.append_unique('CFLAGS', ['-Werror']) 459 elif env.MSVC_COMPILER: 460 env.append_unique('CFLAGS', ['/WX']) 461 462 if 'CXX' in env: 463 if 'clang' in env.CXX_NAME or 'gcc' in env.CXX_NAME: 464 env.append_unique('CXXFLAGS', ['-Werror']) 465 elif env.MSVC_COMPILER: 466 env.append_unique('CXXFLAGS', ['/WX']) 467 468 469def add_compiler_flags(env, lang, compiler_to_flags): 470 """Add compiler-specific flags, for example to suppress warnings. 471 472 The lang argument must be "c", "cxx", or "*" for both. 473 474 The compiler_to_flags argument must be a map from compiler name 475 ("clang", "gcc", or "msvc") to a list of command line flags. 476 """ 477 478 if lang == "*": 479 add_compiler_flags(env, 'c', compiler_to_flags) 480 add_compiler_flags(env, 'cxx', compiler_to_flags) 481 else: 482 if lang == 'c': 483 compiler_name = env.CC_NAME 484 elif lang == 'cxx': 485 compiler_name = env.CXX_NAME 486 else: 487 raise Exception('Unknown language "%s"' % lang) 488 489 var_name = lang.upper() + 'FLAGS' 490 for name, flags in compiler_to_flags.items(): 491 if name in compiler_name: 492 env.append_value(var_name, flags) 493 494 495def configure(conf): 496 def append_cxx_flags(flags): 497 conf.env.append_value('CFLAGS', flags) 498 conf.env.append_value('CXXFLAGS', flags) 499 500 if Options.options.docs: 501 conf.load('doxygen') 502 503 try: 504 conf.load('clang_compilation_database') 505 except Exception: 506 pass 507 508 prefix = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX']))) 509 510 conf.env['DOCS'] = Options.options.docs and conf.env.DOXYGEN 511 conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug 512 conf.env['PARDEBUG'] = Options.options.pardebug 513 conf.env['PREFIX'] = prefix 514 515 def config_dir(var, opt, default): 516 if opt: 517 conf.env[var] = normpath(opt) 518 else: 519 conf.env[var] = normpath(default) 520 521 opts = Options.options 522 523 config_dir('BINDIR', opts.bindir, os.path.join(prefix, 'bin')) 524 config_dir('SYSCONFDIR', opts.configdir, os.path.join(prefix, 'etc')) 525 config_dir('DATADIR', opts.datadir, os.path.join(prefix, 'share')) 526 config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include')) 527 config_dir('LIBDIR', opts.libdir, os.path.join(prefix, 'lib')) 528 529 datadir = conf.env['DATADIR'] 530 config_dir('MANDIR', opts.mandir, os.path.join(datadir, 'man')) 531 config_dir('DOCDIR', opts.docdir, os.path.join(datadir, 'doc')) 532 533 if Options.options.debug: 534 if conf.env['MSVC_COMPILER']: 535 conf.env['CFLAGS'] = ['/Od', '/Z7'] 536 conf.env['CXXFLAGS'] = ['/Od', '/Z7'] 537 conf.env['LINKFLAGS'] = ['/DEBUG', '/MANIFEST'] 538 else: 539 conf.env['CFLAGS'] = ['-O0', '-g'] 540 conf.env['CXXFLAGS'] = ['-O0', '-g'] 541 else: 542 if 'CFLAGS' not in os.environ: 543 if conf.env['MSVC_COMPILER']: 544 conf.env.append_unique('CFLAGS', ['/O2', '/DNDEBUG']) 545 else: 546 conf.env.append_unique('CFLAGS', ['-O2', '-DNDEBUG']) 547 548 if 'CXXFLAGS' not in os.environ: 549 if conf.env['MSVC_COMPILER']: 550 conf.env.append_unique('CXXFLAGS', ['/O2', '/DNDEBUG']) 551 else: 552 conf.env.append_unique('CXXFLAGS', ['-O2', '-DNDEBUG']) 553 554 if conf.env['MSVC_COMPILER']: 555 conf.env['CFLAGS'] += ['/MD'] 556 conf.env['CXXFLAGS'] += ['/MD'] 557 558 if Options.options.ultra_strict: 559 Options.options.strict = True 560 remove_all_warning_flags(conf.env) 561 enable_all_warnings(conf.env) 562 if Options.options.werror and 'clang' in conf.env.CC_NAME: 563 conf.env.append_unique('CFLAGS', '-Wno-unknown-warning-option') 564 if Options.options.werror and 'clang' in conf.env.CXX_NAME: 565 conf.env.append_unique('CXXFLAGS', '-Wno-unknown-warning-option') 566 567 if conf.env.MSVC_COMPILER: 568 Options.options.no_coverage = True 569 append_cxx_flags(['/nologo', 570 '/FS', 571 '/D_CRT_SECURE_NO_WARNINGS', 572 '/experimental:external', 573 '/external:W0', 574 '/external:anglebrackets']) 575 conf.env.append_unique('CXXFLAGS', ['/EHsc']) 576 conf.env.append_value('LINKFLAGS', '/nologo') 577 elif Options.options.strict: 578 if conf.env.DEST_OS != "darwin": 579 sanitizing = False 580 for f in conf.env.LINKFLAGS: 581 if f.startswith('-fsanitize'): 582 sanitizing = True 583 break; 584 585 if not sanitizing: 586 conf.env.append_value('LINKFLAGS', ['-Wl,--no-undefined']) 587 588 # Add less universal flags after checking they work 589 extra_flags = ['-Wlogical-op', 590 '-Wsuggest-attribute=noreturn', 591 '-Wunsafe-loop-optimizations'] 592 cflags = flag_check_flags(conf, conf.env.CFLAGS) + extra_flags 593 if conf.check_cc(cflags=cflags, 594 mandatory=False, 595 msg="Checking for extra C warning flags"): 596 conf.env.append_value('CFLAGS', extra_flags) 597 if 'COMPILER_CXX' in conf.env: 598 cxxflags = flag_check_flags(conf, conf.env.CXXFLAGS) + extra_flags 599 if conf.check_cxx(cxxflags=cxxflags, 600 mandatory=False, 601 msg="Checking for extra C++ warning flags"): 602 conf.env.append_value('CXXFLAGS', extra_flags) 603 604 if not conf.env['MSVC_COMPILER']: 605 append_cxx_flags(['-fshow-column']) 606 607 if Options.options.werror: 608 if conf.env.MSVC_COMPILER: 609 append_cxx_flags('/WX') 610 else: 611 append_cxx_flags('-Werror') 612 613 conf.env.NO_COVERAGE = True 614 conf.env.BUILD_TESTS = False 615 try: 616 conf.env.BUILD_TESTS = Options.options.build_tests 617 conf.env.NO_COVERAGE = Options.options.no_coverage 618 if conf.env.BUILD_TESTS and not Options.options.no_coverage: 619 # Set up unit test code coverage 620 if conf.is_defined('CLANG'): 621 for cov in [conf.env.CC[0].replace('clang', 'llvm-cov'), 622 'llvm-cov']: 623 if conf.find_program(cov, var='LLVM_COV', mandatory=False): 624 break 625 else: 626 if 'CC' in conf.env: 627 if conf.check_cc(cflags=check_flags(conf, conf.env.CFLAGS), 628 lib='gcov', 629 mandatory=False, 630 uselib_store='GCOV'): 631 conf.env.HAVE_GCOV = True 632 else: 633 if conf.check_cxx(cflags=check_flags(conf, conf.env.CXXFLAGS), 634 lib='gcov', 635 mandatory=False, 636 uselib_store='GCOV'): 637 conf.env.HAVE_GCOV = True 638 except AttributeError: 639 pass # Test options do not exist 640 except Exception as e: 641 Logs.error("error: %s" % e) 642 643 # Define version in configuration 644 appname = getattr(Context.g_module, Context.APPNAME, 'noname') 645 version = getattr(Context.g_module, Context.VERSION, '0.0.0') 646 defname = appname.upper().replace('-', '_').replace('.', '_') 647 conf.define(defname + '_VERSION', version) 648 conf.env[defname + '_VERSION'] = version 649 650 651def display_summary(conf, msgs=None): 652 if len(conf.stack_path) == 1: 653 display_msg(conf, "Install prefix", conf.env['PREFIX']) 654 if 'COMPILER_CC' in conf.env: 655 display_msg(conf, "C Flags", ' '.join(conf.env['CFLAGS'])) 656 if 'COMPILER_CXX' in conf.env: 657 display_msg(conf, "C++ Flags", ' '.join(conf.env['CXXFLAGS'])) 658 display_msg(conf, "Debuggable", bool(conf.env['DEBUG'])) 659 display_msg(conf, "Build documentation", bool(conf.env['DOCS'])) 660 661 if msgs is not None: 662 display_msgs(conf, msgs) 663 664 665def check_flags(conf, flags): 666 if conf.env.MSVC_COMPILER: 667 return [] 668 669 # Disable silly attribute warnings that trigger in the generated check code 670 result = [] 671 if '-Wsuggest-attribute=const' in flags: 672 result += ['-Wno-suggest-attribute=const'] 673 if '-Wsuggest-attribute=pure' in flags: 674 result += ['-Wno-suggest-attribute=pure'] 675 676 return result 677 678 679def flag_check_flags(conf, flags): 680 if conf.env.MSVC_COMPILER: 681 return ['/WX'] + check_flags(conf, flags) 682 else: 683 return ['-Werror'] + check_flags(conf, flags) 684 685 686def set_c_lang(conf, lang, **kwargs): 687 "Set a specific C language standard, like 'c99' or 'c11'" 688 if conf.env.MSVC_COMPILER: 689 # MSVC has no hope or desire to compile C99, just compile as C++ 690 conf.env.append_unique('CFLAGS', ['/TP']) 691 return True 692 elif not (lang == 'c99' and '-std=c11' in conf.env.CFLAGS): 693 flag = '-std=%s' % lang 694 if conf.check(features='c cstlib', 695 cflags=flag_check_flags(conf, conf.env.CFLAGS) + [flag], 696 msg="Checking for flag '%s'" % flag, 697 **kwargs): 698 conf.env.append_unique('CFLAGS', [flag]) 699 return True 700 return False 701 702 703def set_cxx_lang(conf, lang): 704 "Set a specific C++ language standard, like 'c++11', 'c++14', or 'c++17'" 705 if conf.env.MSVC_COMPILER: 706 if lang != 'c++14': 707 lang = 'c++latest' 708 conf.env.append_unique('CXXFLAGS', ['/std:%s' % lang]) 709 else: 710 flag = '-std=%s' % lang 711 conf.check(cxxflags=flag_check_flags(conf, conf.env.CXXFLAGS) + [flag], 712 msg="Checking for flag '%s'" % flag) 713 conf.env.append_unique('CXXFLAGS', [flag]) 714 715 716def set_modern_c_flags(conf): 717 "Use the most modern C language available" 718 if 'COMPILER_CC' in conf.env: 719 if conf.env.MSVC_COMPILER: 720 # MSVC has no hope or desire to compile C99, just compile as C++ 721 conf.env.append_unique('CFLAGS', ['/TP']) 722 else: 723 for flag in ['-std=c11', '-std=c99']: 724 if conf.check(cflags=['-Werror', flag], mandatory=False, 725 msg="Checking for flag '%s'" % flag): 726 conf.env.append_unique('CFLAGS', [flag]) 727 break 728 729 730def set_modern_cxx_flags(conf, mandatory=False): 731 "Use the most modern C++ language available" 732 if 'COMPILER_CXX' in conf.env: 733 if conf.env.MSVC_COMPILER: 734 conf.env.append_unique('CXXFLAGS', ['/std:c++latest']) 735 else: 736 for lang in ['c++14', 'c++1y', 'c++11', 'c++0x']: 737 flag = '-std=%s' % lang 738 if conf.check(cxxflags=['-Werror', flag], mandatory=False, 739 msg="Checking for flag '%s'" % flag): 740 conf.env.append_unique('CXXFLAGS', [flag]) 741 break 742 743 744def set_local_lib(conf, name, has_objects): 745 var_name = 'HAVE_' + nameify(name.upper()) 746 conf.define(var_name, 1) 747 conf.env[var_name] = 1 748 if has_objects: 749 if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict: 750 conf.env['AUTOWAF_LOCAL_LIBS'] = {} 751 conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True 752 else: 753 if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict: 754 conf.env['AUTOWAF_LOCAL_HEADERS'] = {} 755 conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True 756 757 758def append_property(obj, key, val): 759 if hasattr(obj, key): 760 setattr(obj, key, getattr(obj, key) + val) 761 else: 762 setattr(obj, key, val) 763 764 765@feature('c', 'cxx') 766@before('apply_link') 767def version_lib(self): 768 if self.env.DEST_OS == 'win32': 769 self.vnum = None # Prevent waf from automatically appending -0 770 if self.env['PARDEBUG']: 771 applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib'] 772 if [x for x in applicable if x in self.features]: 773 self.target = self.target + 'D' 774 775 776def set_lib_env(conf, 777 name, 778 version, 779 has_objects=True, 780 include_path=None, 781 lib_path=None, 782 lib=None): 783 "Set up environment for local library as if found via pkg-config." 784 NAME = name.upper() 785 major_ver = version.split('.')[0] 786 pkg_var_name = 'PKG_' + name.replace('-', '_') + '_' + major_ver 787 lib_name = '%s-%s' % (lib if lib is not None else name, major_ver) 788 789 if lib_path is None: 790 lib_path = str(conf.path.get_bld()) 791 792 if include_path is None: 793 include_path = str(conf.path) 794 795 if conf.env.PARDEBUG: 796 lib_name += 'D' 797 798 conf.env[pkg_var_name] = lib_name 799 conf.env['INCLUDES_' + NAME] = [include_path] 800 conf.env['LIBPATH_' + NAME] = [lib_path] 801 if has_objects: 802 conf.env['LIB_' + NAME] = [lib_name] 803 804 conf.run_env.append_unique(lib_path_name, [lib_path]) 805 conf.define(NAME + '_VERSION', version) 806 807 808def display_msg(conf, msg, status=None, color=None): 809 color = 'CYAN' 810 if type(status) == bool and status: 811 color = 'GREEN' 812 status = 'yes' 813 elif type(status) == bool and not status or status == "False": 814 color = 'YELLOW' 815 status = 'no' 816 Logs.pprint('BOLD', '%s' % msg.ljust(conf.line_just), sep='') 817 Logs.pprint('BOLD', ":", sep='') 818 Logs.pprint(color, status) 819 820 821def display_msgs(conf, msgs): 822 for k, v in msgs.items(): 823 display_msg(conf, k, v) 824 825 826def link_flags(env, lib): 827 return ' '.join(map(lambda x: env['LIB_ST'] % x, 828 env['LIB_' + lib])) 829 830 831def compile_flags(env, lib): 832 return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, 833 env['INCLUDES_' + lib])) 834 835 836def build_pc(bld, name, version, version_suffix, libs, subst_dict={}): 837 """Build a pkg-config file for a library. 838 839 name -- uppercase variable name (e.g. 'SOMENAME') 840 or path to template without .pc.in extension 841 version -- version string (e.g. '1.2.3') 842 version_suffix -- name version suffix (e.g. '2') 843 libs -- string/list of dependencies (e.g. 'LIBFOO GLIB') 844 """ 845 846 if '/' in name: 847 source = '%s.pc.in' % name.lower() 848 name = os.path.basename(name) 849 else: 850 source = '%s.pc.in' % name.lower() 851 852 pkg_prefix = bld.env['PREFIX'] 853 if len(pkg_prefix) > 1 and pkg_prefix[-1] == '/': 854 pkg_prefix = pkg_prefix[:-1] 855 856 target = name.lower() 857 if version_suffix != '': 858 target += '-' + version_suffix 859 860 if bld.env['PARDEBUG']: 861 target += 'D' 862 863 target += '.pc' 864 865 libdir = bld.env['LIBDIR'] 866 if libdir.startswith(pkg_prefix): 867 libdir = libdir.replace(pkg_prefix, '${exec_prefix}') 868 869 includedir = bld.env['INCLUDEDIR'] 870 if includedir.startswith(pkg_prefix): 871 includedir = includedir.replace(pkg_prefix, '${prefix}') 872 873 obj = bld(features='subst', 874 source=source, 875 target=target, 876 install_path=os.path.join(bld.env['LIBDIR'], 'pkgconfig'), 877 exec_prefix='${prefix}', 878 PREFIX=pkg_prefix, 879 EXEC_PREFIX='${prefix}', 880 LIBDIR=libdir, 881 INCLUDEDIR=includedir) 882 883 if type(libs) != list: 884 libs = libs.split() 885 886 subst_dict[name + '_VERSION'] = version 887 subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')] 888 for i in libs: 889 subst_dict[i + '_LIBS'] = link_flags(bld.env, i) 890 lib_cflags = compile_flags(bld.env, i) 891 if lib_cflags == '': 892 lib_cflags = ' ' 893 subst_dict[i + '_CFLAGS'] = lib_cflags 894 895 obj.__dict__.update(subst_dict) 896 897 898def build_dox(bld, 899 name, 900 version, 901 srcdir, 902 blddir, 903 outdir='', 904 versioned=True, 905 install_man=True): 906 """Build Doxygen API documentation""" 907 if not bld.env['DOCS']: 908 return 909 910 # Doxygen paths in are relative to the doxygen file 911 src_dir = bld.path.srcpath() 912 subst_tg = bld(features='subst', 913 source='doc/reference.doxygen.in', 914 target='doc/reference.doxygen', 915 install_path='', 916 name='doxyfile') 917 918 subst_dict = { 919 name + '_VERSION': version, 920 name + '_SRCDIR': os.path.abspath(src_dir), 921 name + '_DOC_DIR': '' 922 } 923 924 subst_tg.__dict__.update(subst_dict) 925 926 subst_tg.post() 927 928 docs = bld(features='doxygen', 929 doxyfile='doc/reference.doxygen') 930 931 docs.post() 932 933 outname = name.lower() 934 if versioned: 935 outname += '-%d' % int(version[0:version.find('.')]) 936 bld.install_files( 937 os.path.join('${DOCDIR}', outname, outdir, 'html'), 938 bld.path.get_bld().ant_glob('doc/html/*')) 939 940 if install_man: 941 for i in range(1, 8): 942 bld.install_files( 943 '${MANDIR}/man%d' % i, 944 bld.path.get_bld().ant_glob('doc/man/man%d/*' % i, 945 excl='**/_*')) 946 947 948def build_version_files(header_path, source_path, domain, major, minor, micro): 949 """Generate version code header""" 950 header_path = os.path.abspath(header_path) 951 source_path = os.path.abspath(source_path) 952 text = "int " + domain + "_major_version = " + str(major) + ";\n" 953 text += "int " + domain + "_minor_version = " + str(minor) + ";\n" 954 text += "int " + domain + "_micro_version = " + str(micro) + ";\n" 955 try: 956 o = open(source_path, 'w') 957 o.write(text) 958 o.close() 959 except IOError: 960 Logs.error('Failed to open %s for writing\n' % source_path) 961 sys.exit(-1) 962 963 text = "#ifndef __" + domain + "_version_h__\n" 964 text += "#define __" + domain + "_version_h__\n" 965 text += "extern const char* " + domain + "_revision;\n" 966 text += "extern int " + domain + "_major_version;\n" 967 text += "extern int " + domain + "_minor_version;\n" 968 text += "extern int " + domain + "_micro_version;\n" 969 text += "#endif /* __" + domain + "_version_h__ */\n" 970 try: 971 o = open(header_path, 'w') 972 o.write(text) 973 o.close() 974 except IOError: 975 Logs.warn('Failed to open %s for writing\n' % header_path) 976 sys.exit(-1) 977 978 return None 979 980 981def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None): 982 Logs.info('Generating pot file from %s' % name) 983 pot_file = '%s.pot' % name 984 985 cmd = ['xgettext', 986 '--keyword=_', 987 '--keyword=N_', 988 '--keyword=S_', 989 '--from-code=UTF-8', 990 '-o', pot_file] 991 992 if copyright_holder: 993 cmd += ['--copyright-holder="%s"' % copyright_holder] 994 995 cmd += sources 996 Logs.info('Updating ' + pot_file) 997 subprocess.call(cmd, cwd=os.path.join(srcdir, dir)) 998 999 1000def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None): 1001 pwd = os.getcwd() 1002 os.chdir(os.path.join(srcdir, dir)) 1003 pot_file = '%s.pot' % name 1004 po_files = glob.glob('po/*.po') 1005 for po_file in po_files: 1006 cmd = ['msgmerge', 1007 '--update', 1008 po_file, 1009 pot_file] 1010 Logs.info('Updating ' + po_file) 1011 subprocess.call(cmd) 1012 os.chdir(pwd) 1013 1014 1015def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None): 1016 pwd = os.getcwd() 1017 os.chdir(os.path.join(srcdir, dir)) 1018 po_files = glob.glob('po/*.po') 1019 for po_file in po_files: 1020 mo_file = po_file.replace('.po', '.mo') 1021 cmd = ['msgfmt', 1022 '-c', 1023 '-f', 1024 '-o', 1025 mo_file, 1026 po_file] 1027 Logs.info('Generating ' + po_file) 1028 subprocess.call(cmd) 1029 os.chdir(pwd) 1030 1031 1032def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None): 1033 build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder) 1034 build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder) 1035 build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder) 1036 1037 1038class ExecutionEnvironment: 1039 """Context that sets system environment variables for program execution""" 1040 def __init__(self, changes): 1041 self.original_environ = os.environ.copy() 1042 1043 self.diff = {} 1044 for path_name, paths in changes.items(): 1045 value = os.pathsep.join(paths) 1046 if path_name in os.environ: 1047 value += os.pathsep + os.environ[path_name] 1048 1049 self.diff[path_name] = value 1050 1051 os.environ.update(self.diff) 1052 1053 def __str__(self): 1054 return '\n'.join(['%s="%s"' % (k, v) for k, v in self.diff.items()]) 1055 1056 def __enter__(self): 1057 return self 1058 1059 def __exit__(self, type, value, traceback): 1060 os.environ = self.original_environ 1061 1062 1063class RunContext(Build.BuildContext): 1064 "runs an executable from the build directory" 1065 cmd = 'run' 1066 1067 def execute(self): 1068 self.restore() 1069 if not self.all_envs: 1070 self.load_envs() 1071 1072 with ExecutionEnvironment(self.env.AUTOWAF_RUN_ENV) as env: 1073 if Options.options.verbose: 1074 Logs.pprint('GREEN', str(env) + '\n') 1075 1076 if Options.options.cmd: 1077 Logs.pprint('GREEN', 'Running %s' % Options.options.cmd) 1078 subprocess.call(Options.options.cmd, shell=True) 1079 else: 1080 Logs.error("error: Missing --cmd option for run command") 1081 1082 1083def show_diff(from_lines, to_lines, from_filename, to_filename): 1084 import difflib 1085 import sys 1086 1087 same = True 1088 for line in difflib.unified_diff( 1089 from_lines, to_lines, 1090 fromfile=os.path.abspath(from_filename), 1091 tofile=os.path.abspath(to_filename)): 1092 sys.stderr.write(line) 1093 same = False 1094 1095 return same 1096 1097 1098def test_file_equals(patha, pathb): 1099 import filecmp 1100 import io 1101 1102 for path in (patha, pathb): 1103 if not os.access(path, os.F_OK): 1104 Logs.pprint('RED', 'error: missing file %s' % path) 1105 return False 1106 1107 if filecmp.cmp(patha, pathb, shallow=False): 1108 return True 1109 1110 with io.open(patha, 'rU', encoding='utf-8') as fa: 1111 with io.open(pathb, 'rU', encoding='utf-8') as fb: 1112 return show_diff(fa.readlines(), fb.readlines(), patha, pathb) 1113 1114 1115def bench_time(): 1116 if hasattr(time, 'perf_counter'): # Added in Python 3.3 1117 return time.perf_counter() 1118 else: 1119 return time.time() 1120 1121 1122class TestOutput: 1123 """Test output that is truthy if result is as expected""" 1124 def __init__(self, expected, result=None): 1125 self.stdout = self.stderr = None 1126 self.expected = expected 1127 self.result = result 1128 1129 def __bool__(self): 1130 return self.expected is None or self.result == self.expected 1131 1132 __nonzero__ = __bool__ 1133 1134 1135def is_string(s): 1136 if sys.version_info[0] < 3: 1137 return isinstance(s, basestring) 1138 return isinstance(s, str) 1139 1140 1141class TestScope: 1142 """Scope for running tests that maintains pass/fail statistics""" 1143 def __init__(self, tst, name, defaults): 1144 self.tst = tst 1145 self.name = name 1146 self.defaults = defaults 1147 self.n_failed = 0 1148 self.n_total = 0 1149 1150 def run(self, test, **kwargs): 1151 if type(test) == list and 'name' not in kwargs: 1152 import pipes 1153 kwargs['name'] = ' '.join(map(pipes.quote, test)) 1154 1155 if Options.options.test_filter and 'name' in kwargs: 1156 import re 1157 found = False 1158 for scope in self.tst.stack: 1159 if re.search(Options.options.test_filter, scope.name): 1160 found = True 1161 break 1162 1163 if (not found and 1164 not re.search(Options.options.test_filter, self.name) and 1165 not re.search(Options.options.test_filter, kwargs['name'])): 1166 return True 1167 1168 if callable(test): 1169 output = self._run_callable(test, **kwargs) 1170 elif type(test) == list: 1171 output = self._run_command(test, **kwargs) 1172 else: 1173 raise Exception("Unknown test type") 1174 1175 if not output: 1176 self.tst.log_bad('FAILED', kwargs['name']) 1177 1178 return self.tst.test_result(output) 1179 1180 def _run_callable(self, test, **kwargs): 1181 expected = kwargs['expected'] if 'expected' in kwargs else True 1182 return TestOutput(expected, test()) 1183 1184 def _run_command(self, test, **kwargs): 1185 if 'stderr' in kwargs and kwargs['stderr'] == NONEMPTY: 1186 # Run with a temp file for stderr and check that it is non-empty 1187 import tempfile 1188 with tempfile.TemporaryFile() as stderr: 1189 kwargs['stderr'] = stderr 1190 output = self.run(test, **kwargs) 1191 stderr.seek(0, 2) # Seek to end 1192 return (output if not output else 1193 self.run( 1194 lambda: stderr.tell() > 0, 1195 name=kwargs['name'] + ' error message')) 1196 1197 try: 1198 # Run with stdout and stderr set to the appropriate streams 1199 out_stream = self._stream('stdout', kwargs) 1200 err_stream = self._stream('stderr', kwargs) 1201 return self._exec(test, **kwargs) 1202 finally: 1203 out_stream = out_stream.close() if out_stream else None 1204 err_stream = err_stream.close() if err_stream else None 1205 1206 def _stream(self, stream_name, kwargs): 1207 s = kwargs[stream_name] if stream_name in kwargs else None 1208 if is_string(s): 1209 kwargs[stream_name] = open(s, 'wb') 1210 return kwargs[stream_name] 1211 return None 1212 1213 def _exec(self, 1214 test, 1215 expected=0, 1216 name='', 1217 stdin=None, 1218 stdout=None, 1219 stderr=None, 1220 verbosity=1): 1221 import tempfile 1222 1223 def stream(s): 1224 return open(s, 'wb') if type(s) == str else s 1225 1226 if verbosity > 1: 1227 self.tst.log_good('RUN ', name) 1228 1229 if Options.options.wrapper: 1230 import shlex 1231 test = shlex.split(Options.options.wrapper) + test 1232 1233 output = TestOutput(expected) 1234 with open(os.devnull, 'wb') as null: 1235 out = null if verbosity < 3 and not stdout else stdout 1236 tmp_err = None 1237 if stderr or verbosity >= 2: 1238 err = stderr 1239 else: 1240 tmp_err = tempfile.TemporaryFile() 1241 err = tmp_err 1242 1243 proc = subprocess.Popen(test, stdin=stdin, stdout=out, stderr=err) 1244 output.stdout, output.stderr = proc.communicate() 1245 output.result = proc.returncode 1246 1247 if tmp_err is not None: 1248 if output.result != expected: 1249 tmp_err.seek(0) 1250 for line in tmp_err: 1251 sys.stderr.write(line.decode('utf-8')) 1252 1253 tmp_err.close() 1254 1255 if output and verbosity > 0: 1256 self.tst.log_good(' OK', name) 1257 1258 return output 1259 1260 1261class TestContext(Build.BuildContext): 1262 "runs test suite" 1263 fun = cmd = 'test' 1264 1265 def __init__(self, **kwargs): 1266 super(TestContext, self).__init__(**kwargs) 1267 self.start_time = bench_time() 1268 self.max_depth = 1 1269 1270 defaults = {'verbosity': Options.options.verbose} 1271 self.stack = [TestScope(self, Context.g_module.APPNAME, defaults)] 1272 1273 def defaults(self): 1274 return self.stack[-1].defaults 1275 1276 def finalize(self): 1277 if self.stack[-1].n_failed > 0: 1278 sys.exit(1) 1279 1280 super(TestContext, self).finalize() 1281 1282 def __call__(self, test, **kwargs): 1283 return self.stack[-1].run(test, **self.args(**kwargs)) 1284 1285 def file_equals(self, from_path, to_path, **kwargs): 1286 kwargs.update({'expected': True, 1287 'name': '%s == %s' % (from_path, to_path)}) 1288 return self(lambda: test_file_equals(from_path, to_path), **kwargs) 1289 1290 def log_good(self, title, fmt, *args): 1291 Logs.pprint('GREEN', '[%s] %s' % (title.center(10), fmt % args)) 1292 1293 def log_bad(self, title, fmt, *args): 1294 Logs.pprint('RED', '[%s] %s' % (title.center(10), fmt % args)) 1295 1296 def pre_recurse(self, node): 1297 wscript_module = Context.load_module(node.abspath()) 1298 group_name = wscript_module.APPNAME 1299 self.stack.append(TestScope(self, group_name, self.defaults())) 1300 self.max_depth = max(self.max_depth, len(self.stack) - 1) 1301 1302 bld_dir = node.get_bld().parent 1303 1304 if hasattr(wscript_module, 'test'): 1305 self.original_dir = os.getcwd() 1306 Logs.info("Waf: Entering directory `%s'", bld_dir) 1307 os.chdir(str(bld_dir)) 1308 1309 parent_is_top = str(node.parent) == Context.top_dir 1310 if not self.env.NO_COVERAGE and parent_is_top: 1311 self.clear_coverage() 1312 1313 Logs.info('') 1314 self.log_good('=' * 10, 'Running %s tests\n', group_name) 1315 1316 super(TestContext, self).pre_recurse(node) 1317 1318 def test_result(self, success): 1319 self.stack[-1].n_total += 1 1320 self.stack[-1].n_failed += 1 if not success else 0 1321 return success 1322 1323 def pop(self): 1324 scope = self.stack.pop() 1325 self.stack[-1].n_total += scope.n_total 1326 self.stack[-1].n_failed += scope.n_failed 1327 return scope 1328 1329 def post_recurse(self, node): 1330 super(TestContext, self).post_recurse(node) 1331 1332 scope = self.pop() 1333 duration = (bench_time() - self.start_time) * 1000.0 1334 is_top = str(node.parent) == str(Context.top_dir) 1335 1336 wscript_module = Context.load_module(node.abspath()) 1337 if not hasattr(wscript_module, 'test'): 1338 os.chdir(self.original_dir) 1339 return 1340 1341 Logs.info('') 1342 self.log_good('=' * 10, '%d tests from %s ran (%d ms total)', 1343 scope.n_total, scope.name, duration) 1344 1345 if not self.env.NO_COVERAGE: 1346 if is_top: 1347 self.gen_coverage() 1348 1349 if os.path.exists('coverage/index.html'): 1350 self.log_good('REPORT', '<file://%s>', 1351 os.path.abspath('coverage/index.html')) 1352 1353 successes = scope.n_total - scope.n_failed 1354 Logs.pprint('GREEN', '[ PASSED ] %d tests' % successes) 1355 if scope.n_failed > 0: 1356 Logs.pprint('RED', '[ FAILED ] %d tests' % scope.n_failed) 1357 1358 Logs.info("\nWaf: Leaving directory `%s'" % os.getcwd()) 1359 os.chdir(self.original_dir) 1360 1361 def execute(self): 1362 self.restore() 1363 if not self.all_envs: 1364 self.load_envs() 1365 1366 if not self.env.BUILD_TESTS: 1367 self.fatal('Configuration does not include tests') 1368 1369 with ExecutionEnvironment(self.env.AUTOWAF_RUN_ENV) as env: 1370 if self.defaults()['verbosity'] > 0: 1371 Logs.pprint('GREEN', str(env) + '\n') 1372 self.recurse([self.run_dir]) 1373 1374 def src_path(self, path): 1375 return os.path.relpath(os.path.join(str(self.path), path)) 1376 1377 def args(self, **kwargs): 1378 all_kwargs = self.defaults().copy() 1379 all_kwargs.update(kwargs) 1380 return all_kwargs 1381 1382 def group(self, name, **kwargs): 1383 return TestGroup( 1384 self, self.stack[-1].name, name, **self.args(**kwargs)) 1385 1386 def set_test_defaults(self, **kwargs): 1387 """Set default arguments to be passed to all tests""" 1388 self.stack[-1].defaults.update(kwargs) 1389 1390 def clear_coverage(self): 1391 """Zero old coverage data""" 1392 try: 1393 with open('cov-clear.log', 'w') as log: 1394 subprocess.call(['lcov', '-z', '-d', str(self.path)], 1395 stdout=log, stderr=log) 1396 1397 except Exception as e: 1398 Logs.warn('Failed to run lcov to clear old coverage data (%s)' % e) 1399 1400 def gen_coverage(self): 1401 """Generate coverage data and report""" 1402 try: 1403 with open('cov.lcov', 'w') as out: 1404 with open('cov.log', 'w') as err: 1405 subprocess.call(['lcov', '-c', '--no-external', 1406 '--rc', 'lcov_branch_coverage=1', 1407 '-b', '.', 1408 '-d', str(self.path)], 1409 stdout=out, stderr=err) 1410 1411 if not os.path.isdir('coverage'): 1412 os.makedirs('coverage') 1413 1414 with open('genhtml.log', 'w') as log: 1415 subprocess.call(['genhtml', 1416 '-o', 'coverage', 1417 '--rc', 'genhtml_branch_coverage=1', 1418 'cov.lcov'], 1419 stdout=log, stderr=log) 1420 1421 summary = subprocess.check_output( 1422 ['lcov', '--summary', 1423 '--rc', 'lcov_branch_coverage=1', 1424 'cov.lcov'], 1425 stderr=subprocess.STDOUT).decode('ascii') 1426 1427 import re 1428 lines = re.search(r'lines\.*: (.*)%.*', summary).group(1) 1429 functions = re.search(r'functions\.*: (.*)%.*', summary).group(1) 1430 branches = re.search(r'branches\.*: (.*)%.*', summary).group(1) 1431 self.log_good( 1432 'COVERAGE', '%s%% lines, %s%% functions, %s%% branches', 1433 lines, functions, branches) 1434 1435 except Exception as e: 1436 Logs.warn('Failed to run lcov to generate coverage report (%s)') 1437 1438 1439class TestGroup: 1440 def __init__(self, tst, suitename, name, **kwargs): 1441 self.tst = tst 1442 self.suitename = suitename 1443 self.name = name 1444 self.kwargs = kwargs 1445 self.start_time = bench_time() 1446 tst.stack.append(TestScope(tst, name, tst.defaults())) 1447 1448 def label(self): 1449 return self.suitename + '.%s' % self.name if self.name else '' 1450 1451 def args(self, **kwargs): 1452 all_kwargs = self.tst.args(**self.kwargs) 1453 all_kwargs.update(kwargs) 1454 return all_kwargs 1455 1456 def __enter__(self): 1457 if 'verbosity' in self.kwargs and self.kwargs['verbosity'] > 0: 1458 self.tst.log_good('-' * 10, self.label()) 1459 return self 1460 1461 def __call__(self, test, **kwargs): 1462 return self.tst(test, **self.args(**kwargs)) 1463 1464 def file_equals(self, from_path, to_path, **kwargs): 1465 return self.tst.file_equals(from_path, to_path, **kwargs) 1466 1467 def __exit__(self, type, value, traceback): 1468 duration = (bench_time() - self.start_time) * 1000.0 1469 scope = self.tst.pop() 1470 n_passed = scope.n_total - scope.n_failed 1471 if scope.n_failed == 0: 1472 self.tst.log_good('-' * 10, '%d tests from %s (%d ms total)', 1473 scope.n_total, self.label(), duration) 1474 else: 1475 self.tst.log_bad('-' * 10, '%d/%d tests from %s (%d ms total)', 1476 n_passed, scope.n_total, self.label(), duration) 1477 1478 1479def run_ldconfig(ctx): 1480 should_run = (ctx.cmd == 'install' and 1481 not ctx.env['RAN_LDCONFIG'] and 1482 ctx.env['LIBDIR'] and 1483 'DESTDIR' not in os.environ and 1484 not Options.options.destdir) 1485 1486 if should_run: 1487 try: 1488 Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR']) 1489 subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']]) 1490 ctx.env['RAN_LDCONFIG'] = True 1491 except Exception: 1492 pass 1493 1494 1495def run_script(cmds): 1496 for cmd in cmds: 1497 subprocess.check_call(cmd, shell=True) 1498