Logo Search packages:      
Sourcecode: waf version File versions  Download package

config_c.py

#!/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:]

            if st == '-I' or st == '/I':
                  if not ot: ot = lst.pop(0)
                  env.append_unique('CPPPATH_' + uselib, ot)
            elif st == '-D':
                  if not ot: ot = lst.pop(0)
                  env.append_unique('CXXDEFINES_' + uselib, ot)
                  env.append_unique('CCDEFINES_' + uselib, ot)
            elif st == '-l':
                  if not ot: ot = lst.pop(0)
                  env.append_unique('LIB_' + uselib, ot)
            elif st == '-L':
                  if not ot: ot = lst.pop(0)
                  env.append_unique('LIBPATH_' + uselib, ot)
            elif x == '-pthread' or x.startswith('+'):
                  env.append_unique('CCFLAGS_' + uselib, x)
                  env.append_unique('CXXFLAGS_' + uselib, x)
                  env.append_unique('LINKFLAGS_' + uselib, x)
            elif x == '-framework':
                  env.append_unique('FRAMEWORK_' + uselib, lst.pop(0))
            elif x.startswith('-F'):
                  env.append_unique('FRAMEWORKPATH_' + uselib, x[2:])
            elif x.startswith('-std'):
                  env.append_unique('CCFLAGS_' + uselib, x)
                  env.append_unique('LINKFLAGS_' + uselib, x)
            elif x.startswith('-Wl'):
                  env.append_unique('LINKFLAGS_' + uselib, x)
            elif x.startswith('-m') or x.startswith('-f'):
                  env.append_unique('CCFLAGS_' + uselib, x)
                  env.append_unique('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

      # 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']
      if not 'okmsg' in kw:
            kw['okmsg'] = 'ok'
      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, shell=True)
            output = p.communicate()[0]
      except OSError:
            self.fatal('fail')

      if p.returncode:
            if not kw.get('errmsg', ''):
                  if kw.get('mandatory', False):
                        kw['errmsg'] = output.strip()
                  else:
                        kw['errmsg'] = 'fail'
            self.fatal('fail')
      return output

@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'] = 'ok'
            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'] = 'ok'
                  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

      lst = [kw['path']]
      for key, val in kw.get('define_variable', {}).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'] = 'ok'

      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):
      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: program, shlib, staticlib, objects
# 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'] = 'fail'

      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'] = 'fail'

      if not 'execute' in kw:
            kw['execute'] = False

      if not 'errmsg' in kw:
            kw['errmsg'] = 'not found'

      if not 'okmsg' in kw:
            kw['okmsg'] = 'ok'

      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 = 0
      if kw['execute']:
            if kw['success']:
                  is_success = kw['success']
      else:
            is_success = (kw['success'] == 0)

      def define_or_stuff():
            nm = kw['define_name']
            if kw['execute'] and kw.get('define_ret', None) and isinstance(is_success, str):
                  self.define(kw['define_name'], is_success, quote=kw.get('quote', 1))
            else:
                  self.define_cond(kw['define_name'], is_success)

      if 'define_name' in kw:
            if 'header_name' in kw or 'function_name' in kw or 'type_name' in kw or 'fragment' in kw:
                  define_or_stuff()

      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)

      o = bld.new_task_gen(features=[kw['compile_mode'], kw['type']], 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))

      # keep the name of the program to execute
      if kw['execute']:
            lastprog = o.link_task.outputs[0].abspath(env)

      # if we need to run the program, try to get its result
      if kw['execute']:
            args = Utils.to_list(kw.get('exec_args', []))
            try:
                  data = Utils.cmd_output([lastprog] + args).strip()
            except ValueError, e:
                  self.fatal(Utils.ex_stack())
            ret = data

      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"' % str(value)
            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_value(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)
            elif isinstance(value, str):
                  config_header.append('#define %s %s' % (key, repr(value)[1:-1]))
            else:
                  config_header.append('#define %s %s' % (key, value))
      return "\n".join(config_header)

@conftest
def find_cpp(conf):
      v = conf.env
      cpp = None
      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')


Generated by  Doxygen 1.6.0   Back to index