diff options
Diffstat (limited to 'buildtools/wafadmin/Tools/config_c.py')
-rw-r--r-- | buildtools/wafadmin/Tools/config_c.py | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/buildtools/wafadmin/Tools/config_c.py b/buildtools/wafadmin/Tools/config_c.py new file mode 100644 index 0000000000..a32d8aaf1a --- /dev/null +++ b/buildtools/wafadmin/Tools/config_c.py @@ -0,0 +1,736 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +""" +c/c++ configuration routines +""" + +import os, imp, sys, shlex, shutil +from Utils import md5 +import Build, Utils, Configure, Task, Options, Logs, TaskGen +from Constants import * +from Configure import conf, conftest + +cfg_ver = { + 'atleast-version': '>=', + 'exact-version': '==', + 'max-version': '<=', +} + +SNIP1 = ''' + int main() { + void *p; + p=(void*)(%s); + return 0; +} +''' + +SNIP2 = ''' +int main() { + if ((%(type_name)s *) 0) return 0; + if (sizeof (%(type_name)s)) return 0; +} +''' + +SNIP3 = ''' +int main() { + return 0; +} +''' + +def parse_flags(line, uselib, env): + """pkg-config still has bugs on some platforms, and there are many -config programs, parsing flags is necessary :-/""" + + lst = shlex.split(line) + while lst: + x = lst.pop(0) + st = x[:2] + ot = x[2:] + app = env.append_value + if st == '-I' or st == '/I': + if not ot: ot = lst.pop(0) + app('CPPPATH_' + uselib, ot) + elif st == '-D': + if not ot: ot = lst.pop(0) + app('CXXDEFINES_' + uselib, ot) + app('CCDEFINES_' + uselib, ot) + elif st == '-l': + if not ot: ot = lst.pop(0) + app('LIB_' + uselib, ot) + elif st == '-L': + if not ot: ot = lst.pop(0) + app('LIBPATH_' + uselib, ot) + elif x == '-pthread' or x.startswith('+'): + app('CCFLAGS_' + uselib, x) + app('CXXFLAGS_' + uselib, x) + app('LINKFLAGS_' + uselib, x) + elif x == '-framework': + app('FRAMEWORK_' + uselib, lst.pop(0)) + elif x.startswith('-F'): + app('FRAMEWORKPATH_' + uselib, x[2:]) + elif x.startswith('-std'): + app('CCFLAGS_' + uselib, x) + app('CXXFLAGS_' + uselib, x) + app('LINKFLAGS_' + uselib, x) + elif x.startswith('-Wl'): + app('LINKFLAGS_' + uselib, x) + elif x.startswith('-m') or x.startswith('-f'): + app('CCFLAGS_' + uselib, x) + app('CXXFLAGS_' + uselib, x) + +@conf +def ret_msg(self, f, kw): + """execute a function, when provided""" + if isinstance(f, str): + return f + return f(kw) + +@conf +def validate_cfg(self, kw): + if not 'path' in kw: + kw['path'] = 'pkg-config --errors-to-stdout --print-errors' + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for pkg-config version >= %s' % kw['atleast_pkgconfig_version'] + return + + # pkg-config --modversion + if 'modversion' in kw: + return + + if 'variables' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for %s variables' % kw['package'] + return + + # checking for the version of a module, for the moment, one thing at a time + for x in cfg_ver.keys(): + y = x.replace('-', '_') + if y in kw: + if not 'package' in kw: + raise ValueError('%s requires a package' % x) + + if not 'msg' in kw: + kw['msg'] = 'Checking for %s %s %s' % (kw['package'], cfg_ver[x], kw[y]) + return + + if not 'msg' in kw: + kw['msg'] = 'Checking for %s' % (kw['package'] or kw['path']) + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + if not 'errmsg' in kw: + kw['errmsg'] = 'not found' + +@conf +def cmd_and_log(self, cmd, kw): + Logs.debug('runner: %s\n' % cmd) + if self.log: + self.log.write('%s\n' % cmd) + + try: + p = Utils.pproc.Popen(cmd, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE, shell=True) + (out, err) = p.communicate() + except OSError, e: + self.log.write('error %r' % e) + self.fatal(str(e)) + + # placeholder, don't touch + out = str(out) + err = str(err) + + if self.log: + self.log.write(out) + self.log.write(err) + + if p.returncode: + if not kw.get('errmsg', ''): + if kw.get('mandatory', False): + kw['errmsg'] = out.strip() + else: + kw['errmsg'] = 'no' + self.fatal('fail') + return out + +@conf +def exec_cfg(self, kw): + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + cmd = '%s --atleast-pkgconfig-version=%s' % (kw['path'], kw['atleast_pkgconfig_version']) + self.cmd_and_log(cmd, kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + return + + # checking for the version of a module + for x in cfg_ver: + y = x.replace('-', '_') + if y in kw: + self.cmd_and_log('%s --%s=%s %s' % (kw['path'], x, kw[y], kw['package']), kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + self.define(self.have_define(kw.get('uselib_store', kw['package'])), 1, 0) + break + + # retrieving the version of a module + if 'modversion' in kw: + version = self.cmd_and_log('%s --modversion %s' % (kw['path'], kw['modversion']), kw).strip() + self.define('%s_VERSION' % Utils.quote_define_name(kw.get('uselib_store', kw['modversion'])), version) + return version + + # retrieving variables of a module + if 'variables' in kw: + env = kw.get('env', self.env) + uselib = kw.get('uselib_store', kw['package'].upper()) + vars = Utils.to_list(kw['variables']) + for v in vars: + val = self.cmd_and_log('%s --variable=%s %s' % (kw['path'], v, kw['package']), kw).strip() + var = '%s_%s' % (uselib, v) + env[var] = val + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + return + + lst = [kw['path']] + + + defi = kw.get('define_variable', None) + if not defi: + defi = self.env.PKG_CONFIG_DEFINES or {} + for key, val in defi.iteritems(): + lst.append('--define-variable=%s=%s' % (key, val)) + + lst.append(kw.get('args', '')) + lst.append(kw['package']) + + # so we assume the command-line will output flags to be parsed afterwards + cmd = ' '.join(lst) + ret = self.cmd_and_log(cmd, kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + + self.define(self.have_define(kw.get('uselib_store', kw['package'])), 1, 0) + parse_flags(ret, kw.get('uselib_store', kw['package'].upper()), kw.get('env', self.env)) + return ret + +@conf +def check_cfg(self, *k, **kw): + """ + for pkg-config mostly, but also all the -config tools + conf.check_cfg(path='mpicc', args='--showme:compile --showme:link', package='', uselib_store='OPEN_MPI') + conf.check_cfg(package='dbus-1', variables='system_bus_default_address session_bus_services_dir') + """ + + self.validate_cfg(kw) + if 'msg' in kw: + self.check_message_1(kw['msg']) + ret = None + try: + ret = self.exec_cfg(kw) + except Configure.ConfigurationError, e: + if 'errmsg' in kw: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw and kw['mandatory']: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + kw['success'] = ret + if 'okmsg' in kw: + self.check_message_2(self.ret_msg(kw['okmsg'], kw)) + + return ret + +# the idea is the following: now that we are certain +# that all the code here is only for c or c++, it is +# easy to put all the logic in one function +# +# this should prevent code duplication (ita) + +# env: an optional environment (modified -> provide a copy) +# compiler: cc or cxx - it tries to guess what is best +# type: cprogram, cshlib, cstaticlib +# code: a c code to execute +# uselib_store: where to add the variables +# uselib: parameters to use for building +# define: define to set, like FOO in #define FOO, if not set, add /* #undef FOO */ +# execute: True or False - will return the result of the execution + +@conf +def validate_c(self, kw): + """validate the parameters for the test method""" + + if not 'env' in kw: + kw['env'] = self.env.copy() + + env = kw['env'] + if not 'compiler' in kw: + kw['compiler'] = 'cc' + if env['CXX_NAME'] and Task.TaskBase.classes.get('cxx', None): + 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 'type' in kw: + kw['type'] = 'cprogram' + + assert not(kw['type'] != 'cprogram' and kw.get('execute', 0)), 'can only execute programs' + + + #if kw['type'] != 'program' and kw.get('execute', 0): + # raise ValueError, 'can only execute programs' + + 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 '' + + # set the file name + if not 'compile_mode' in kw: + kw['compile_mode'] = (kw['compiler'] == 'cxx') and 'cxx' or 'cc' + + if not 'compile_filename' in kw: + kw['compile_filename'] = 'test.c' + ((kw['compile_mode'] == 'cxx') and 'pp' or '') + + #OSX + if 'framework_name' in kw: + try: TaskGen.task_gen.create_task_macapp + except AttributeError: self.fatal('frameworks require the osx tool') + + fwkname = kw['framework_name'] + if not 'uselib_store' in kw: + kw['uselib_store'] = fwkname.upper() + + if not kw.get('no_header', False): + if not 'header_name' in kw: + kw['header_name'] = [] + fwk = '%s/%s.h' % (fwkname, fwkname) + if kw.get('remove_dot_h', None): + fwk = fwk[:-2] + kw['header_name'] = Utils.to_list(kw['header_name']) + [fwk] + + kw['msg'] = 'Checking for framework %s' % fwkname + kw['framework'] = fwkname + #kw['frameworkpath'] = set it yourself + + if 'function_name' in kw: + fu = kw['function_name'] + if not 'msg' in kw: + kw['msg'] = 'Checking for function %s' % fu + kw['code'] = to_header(kw) + SNIP1 % fu + if not 'uselib_store' in kw: + kw['uselib_store'] = fu.upper() + if not 'define_name' in kw: + kw['define_name'] = self.have_define(fu) + + elif 'type_name' in kw: + tu = kw['type_name'] + if not 'msg' in kw: + kw['msg'] = 'Checking for type %s' % tu + if not 'header_name' in kw: + kw['header_name'] = 'stdint.h' + kw['code'] = to_header(kw) + SNIP2 % {'type_name' : tu} + if not 'define_name' in kw: + kw['define_name'] = self.have_define(tu.upper()) + + 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)>0, 'list of headers in header_name is empty' + + kw['code'] = to_header(kw) + SNIP3 + + 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 'staticlib' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for static library %s' % kw['staticlib'] + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['staticlib'].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 custom code' + 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 not 'errmsg' in kw: + kw['errmsg'] = 'not found' + + if not 'okmsg' in kw: + kw['okmsg'] = 'yes' + + if not 'code' in kw: + kw['code'] = SNIP3 + + if not kw.get('success'): kw['success'] = None + + assert 'msg' in kw, 'invalid parameters, read http://freehackers.org/~tnagy/wafbook/single.html#config_helpers_c' + +@conf +def post_check(self, *k, **kw): + "set the variables after a test was run successfully" + + is_success = False + if kw['execute']: + if kw['success'] is not None: + is_success = True + else: + is_success = (kw['success'] == 0) + + if 'define_name' in kw: + if 'header_name' in kw or 'function_name' in kw or 'type_name' in kw or 'fragment' in kw: + if kw['execute']: + key = kw['success'] + if isinstance(key, str): + if key: + self.define(kw['define_name'], key, quote=kw.get('quote', 1)) + else: + self.define_cond(kw['define_name'], True) + else: + self.define_cond(kw['define_name'], False) + else: + self.define_cond(kw['define_name'], is_success) + + if is_success and 'uselib_store' in kw: + import cc, cxx + for k in set(cc.g_cc_flag_vars).union(cxx.g_cxx_flag_vars): + lk = k.lower() + # inconsistency: includes -> CPPPATH + if k == 'CPPPATH': lk = 'includes' + if k == 'CXXDEFINES': lk = 'defines' + if k == 'CCDEFINES': lk = 'defines' + if lk in kw: + val = kw[lk] + # remove trailing slash + if isinstance(val, str): + val = val.rstrip(os.path.sep) + self.env.append_unique(k + '_' + kw['uselib_store'], val) + +@conf +def check(self, *k, **kw): + # so this will be the generic function + # it will be safer to use check_cxx or check_cc + self.validate_c(kw) + self.check_message_1(kw['msg']) + ret = None + try: + ret = self.run_c_code(*k, **kw) + except Configure.ConfigurationError, e: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw and kw['mandatory']: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + kw['success'] = ret + self.check_message_2(self.ret_msg(kw['okmsg'], kw)) + + self.post_check(*k, **kw) + if not kw.get('execute', False): + return ret == 0 + return ret + +@conf +def run_c_code(self, *k, **kw): + test_f_name = kw['compile_filename'] + + k = 0 + while k < 10000: + # make certain to use a fresh folder - necessary for win32 + dir = os.path.join(self.blddir, '.conf_check_%d' % k) + + # if the folder already exists, remove it + try: + shutil.rmtree(dir) + except OSError: + pass + + try: + os.stat(dir) + except OSError: + break + + k += 1 + + try: + os.makedirs(dir) + except: + self.fatal('cannot create a configuration test folder %r' % dir) + + try: + os.stat(dir) + except: + self.fatal('cannot use the configuration test folder %r' % dir) + + bdir = os.path.join(dir, 'testbuild') + + if not os.path.exists(bdir): + os.makedirs(bdir) + + env = kw['env'] + + dest = open(os.path.join(dir, test_f_name), 'w') + dest.write(kw['code']) + dest.close() + + back = os.path.abspath('.') + + bld = Build.BuildContext() + bld.log = self.log + bld.all_envs.update(self.all_envs) + bld.all_envs['default'] = env + bld.lst_variants = bld.all_envs.keys() + bld.load_dirs(dir, bdir) + + os.chdir(dir) + + bld.rescan(bld.srcnode) + + if not 'features' in kw: + # conf.check(features='cc cprogram pyext', ...) + kw['features'] = [kw['compile_mode'], kw['type']] # "cprogram cc" + + o = bld(features=kw['features'], source=test_f_name, target='testprog') + + for k, v in kw.iteritems(): + setattr(o, k, v) + + self.log.write("==>\n%s\n<==\n" % kw['code']) + + # compile the program + try: + bld.compile() + except Utils.WafError: + ret = Utils.ex_stack() + else: + ret = 0 + + # chdir before returning + os.chdir(back) + + if ret: + self.log.write('command returned %r' % ret) + self.fatal(str(ret)) + + # if we need to run the program, try to get its result + # keep the name of the program to execute + if kw['execute']: + lastprog = o.link_task.outputs[0].abspath(env) + + args = Utils.to_list(kw.get('exec_args', [])) + proc = Utils.pproc.Popen([lastprog] + args, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE) + (out, err) = proc.communicate() + w = self.log.write + w(str(out)) + w('\n') + w(str(err)) + w('\n') + w('returncode %r' % proc.returncode) + w('\n') + if proc.returncode: + self.fatal(Utils.ex_stack()) + ret = out + + return ret + +@conf +def check_cxx(self, *k, **kw): + kw['compiler'] = 'cxx' + return self.check(*k, **kw) + +@conf +def check_cc(self, *k, **kw): + kw['compiler'] = 'cc' + return self.check(*k, **kw) + +@conf +def define(self, define, value, quote=1): + """store a single define and its state into an internal list for later + writing to a config header file. Value can only be + a string or int; other types not supported. String + values will appear properly quoted in the generated + header file.""" + assert define and isinstance(define, str) + + # ordered_dict is for writing the configuration header in order + tbl = self.env[DEFINES] or Utils.ordered_dict() + + # the user forgot to tell if the value is quoted or not + if isinstance(value, str): + if quote: + tbl[define] = '"%s"' % repr('"'+value)[2:-1].replace('"', '\\"') + else: + tbl[define] = value + elif isinstance(value, int): + tbl[define] = value + else: + raise TypeError('define %r -> %r must be a string or an int' % (define, value)) + + # add later to make reconfiguring faster + self.env[DEFINES] = tbl + self.env[define] = value # <- not certain this is necessary + +@conf +def undefine(self, define): + """store a single define and its state into an internal list + for later writing to a config header file""" + assert define and isinstance(define, str) + + tbl = self.env[DEFINES] or Utils.ordered_dict() + + value = UNDEFINED + tbl[define] = value + + # add later to make reconfiguring faster + self.env[DEFINES] = tbl + self.env[define] = value + +@conf +def define_cond(self, name, value): + """Conditionally define a name. + Formally equivalent to: if value: define(name, 1) else: undefine(name)""" + if value: + self.define(name, 1) + else: + self.undefine(name) + +@conf +def is_defined(self, key): + defines = self.env[DEFINES] + if not defines: + return False + try: + value = defines[key] + except KeyError: + return False + else: + return value != UNDEFINED + +@conf +def get_define(self, define): + "get the value of a previously stored define" + try: return self.env[DEFINES][define] + except KeyError: return None + +@conf +def have_define(self, name): + "prefix the define with 'HAVE_' and make sure it has valid characters." + return self.__dict__.get('HAVE_PAT', 'HAVE_%s') % Utils.quote_define_name(name) + +@conf +def write_config_header(self, configfile='', env='', guard='', top=False): + "save the defines into a file" + if not configfile: configfile = WAF_CONFIG_H + waf_guard = guard or '_%s_WAF' % Utils.quote_define_name(configfile) + + # configfile -> absolute path + # there is a good reason to concatenate first and to split afterwards + if not env: env = self.env + if top: + diff = '' + else: + diff = Utils.diff_path(self.srcdir, self.curdir) + full = os.sep.join([self.blddir, env.variant(), diff, configfile]) + full = os.path.normpath(full) + (dir, base) = os.path.split(full) + + try: os.makedirs(dir) + except: pass + + dest = open(full, 'w') + dest.write('/* Configuration header created by Waf - do not edit */\n') + dest.write('#ifndef %s\n#define %s\n\n' % (waf_guard, waf_guard)) + + dest.write(self.get_config_header()) + + # config files are not removed on "waf clean" + env.append_unique(CFG_FILES, os.path.join(diff, configfile)) + + dest.write('\n#endif /* %s */\n' % waf_guard) + dest.close() + +@conf +def get_config_header(self): + """Fill-in the contents of the config header. Override when you need to write your own config header.""" + config_header = [] + + tbl = self.env[DEFINES] or Utils.ordered_dict() + for key in tbl.allkeys: + value = tbl[key] + if value is None: + config_header.append('#define %s' % key) + elif value is UNDEFINED: + config_header.append('/* #undef %s */' % key) + else: + config_header.append('#define %s %s' % (key, value)) + return "\n".join(config_header) + +@conftest +def find_cpp(conf): + v = conf.env + cpp = [] + if v['CPP']: cpp = v['CPP'] + elif 'CPP' in conf.environ: cpp = conf.environ['CPP'] + if not cpp: cpp = conf.find_program('cpp', var='CPP') + #if not cpp: cpp = v['CC'] + #if not cpp: cpp = v['CXX'] + v['CPP'] = cpp + +@conftest +def cc_add_flags(conf): + conf.add_os_flags('CFLAGS', 'CCFLAGS') + conf.add_os_flags('CPPFLAGS') + +@conftest +def cxx_add_flags(conf): + conf.add_os_flags('CXXFLAGS') + conf.add_os_flags('CPPFLAGS') + +@conftest +def link_add_flags(conf): + conf.add_os_flags('LINKFLAGS') + conf.add_os_flags('LDFLAGS', 'LINKFLAGS') + +@conftest +def cc_load_tools(conf): + conf.check_tool('cc') + +@conftest +def cxx_load_tools(conf): + conf.check_tool('cxx') + |