diff options
author | David Robillard <d@drobilla.net> | 2018-11-24 13:44:02 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2018-11-24 13:44:02 +0100 |
commit | d74de7f575a9ec49f96138c3c5251f28946c0c0e (patch) | |
tree | d9b620bfba1e7462df4ddb3f6225cc5216c0ca81 /Tools/c_config.py | |
parent | c6e874c2cc1c5c99a3859112e1bba0f07bcdb8ba (diff) | |
download | ganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.tar.gz ganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.tar.bz2 ganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.zip |
Squashed 'waflib/' changes from 6e726eb..5ea8f99
5ea8f99 Improve test output spacing
0e23b29 Raise exception when test suite fails to ensure non-zero exit status
d6de073 Show run time of unit tests
5b65554 Add short configure option for ultra-strict flags
4687ba6 Use gtest-like test output
258903d Fix failure count in test group summaries
da07e73 Fix verbose tests with Python 3
git-subtree-dir: waflib
git-subtree-split: 5ea8f99f6e1246079c1fe6bb590c38a53aadd40d
Diffstat (limited to 'Tools/c_config.py')
-rw-r--r-- | Tools/c_config.py | 1351 |
1 files changed, 1351 insertions, 0 deletions
diff --git a/Tools/c_config.py b/Tools/c_config.py new file mode 100644 index 0000000..d2b3c0d --- /dev/null +++ b/Tools/c_config.py @@ -0,0 +1,1351 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2018 (ita) + +""" +C/C++/D configuration helpers +""" + +from __future__ import with_statement + +import os, re, shlex +from waflib import Build, Utils, Task, Options, Logs, Errors, Runner +from waflib.TaskGen import after_method, feature +from waflib.Configure import conf + +WAF_CONFIG_H = 'config.h' +"""default name for the config.h file""" + +DEFKEYS = 'define_key' +INCKEYS = 'include_key' + +SNIP_EMPTY_PROGRAM = ''' +int main(int argc, char **argv) { + (void)argc; (void)argv; + return 0; +} +''' + +MACRO_TO_DESTOS = { +'__linux__' : 'linux', +'__GNU__' : 'gnu', # hurd +'__FreeBSD__' : 'freebsd', +'__NetBSD__' : 'netbsd', +'__OpenBSD__' : 'openbsd', +'__sun' : 'sunos', +'__hpux' : 'hpux', +'__sgi' : 'irix', +'_AIX' : 'aix', +'__CYGWIN__' : 'cygwin', +'__MSYS__' : 'cygwin', +'_UWIN' : 'uwin', +'_WIN64' : 'win32', +'_WIN32' : 'win32', +# Note about darwin: this is also tested with 'defined __APPLE__ && defined __MACH__' somewhere below in this file. +'__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' : 'darwin', +'__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' : 'darwin', # iphone +'__QNX__' : 'qnx', +'__native_client__' : 'nacl' # google native client platform +} + +MACRO_TO_DEST_CPU = { +'__x86_64__' : 'x86_64', +'__amd64__' : 'x86_64', +'__i386__' : 'x86', +'__ia64__' : 'ia', +'__mips__' : 'mips', +'__sparc__' : 'sparc', +'__alpha__' : 'alpha', +'__aarch64__' : 'aarch64', +'__thumb__' : 'thumb', +'__arm__' : 'arm', +'__hppa__' : 'hppa', +'__powerpc__' : 'powerpc', +'__ppc__' : 'powerpc', +'__convex__' : 'convex', +'__m68k__' : 'm68k', +'__s390x__' : 's390x', +'__s390__' : 's390', +'__sh__' : 'sh', +'__xtensa__' : 'xtensa', +} + +@conf +def parse_flags(self, line, uselib_store, env=None, force_static=False, posix=None): + """ + Parses flags from the input lines, and adds them to the relevant use variables:: + + def configure(conf): + conf.parse_flags('-O3', 'FOO') + # conf.env.CXXFLAGS_FOO = ['-O3'] + # conf.env.CFLAGS_FOO = ['-O3'] + + :param line: flags + :type line: string + :param uselib_store: where to add the flags + :type uselib_store: string + :param env: config set or conf.env by default + :type env: :py:class:`waflib.ConfigSet.ConfigSet` + """ + + assert(isinstance(line, str)) + + env = env or self.env + + # Issue 811 and 1371 + if posix is None: + posix = True + if '\\' in line: + posix = ('\\ ' in line) or ('\\\\' in line) + + lex = shlex.shlex(line, posix=posix) + lex.whitespace_split = True + lex.commenters = '' + lst = list(lex) + + # append_unique is not always possible + # for example, apple flags may require both -arch i386 and -arch ppc + uselib = uselib_store + def app(var, val): + env.append_value('%s_%s' % (var, uselib), val) + def appu(var, val): + env.append_unique('%s_%s' % (var, uselib), val) + static = False + while lst: + x = lst.pop(0) + st = x[:2] + ot = x[2:] + + if st == '-I' or st == '/I': + if not ot: + ot = lst.pop(0) + appu('INCLUDES', ot) + elif st == '-i': + tmp = [x, lst.pop(0)] + app('CFLAGS', tmp) + app('CXXFLAGS', tmp) + elif st == '-D' or (env.CXX_NAME == 'msvc' and st == '/D'): # not perfect but.. + if not ot: + ot = lst.pop(0) + app('DEFINES', ot) + elif st == '-l': + if not ot: + ot = lst.pop(0) + prefix = 'STLIB' if (force_static or static) else 'LIB' + app(prefix, ot) + elif st == '-L': + if not ot: + ot = lst.pop(0) + prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH' + appu(prefix, ot) + elif x.startswith('/LIBPATH:'): + prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH' + appu(prefix, x.replace('/LIBPATH:', '')) + elif x.startswith('-std='): + prefix = 'CXXFLAGS' if '++' in x else 'CFLAGS' + app(prefix, x) + elif x.startswith('+') or x in ('-pthread', '-fPIC', '-fpic', '-fPIE', '-fpie'): + app('CFLAGS', x) + app('CXXFLAGS', x) + app('LINKFLAGS', x) + elif x == '-framework': + appu('FRAMEWORK', lst.pop(0)) + elif x.startswith('-F'): + appu('FRAMEWORKPATH', x[2:]) + elif x == '-Wl,-rpath' or x == '-Wl,-R': + app('RPATH', lst.pop(0).lstrip('-Wl,')) + elif x.startswith('-Wl,-R,'): + app('RPATH', x[7:]) + elif x.startswith('-Wl,-R'): + app('RPATH', x[6:]) + elif x.startswith('-Wl,-rpath,'): + app('RPATH', x[11:]) + elif x == '-Wl,-Bstatic' or x == '-Bstatic': + static = True + elif x == '-Wl,-Bdynamic' or x == '-Bdynamic': + static = False + elif x.startswith('-Wl') or x in ('-rdynamic', '-pie'): + app('LINKFLAGS', x) + elif x.startswith(('-m', '-f', '-dynamic', '-O', '-g')): + # Adding the -W option breaks python builds on Openindiana + app('CFLAGS', x) + app('CXXFLAGS', x) + elif x.startswith('-bundle'): + app('LINKFLAGS', x) + elif x.startswith(('-undefined', '-Xlinker')): + arg = lst.pop(0) + app('LINKFLAGS', [x, arg]) + elif x.startswith(('-arch', '-isysroot')): + tmp = [x, lst.pop(0)] + app('CFLAGS', tmp) + app('CXXFLAGS', tmp) + app('LINKFLAGS', tmp) + elif x.endswith(('.a', '.so', '.dylib', '.lib')): + appu('LINKFLAGS', x) # not cool, #762 + else: + self.to_log('Unhandled flag %r' % x) + +@conf +def validate_cfg(self, kw): + """ + Searches for the program *pkg-config* if missing, and validates the + parameters to pass to :py:func:`waflib.Tools.c_config.exec_cfg`. + + :param path: the **-config program to use** (default is *pkg-config*) + :type path: list of string + :param msg: message to display to describe the test executed + :type msg: string + :param okmsg: message to display when the test is successful + :type okmsg: string + :param errmsg: message to display in case of error + :type errmsg: string + """ + if not 'path' in kw: + if not self.env.PKGCONFIG: + self.find_program('pkg-config', var='PKGCONFIG') + kw['path'] = self.env.PKGCONFIG + + # verify that exactly one action is requested + s = ('atleast_pkgconfig_version' in kw) + ('modversion' in kw) + ('package' in kw) + if s != 1: + raise ValueError('exactly one of atleast_pkgconfig_version, modversion and package must be set') + if not 'msg' in kw: + if 'atleast_pkgconfig_version' in kw: + kw['msg'] = 'Checking for pkg-config version >= %r' % kw['atleast_pkgconfig_version'] + elif 'modversion' in kw: + kw['msg'] = 'Checking for %r version' % kw['modversion'] + else: + kw['msg'] = 'Checking for %r' %(kw['package']) + + # let the modversion check set the okmsg to the detected version + if not 'okmsg' in kw and not 'modversion' in kw: + kw['okmsg'] = 'yes' + if not 'errmsg' in kw: + kw['errmsg'] = 'not found' + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + pass + elif 'modversion' in kw: + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['modversion'] + if not 'define_name' in kw: + kw['define_name'] = '%s_VERSION' % Utils.quote_define_name(kw['uselib_store']) + else: + if not 'uselib_store' in kw: + kw['uselib_store'] = Utils.to_list(kw['package'])[0].upper() + if not 'define_name' in kw: + kw['define_name'] = self.have_define(kw['uselib_store']) + +@conf +def exec_cfg(self, kw): + """ + Executes ``pkg-config`` or other ``-config`` applications to collect configuration flags: + + * if atleast_pkgconfig_version is given, check that pkg-config has the version n and return + * if modversion is given, then return the module version + * else, execute the *-config* program with the *args* and *variables* given, and set the flags on the *conf.env.FLAGS_name* variable + + :param atleast_pkgconfig_version: minimum pkg-config version to use (disable other tests) + :type atleast_pkgconfig_version: string + :param package: package name, for example *gtk+-2.0* + :type package: string + :param uselib_store: if the test is successful, define HAVE\_*name*. It is also used to define *conf.env.FLAGS_name* variables. + :type uselib_store: string + :param modversion: if provided, return the version of the given module and define *name*\_VERSION + :type modversion: string + :param args: arguments to give to *package* when retrieving flags + :type args: list of string + :param variables: return the values of particular variables + :type variables: list of string + :param define_variable: additional variables to define (also in conf.env.PKG_CONFIG_DEFINES) + :type define_variable: dict(string: string) + """ + + path = Utils.to_list(kw['path']) + env = self.env.env or None + if kw.get('pkg_config_path'): + if not env: + env = dict(self.environ) + env['PKG_CONFIG_PATH'] = kw['pkg_config_path'] + + def define_it(): + define_name = kw['define_name'] + # by default, add HAVE_X to the config.h, else provide DEFINES_X for use=X + if kw.get('global_define', 1): + self.define(define_name, 1, False) + else: + self.env.append_unique('DEFINES_%s' % kw['uselib_store'], "%s=1" % define_name) + + if kw.get('add_have_to_env', 1): + self.env[define_name] = 1 + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + cmd = path + ['--atleast-pkgconfig-version=%s' % kw['atleast_pkgconfig_version']] + self.cmd_and_log(cmd, env=env) + return + + # single version for a module + if 'modversion' in kw: + version = self.cmd_and_log(path + ['--modversion', kw['modversion']], env=env).strip() + if not 'okmsg' in kw: + kw['okmsg'] = version + self.define(kw['define_name'], version) + return version + + lst = [] + path + + defi = kw.get('define_variable') + if not defi: + defi = self.env.PKG_CONFIG_DEFINES or {} + for key, val in defi.items(): + lst.append('--define-variable=%s=%s' % (key, val)) + + static = kw.get('force_static', False) + if 'args' in kw: + args = Utils.to_list(kw['args']) + if '--static' in args or '--static-libs' in args: + static = True + lst += args + + # tools like pkgconf expect the package argument after the -- ones -_- + lst.extend(Utils.to_list(kw['package'])) + + # retrieving variables of a module + if 'variables' in kw: + v_env = kw.get('env', self.env) + vars = Utils.to_list(kw['variables']) + for v in vars: + val = self.cmd_and_log(lst + ['--variable=' + v], env=env).strip() + var = '%s_%s' % (kw['uselib_store'], v) + v_env[var] = val + return + + # so we assume the command-line will output flags to be parsed afterwards + ret = self.cmd_and_log(lst, env=env) + + define_it() + self.parse_flags(ret, kw['uselib_store'], kw.get('env', self.env), force_static=static, posix=kw.get('posix')) + return ret + +@conf +def check_cfg(self, *k, **kw): + """ + Checks for configuration flags using a **-config**-like program (pkg-config, sdl-config, etc). + This wraps internal calls to :py:func:`waflib.Tools.c_config.validate_cfg` and :py:func:`waflib.Tools.c_config.exec_cfg` + + A few examples:: + + def configure(conf): + conf.load('compiler_c') + conf.check_cfg(package='glib-2.0', args='--libs --cflags') + conf.check_cfg(package='pango') + conf.check_cfg(package='pango', uselib_store='MYPANGO', args=['--cflags', '--libs']) + conf.check_cfg(package='pango', + args=['pango >= 0.1.0', 'pango < 9.9.9', '--cflags', '--libs'], + msg="Checking for 'pango 0.1.0'") + conf.check_cfg(path='sdl-config', args='--cflags --libs', package='', uselib_store='SDL') + conf.check_cfg(path='mpicc', args='--showme:compile --showme:link', + package='', uselib_store='OPEN_MPI', mandatory=False) + # variables + conf.check_cfg(package='gtk+-2.0', variables=['includedir', 'prefix'], uselib_store='FOO') + print(conf.env.FOO_includedir) + """ + self.validate_cfg(kw) + if 'msg' in kw: + self.start_msg(kw['msg'], **kw) + ret = None + try: + ret = self.exec_cfg(kw) + except self.errors.WafError as e: + if 'errmsg' in kw: + self.end_msg(kw['errmsg'], 'YELLOW', **kw) + if Logs.verbose > 1: + self.to_log('Command failure: %s' % e) + self.fatal('The configuration failed') + else: + if not ret: + ret = True + kw['success'] = ret + if 'okmsg' in kw: + self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw) + + return ret + +def build_fun(bld): + """ + Build function that is used for running configuration tests with ``conf.check()`` + """ + if bld.kw['compile_filename']: + node = bld.srcnode.make_node(bld.kw['compile_filename']) + node.write(bld.kw['code']) + + o = bld(features=bld.kw['features'], source=bld.kw['compile_filename'], target='testprog') + + for k, v in bld.kw.items(): + setattr(o, k, v) + + if not bld.kw.get('quiet'): + bld.conf.to_log("==>\n%s\n<==" % bld.kw['code']) + +@conf +def validate_c(self, kw): + """ + Pre-checks the parameters that will be given to :py:func:`waflib.Configure.run_build` + + :param compiler: c or cxx (tries to guess what is best) + :type compiler: string + :param type: cprogram, cshlib, cstlib - not required if *features are given directly* + :type type: binary to create + :param feature: desired features for the task generator that will execute the test, for example ``cxx cxxstlib`` + :type feature: list of string + :param fragment: provide a piece of code for the test (default is to let the system create one) + :type fragment: string + :param uselib_store: define variables after the test is executed (IMPORTANT!) + :type uselib_store: string + :param use: parameters to use for building (just like the normal *use* keyword) + :type use: list of string + :param define_name: define to set when the check is over + :type define_name: string + :param execute: execute the resulting binary + :type execute: bool + :param define_ret: if execute is set to True, use the execution output in both the define and the return value + :type define_ret: bool + :param header_name: check for a particular header + :type header_name: string + :param auto_add_header_name: if header_name was set, add the headers in env.INCKEYS so the next tests will include these headers + :type auto_add_header_name: bool + """ + for x in ('type_name', 'field_name', 'function_name'): + if x in kw: + Logs.warn('Invalid argument %r in test' % x) + + if not 'build_fun' in kw: + kw['build_fun'] = build_fun + + if not 'env' in kw: + kw['env'] = self.env.derive() + env = kw['env'] + + if not 'compiler' in kw and not 'features' in kw: + kw['compiler'] = 'c' + if env.CXX_NAME and Task.classes.get('cxx'): + kw['compiler'] = 'cxx' + if not self.env.CXX: + self.fatal('a c++ compiler is required') + else: + if not self.env.CC: + self.fatal('a c compiler is required') + + if not 'compile_mode' in kw: + kw['compile_mode'] = 'c' + if 'cxx' in Utils.to_list(kw.get('features', [])) or kw.get('compiler') == 'cxx': + kw['compile_mode'] = 'cxx' + + if not 'type' in kw: + kw['type'] = 'cprogram' + + if not 'features' in kw: + if not 'header_name' in kw or kw.get('link_header_test', True): + kw['features'] = [kw['compile_mode'], kw['type']] # "c ccprogram" + else: + kw['features'] = [kw['compile_mode']] + else: + kw['features'] = Utils.to_list(kw['features']) + + if not 'compile_filename' in kw: + kw['compile_filename'] = 'test.c' + ((kw['compile_mode'] == 'cxx') and 'pp' or '') + + def to_header(dct): + if 'header_name' in dct: + dct = Utils.to_list(dct['header_name']) + return ''.join(['#include <%s>\n' % x for x in dct]) + return '' + + if 'framework_name' in kw: + # OSX, not sure this is used anywhere + fwkname = kw['framework_name'] + if not 'uselib_store' in kw: + kw['uselib_store'] = fwkname.upper() + if not kw.get('no_header'): + fwk = '%s/%s.h' % (fwkname, fwkname) + if kw.get('remove_dot_h'): + fwk = fwk[:-2] + val = kw.get('header_name', []) + kw['header_name'] = Utils.to_list(val) + [fwk] + kw['msg'] = 'Checking for framework %s' % fwkname + kw['framework'] = fwkname + + elif 'header_name' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for header %s' % kw['header_name'] + + l = Utils.to_list(kw['header_name']) + assert len(l), 'list of headers in header_name is empty' + + kw['code'] = to_header(kw) + SNIP_EMPTY_PROGRAM + if not 'uselib_store' in kw: + kw['uselib_store'] = l[0].upper() + if not 'define_name' in kw: + kw['define_name'] = self.have_define(l[0]) + + if 'lib' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for library %s' % kw['lib'] + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['lib'].upper() + + if 'stlib' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for static library %s' % kw['stlib'] + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['stlib'].upper() + + if 'fragment' in kw: + # an additional code fragment may be provided to replace the predefined code + # in custom headers + kw['code'] = kw['fragment'] + if not 'msg' in kw: + kw['msg'] = 'Checking for code snippet' + if not 'errmsg' in kw: + kw['errmsg'] = 'no' + + for (flagsname,flagstype) in (('cxxflags','compiler'), ('cflags','compiler'), ('linkflags','linker')): + if flagsname in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for %s flags %s' % (flagstype, kw[flagsname]) + if not 'errmsg' in kw: + kw['errmsg'] = 'no' + + if not 'execute' in kw: + kw['execute'] = False + if kw['execute']: + kw['features'].append('test_exec') + kw['chmod'] = Utils.O755 + + if not 'errmsg' in kw: + kw['errmsg'] = 'not found' + + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + + if not 'code' in kw: + kw['code'] = SNIP_EMPTY_PROGRAM + + # if there are headers to append automatically to the next tests + if self.env[INCKEYS]: + kw['code'] = '\n'.join(['#include <%s>' % x for x in self.env[INCKEYS]]) + '\n' + kw['code'] + + # in case defines lead to very long command-lines + if kw.get('merge_config_header') or env.merge_config_header: + kw['code'] = '%s\n\n%s' % (self.get_config_header(), kw['code']) + env.DEFINES = [] # modify the copy + + if not kw.get('success'): + kw['success'] = None + + if 'define_name' in kw: + self.undefine(kw['define_name']) + if not 'msg' in kw: + self.fatal('missing "msg" in conf.check(...)') + +@conf +def post_check(self, *k, **kw): + """ + Sets the variables after a test executed in + :py:func:`waflib.Tools.c_config.check` was run successfully + """ + is_success = 0 + if kw['execute']: + if kw['success'] is not None: + if kw.get('define_ret'): + is_success = kw['success'] + else: + is_success = (kw['success'] == 0) + else: + is_success = (kw['success'] == 0) + + if kw.get('define_name'): + comment = kw.get('comment', '') + define_name = kw['define_name'] + if kw['execute'] and kw.get('define_ret') and isinstance(is_success, str): + if kw.get('global_define', 1): + self.define(define_name, is_success, quote=kw.get('quote', 1), comment=comment) + else: + if kw.get('quote', 1): + succ = '"%s"' % is_success + else: + succ = int(is_success) + val = '%s=%s' % (define_name, succ) + var = 'DEFINES_%s' % kw['uselib_store'] + self.env.append_value(var, val) + else: + if kw.get('global_define', 1): + self.define_cond(define_name, is_success, comment=comment) + else: + var = 'DEFINES_%s' % kw['uselib_store'] + self.env.append_value(var, '%s=%s' % (define_name, int(is_success))) + + # define conf.env.HAVE_X to 1 + if kw.get('add_have_to_env', 1): + if kw.get('uselib_store'): + self.env[self.have_define(kw['uselib_store'])] = 1 + elif kw['execute'] and kw.get('define_ret'): + self.env[define_name] = is_success + else: + self.env[define_name] = int(is_success) + + if 'header_name' in kw: + if kw.get('auto_add_header_name'): + self.env.append_value(INCKEYS, Utils.to_list(kw['header_name'])) + + if is_success and 'uselib_store' in kw: + from waflib.Tools import ccroot + # See get_uselib_vars in ccroot.py + _vars = set() + for x in kw['features']: + if x in ccroot.USELIB_VARS: + _vars |= ccroot.USELIB_VARS[x] + + for k in _vars: + x = k.lower() + if x in kw: + self.env.append_value(k + '_' + kw['uselib_store'], kw[x]) + return is_success + +@conf +def check(self, *k, **kw): + """ + Performs a configuration test by calling :py:func:`waflib.Configure.run_build`. + For the complete list of parameters, see :py:func:`waflib.Tools.c_config.validate_c`. + To force a specific compiler, pass ``compiler='c'`` or ``compiler='cxx'`` to the list of arguments + + Besides build targets, complete builds can be given through a build function. All files will + be written to a temporary directory:: + + def build(bld): + lib_node = bld.srcnode.make_node('libdir/liblc1.c') + lib_node.parent.mkdir() + lib_node.write('#include <stdio.h>\\nint lib_func(void) { FILE *f = fopen("foo", "r");}\\n', 'w') + bld(features='c cshlib', source=[lib_node], linkflags=conf.env.EXTRA_LDFLAGS, target='liblc') + conf.check(build_fun=build, msg=msg) + """ + self.validate_c(kw) + self.start_msg(kw['msg'], **kw) + ret = None + try: + ret = self.run_build(*k, **kw) + except self.errors.ConfigurationError: + self.end_msg(kw['errmsg'], 'YELLOW', **kw) + if Logs.verbose > 1: + raise + else: + self.fatal('The configuration failed') + else: + kw['success'] = ret + + ret = self.post_check(*k, **kw) + if not ret: + self.end_msg(kw['errmsg'], 'YELLOW', **kw) + self.fatal('The configuration failed %r' % ret) + else: + self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw) + return ret + +class test_exec(Task.Task): + """ + A task that runs programs after they are built. See :py:func:`waflib.Tools.c_config.test_exec_fun`. + """ + color = 'PINK' + def run(self): + if getattr(self.generator, 'rpath', None): + if getattr(self.generator, 'define_ret', False): + self.generator.bld.retval = self.generator.bld.cmd_and_log([self.inputs[0].abspath()]) + else: + self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()]) + else: + env = self.env.env or {} + env.update(dict(os.environ)) + for var in ('LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'PATH'): + env[var] = self.inputs[0].parent.abspath() + os.path.pathsep + env.get(var, '') + if getattr(self.generator, 'define_ret', False): + self.generator.bld.retval = self.generator.bld.cmd_and_log([self.inputs[0].abspath()], env=env) + else: + self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()], env=env) + +@feature('test_exec') +@after_method('apply_link') +def test_exec_fun(self): + """ + The feature **test_exec** is used to create a task that will to execute the binary + created (link task output) during the build. The exit status will be set + on the build context, so only one program may have the feature *test_exec*. + This is used by configuration tests:: + + def configure(conf): + conf.check(execute=True) + """ + self.create_task('test_exec', self.link_task.outputs[0]) + +@conf +def check_cxx(self, *k, **kw): + """ + Runs a test with a task generator of the form:: + + conf.check(features='cxx cxxprogram', ...) + """ + kw['compiler'] = 'cxx' + return self.check(*k, **kw) + +@conf +def check_cc(self, *k, **kw): + """ + Runs a test with a task generator of the form:: + + conf.check(features='c cprogram', ...) + """ + kw['compiler'] = 'c' + return self.check(*k, **kw) + +@conf +def set_define_comment(self, key, comment): + """ + Sets a comment that will appear in the configuration header + + :type key: string + :type comment: string + """ + coms = self.env.DEFINE_COMMENTS + if not coms: + coms = self.env.DEFINE_COMMENTS = {} + coms[key] = comment or '' + +@conf +def get_define_comment(self, key): + """ + Returns the comment associated to a define + + :type key: string + """ + coms = self.env.DEFINE_COMMENTS or {} + return coms.get(key, '') + +@conf +def define(self, key, val, quote=True, comment=''): + """ + Stores a single define and its state into ``conf.env.DEFINES``. The value is cast to an integer (0/1). + + :param key: define name + :type key: string + :param val: value + :type val: int or string + :param quote: enclose strings in quotes (yes by default) + :type quote: bool + """ + assert isinstance(key, str) + if not key: + return + if val is True: + val = 1 + elif val in (False, None): + val = 0 + + if isinstance(val, int) or isinstance(val, float): + s = '%s=%s' + else: + s = quote and '%s="%s"' or '%s=%s' + app = s % (key, str(val)) + + ban = key + '=' + lst = self.env.DEFINES + for x in lst: + if x.startswith(ban): + lst[lst.index(x)] = app + break + else: + self.env.append_value('DEFINES', app) + + self.env.append_unique(DEFKEYS, key) + self.set_define_comment(key, comment) + +@conf +def undefine(self, key, comment=''): + """ + Removes a global define from ``conf.env.DEFINES`` + + :param key: define name + :type key: string + """ + assert isinstance(key, str) + if not key: + return + ban = key + '=' + lst = [x for x in self.env.DEFINES if not x.startswith(ban)] + self.env.DEFINES = lst + self.env.append_unique(DEFKEYS, key) + self.set_define_comment(key, comment) + +@conf +def define_cond(self, key, val, comment=''): + """ + Conditionally defines a name:: + + def configure(conf): + conf.define_cond('A', True) + # equivalent to: + # if val: conf.define('A', 1) + # else: conf.undefine('A') + + :param key: define name + :type key: string + :param val: value + :type val: int or string + """ + assert isinstance(key, str) + if not key: + return + if val: + self.define(key, 1, comment=comment) + else: + self.undefine(key, comment=comment) + +@conf +def is_defined(self, key): + """ + Indicates whether a particular define is globally set in ``conf.env.DEFINES``. + + :param key: define name + :type key: string + :return: True if the define is set + :rtype: bool + """ + assert key and isinstance(key, str) + + ban = key + '=' + for x in self.env.DEFINES: + if x.startswith(ban): + return True + return False + +@conf +def get_define(self, key): + """ + Returns the value of an existing define, or None if not found + + :param key: define name + :type key: string + :rtype: string + """ + assert key and isinstance(key, str) + + ban = key + '=' + for x in self.env.DEFINES: + if x.startswith(ban): + return x[len(ban):] + return None + +@conf +def have_define(self, key): + """ + Returns a variable suitable for command-line or header use by removing invalid characters + and prefixing it with ``HAVE_`` + + :param key: define name + :type key: string + :return: the input key prefixed by *HAVE_* and substitute any invalid characters. + :rtype: string + """ + return (self.env.HAVE_PAT or 'HAVE_%s') % Utils.quote_define_name(key) + +@conf +def write_config_header(self, configfile='', guard='', top=False, defines=True, headers=False, remove=True, define_prefix=''): + """ + Writes a configuration header containing defines and includes:: + + def configure(cnf): + cnf.define('A', 1) + cnf.write_config_header('config.h') + + This function only adds include guards (if necessary), consult + :py:func:`waflib.Tools.c_config.get_config_header` for details on the body. + + :param configfile: path to the file to create (relative or absolute) + :type configfile: string + :param guard: include guard name to add, by default it is computed from the file name + :type guard: string + :param top: write the configuration header from the build directory (default is from the current path) + :type top: bool + :param defines: add the defines (yes by default) + :type defines: bool + :param headers: add #include in the file + :type headers: bool + :param remove: remove the defines after they are added (yes by default, works like in autoconf) + :type remove: bool + :type define_prefix: string + :param define_prefix: prefix all the defines in the file with a particular prefix + """ + if not configfile: + configfile = WAF_CONFIG_H + waf_guard = guard or 'W_%s_WAF' % Utils.quote_define_name(configfile) + + node = top and self.bldnode or self.path.get_bld() + node = node.make_node(configfile) + node.parent.mkdir() + + lst = ['/* WARNING! All changes made to this file will be lost! */\n'] + lst.append('#ifndef %s\n#define %s\n' % (waf_guard, waf_guard)) + lst.append(self.get_config_header(defines, headers, define_prefix=define_prefix)) + lst.append('\n#endif /* %s */\n' % waf_guard) + + node.write('\n'.join(lst)) + + # config files must not be removed on "waf clean" + self.env.append_unique(Build.CFG_FILES, [node.abspath()]) + + if remove: + for key in self.env[DEFKEYS]: + self.undefine(key) + self.env[DEFKEYS] = [] + +@conf +def get_config_header(self, defines=True, headers=False, define_prefix=''): + """ + Creates the contents of a ``config.h`` file from the defines and includes + set in conf.env.define_key / conf.env.include_key. No include guards are added. + + A prelude will be added from the variable env.WAF_CONFIG_H_PRELUDE if provided. This + can be used to insert complex macros or include guards:: + + def configure(conf): + conf.env.WAF_CONFIG_H_PRELUDE = '#include <unistd.h>\\n' + conf.write_config_header('config.h') + + :param defines: write the defines values + :type defines: bool + :param headers: write include entries for each element in self.env.INCKEYS + :type headers: bool + :type define_prefix: string + :param define_prefix: prefix all the defines with a particular prefix + :return: the contents of a ``config.h`` file + :rtype: string + """ + lst = [] + + if self.env.WAF_CONFIG_H_PRELUDE: + lst.append(self.env.WAF_CONFIG_H_PRELUDE) + + if headers: + for x in self.env[INCKEYS]: + lst.append('#include <%s>' % x) + + if defines: + tbl = {} + for k in self.env.DEFINES: + a, _, b = k.partition('=') + tbl[a] = b + + for k in self.env[DEFKEYS]: + caption = self.get_define_comment(k) + if caption: + caption = ' /* %s */' % caption + try: + txt = '#define %s%s %s%s' % (define_prefix, k, tbl[k], caption) + except KeyError: + txt = '/* #undef %s%s */%s' % (define_prefix, k, caption) + lst.append(txt) + return "\n".join(lst) + +@conf +def cc_add_flags(conf): + """ + Adds CFLAGS / CPPFLAGS from os.environ to conf.env + """ + conf.add_os_flags('CPPFLAGS', dup=False) + conf.add_os_flags('CFLAGS', dup=False) + +@conf +def cxx_add_flags(conf): + """ + Adds CXXFLAGS / CPPFLAGS from os.environ to conf.env + """ + conf.add_os_flags('CPPFLAGS', dup=False) + conf.add_os_flags('CXXFLAGS', dup=False) + +@conf +def link_add_flags(conf): + """ + Adds LINKFLAGS / LDFLAGS from os.environ to conf.env + """ + conf.add_os_flags('LINKFLAGS', dup=False) + conf.add_os_flags('LDFLAGS', dup=False) + +@conf +def cc_load_tools(conf): + """ + Loads the Waf c extensions + """ + if not conf.env.DEST_OS: + conf.env.DEST_OS = Utils.unversioned_sys_platform() + conf.load('c') + +@conf +def cxx_load_tools(conf): + """ + Loads the Waf c++ extensions + """ + if not conf.env.DEST_OS: + conf.env.DEST_OS = Utils.unversioned_sys_platform() + conf.load('cxx') + +@conf +def get_cc_version(conf, cc, gcc=False, icc=False, clang=False): + """ + Runs the preprocessor to determine the gcc/icc/clang version + + The variables CC_VERSION, DEST_OS, DEST_BINFMT and DEST_CPU will be set in *conf.env* + + :raise: :py:class:`waflib.Errors.ConfigurationError` + """ + cmd = cc + ['-dM', '-E', '-'] + env = conf.env.env or None + try: + out, err = conf.cmd_and_log(cmd, output=0, input='\n'.encode(), env=env) + except Errors.WafError: + conf.fatal('Could not determine the compiler version %r' % cmd) + + if gcc: + if out.find('__INTEL_COMPILER') >= 0: + conf.fatal('The intel compiler pretends to be gcc') + if out.find('__GNUC__') < 0 and out.find('__clang__') < 0: + conf.fatal('Could not determine the compiler type') + + if icc and out.find('__INTEL_COMPILER') < 0: + conf.fatal('Not icc/icpc') + + if clang and out.find('__clang__') < 0: + conf.fatal('Not clang/clang++') + if not clang and out.find('__clang__') >= 0: + conf.fatal('Could not find gcc/g++ (only Clang), if renamed try eg: CC=gcc48 CXX=g++48 waf configure') + + k = {} + if icc or gcc or clang: + out = out.splitlines() + for line in out: + lst = shlex.split(line) + if len(lst)>2: + key = lst[1] + val = lst[2] + k[key] = val + + def isD(var): + return var in k + + # Some documentation is available at http://predef.sourceforge.net + # The names given to DEST_OS must match what Utils.unversioned_sys_platform() returns. + if not conf.env.DEST_OS: + conf.env.DEST_OS = '' + for i in MACRO_TO_DESTOS: + if isD(i): + conf.env.DEST_OS = MACRO_TO_DESTOS[i] + break + else: + if isD('__APPLE__') and isD('__MACH__'): + conf.env.DEST_OS = 'darwin' + elif isD('__unix__'): # unix must be tested last as it's a generic fallback + conf.env.DEST_OS = 'generic' + + if isD('__ELF__'): + conf.env.DEST_BINFMT = 'elf' + elif isD('__WINNT__') or isD('__CYGWIN__') or isD('_WIN32'): + conf.env.DEST_BINFMT = 'pe' + if not conf.env.IMPLIBDIR: + conf.env.IMPLIBDIR = conf.env.LIBDIR # for .lib or .dll.a files + conf.env.LIBDIR = conf.env.BINDIR + elif isD('__APPLE__'): + conf.env.DEST_BINFMT = 'mac-o' + + if not conf.env.DEST_BINFMT: + # Infer the binary format from the os name. + conf.env.DEST_BINFMT = Utils.destos_to_binfmt(conf.env.DEST_OS) + + for i in MACRO_TO_DEST_CPU: + if isD(i): + conf.env.DEST_CPU = MACRO_TO_DEST_CPU[i] + break + + Logs.debug('ccroot: dest platform: ' + ' '.join([conf.env[x] or '?' for x in ('DEST_OS', 'DEST_BINFMT', 'DEST_CPU')])) + if icc: + ver = k['__INTEL_COMPILER'] + conf.env.CC_VERSION = (ver[:-2], ver[-2], ver[-1]) + else: + if isD('__clang__') and isD('__clang_major__'): + conf.env.CC_VERSION = (k['__clang_major__'], k['__clang_minor__'], k['__clang_patchlevel__']) + else: + # older clang versions and gcc + conf.env.CC_VERSION = (k['__GNUC__'], k['__GNUC_MINOR__'], k.get('__GNUC_PATCHLEVEL__', '0')) + return k + +@conf +def get_xlc_version(conf, cc): + """ + Returns the Aix compiler version + + :raise: :py:class:`waflib.Errors.ConfigurationError` + """ + cmd = cc + ['-qversion'] + try: + out, err = conf.cmd_and_log(cmd, output=0) + except Errors.WafError: + conf.fatal('Could not find xlc %r' % cmd) + + # the intention is to catch the 8.0 in "IBM XL C/C++ Enterprise Edition V8.0 for AIX..." + for v in (r"IBM XL C/C\+\+.* V(?P<major>\d*)\.(?P<minor>\d*)",): + version_re = re.compile(v, re.I).search + match = version_re(out or err) + if match: + k = match.groupdict() + conf.env.CC_VERSION = (k['major'], k['minor']) + break + else: + conf.fatal('Could not determine the XLC version.') + +@conf +def get_suncc_version(conf, cc): + """ + Returns the Sun compiler version + + :raise: :py:class:`waflib.Errors.ConfigurationError` + """ + cmd = cc + ['-V'] + try: + out, err = conf.cmd_and_log(cmd, output=0) + except Errors.WafError as e: + # Older versions of the compiler exit with non-zero status when reporting their version + if not (hasattr(e, 'returncode') and hasattr(e, 'stdout') and hasattr(e, 'stderr')): + conf.fatal('Could not find suncc %r' % cmd) + out = e.stdout + err = e.stderr + + version = (out or err) + version = version.splitlines()[0] + + # cc: Sun C 5.10 SunOS_i386 2009/06/03 + # cc: Studio 12.5 Sun C++ 5.14 SunOS_sparc Beta 2015/11/17 + # cc: WorkShop Compilers 5.0 98/12/15 C 5.0 + version_re = re.compile(r'cc: (studio.*?|\s+)?(sun\s+(c\+\+|c)|(WorkShop\s+Compilers))?\s+(?P<major>\d*)\.(?P<minor>\d*)', re.I).search + match = version_re(version) + if match: + k = match.groupdict() + conf.env.CC_VERSION = (k['major'], k['minor']) + else: + conf.fatal('Could not determine the suncc version.') + +# ============ the --as-needed flag should added during the configuration, not at runtime ========= + +@conf +def add_as_needed(self): + """ + Adds ``--as-needed`` to the *LINKFLAGS* + On some platforms, it is a default flag. In some cases (e.g., in NS-3) it is necessary to explicitly disable this feature with `-Wl,--no-as-needed` flag. + """ + if self.env.DEST_BINFMT == 'elf' and 'gcc' in (self.env.CXX_NAME, self.env.CC_NAME): + self.env.append_unique('LINKFLAGS', '-Wl,--as-needed') + +# ============ parallel configuration + +class cfgtask(Task.Task): + """ + A task that executes build configuration tests (calls conf.check) + + Make sure to use locks if concurrent access to the same conf.env data is necessary. + """ + def __init__(self, *k, **kw): + Task.Task.__init__(self, *k, **kw) + self.run_after = set() + + def display(self): + return '' + + def runnable_status(self): + for x in self.run_after: + if not x.hasrun: + return Task.ASK_LATER + return Task.RUN_ME + + def uid(self): + return Utils.SIG_NIL + + def signature(self): + return Utils.SIG_NIL + + def run(self): + conf = self.conf + bld = Build.BuildContext(top_dir=conf.srcnode.abspath(), out_dir=conf.bldnode.abspath()) + bld.env = conf.env + bld.init_dirs() + bld.in_msg = 1 # suppress top-level start_msg + bld.logger = self.logger + bld.multicheck_task = self + args = self.args + try: + if 'func' in args: + bld.test(build_fun=args['func'], + msg=args.get('msg', ''), + okmsg=args.get('okmsg', ''), + errmsg=args.get('errmsg', ''), + ) + else: + args['multicheck_mandatory'] = args.get('mandatory', True) + args['mandatory'] = True + try: + bld.check(**args) + finally: + args['mandatory'] = args['multicheck_mandatory'] + except Exception: + return 1 + + def process(self): + Task.Task.process(self) + if 'msg' in self.args: + with self.generator.bld.multicheck_lock: + self.conf.start_msg(self.args['msg']) + if self.hasrun == Task.NOT_RUN: + self.conf.end_msg('test cancelled', 'YELLOW') + elif self.hasrun != Task.SUCCESS: + self.conf.end_msg(self.args.get('errmsg', 'no'), 'YELLOW') + else: + self.conf.end_msg(self.args.get('okmsg', 'yes'), 'GREEN') + +@conf +def multicheck(self, *k, **kw): + """ + Runs configuration tests in parallel; results are printed sequentially at the end of the build + but each test must provide its own msg value to display a line:: + + def test_build(ctx): + ctx.in_msg = True # suppress console outputs + ctx.check_large_file(mandatory=False) + + conf.multicheck( + {'header_name':'stdio.h', 'msg':'... stdio', 'uselib_store':'STDIO', 'global_define':False}, + {'header_name':'xyztabcd.h', 'msg':'... optional xyztabcd.h', 'mandatory': False}, + {'header_name':'stdlib.h', 'msg':'... stdlib', 'okmsg': 'aye', 'errmsg': 'nope'}, + {'func': test_build, 'msg':'... testing an arbitrary build function', 'okmsg':'ok'}, + msg = 'Checking for headers in parallel', + mandatory = True, # mandatory tests raise an error at the end + run_all_tests = True, # try running all tests + ) + + The configuration tests may modify the values in conf.env in any order, and the define + values can affect configuration tests being executed. It is hence recommended + to provide `uselib_store` values with `global_define=False` to prevent such issues. + """ + self.start_msg(kw.get('msg', 'Executing %d configuration tests' % len(k)), **kw) + + # Force a copy so that threads append to the same list at least + # no order is guaranteed, but the values should not disappear at least + for var in ('DEFINES', DEFKEYS): + self.env.append_value(var, []) + self.env.DEFINE_COMMENTS = self.env.DEFINE_COMMENTS or {} + + # define a task object that will execute our tests + class par(object): + def __init__(self): + self.keep = False + self.task_sigs = {} + self.progress_bar = 0 + def total(self): + return len(tasks) + def to_log(self, *k, **kw): + return + + bld = par() + bld.keep = kw.get('run_all_tests', True) + bld.imp_sigs = {} + tasks = [] + + id_to_task = {} + for dct in k: + x = Task.classes['cfgtask'](bld=bld, env=None) + tasks.append(x) + x.args = dct + x.bld = bld + x.conf = self + x.args = dct + + # bind a logger that will keep the info in memory + x.logger = Logs.make_mem_logger(str(id(x)), self.logger) + + if 'id' in dct: + id_to_task[dct['id']] = x + + # second pass to set dependencies with after_test/before_test + for x in tasks: + for key in Utils.to_list(x.args.get('before_tests', [])): + tsk = id_to_task[key] + if not tsk: + raise ValueError('No test named %r' % key) + tsk.run_after.add(x) + for key in Utils.to_list(x.args.get('after_tests', [])): + tsk = id_to_task[key] + if not tsk: + raise ValueError('No test named %r' % key) + x.run_after.add(tsk) + + def it(): + yield tasks + while 1: + yield [] + bld.producer = p = Runner.Parallel(bld, Options.options.jobs) + bld.multicheck_lock = Utils.threading.Lock() + p.biter = it() + + self.end_msg('started') + p.start() + + # flush the logs in order into the config.log + for x in tasks: + x.logger.memhandler.flush() + + self.start_msg('-> processing test results') + if p.error: + for x in p.error: + if getattr(x, 'err_msg', None): + self.to_log(x.err_msg) + self.end_msg('fail', color='RED') + raise Errors.WafError('There is an error in the library, read config.log for more information') + + failure_count = 0 + for x in tasks: + if x.hasrun not in (Task.SUCCESS, Task.NOT_RUN): + failure_count += 1 + + if failure_count: + self.end_msg(kw.get('errmsg', '%s test failed' % failure_count), color='YELLOW', **kw) + else: + self.end_msg('all ok', **kw) + + for x in tasks: + if x.hasrun != Task.SUCCESS: + if x.args.get('mandatory', True): + self.fatal(kw.get('fatalmsg') or 'One of the tests has failed, read config.log for more information') + +@conf +def check_gcc_o_space(self, mode='c'): + if int(self.env.CC_VERSION[0]) > 4: + # this is for old compilers + return + self.env.stash() + if mode == 'c': + self.env.CCLNK_TGT_F = ['-o', ''] + elif mode == 'cxx': + self.env.CXXLNK_TGT_F = ['-o', ''] + features = '%s %sshlib' % (mode, mode) + try: + self.check(msg='Checking if the -o link must be split from arguments', fragment=SNIP_EMPTY_PROGRAM, features=features) + except self.errors.ConfigurationError: + self.env.revert() + else: + self.env.commit() + |