bindings/python/wscript
changeset 7487 82cd20da9650
parent 7470 234c01373116
child 7488 72d0c878f3c7
equal deleted inserted replaced
7477:4764c1472bd6 7487:82cd20da9650
     1 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
     1 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
     2 import types
     2 import types
     3 import re
     3 import re
     4 import os
     4 import os
     5 import pproc as subprocess
     5 import subprocess
     6 import shutil
     6 import shutil
     7 import sys
     7 import sys
     8 
     8 
     9 import Task
     9 import Task
    10 import Options
    10 import Options
    19 REQUIRED_PYGCCXML_VERSION = (0, 9, 5)
    19 REQUIRED_PYGCCXML_VERSION = (0, 9, 5)
    20 
    20 
    21 
    21 
    22 
    22 
    23 from TaskGen import feature, after
    23 from TaskGen import feature, after
    24 import Task, ccroot
    24 import Task
    25 from python import _get_python_variables # this comes from wafadmin/Tools/python.py
    25 #from python import _get_python_variables # this comes from wafadmin/Tools/python.py
    26 
    26 
    27 
    27 if 0:
    28 # Patch a bug in waf-1.5.16's python detection, see
    28     # Patch a bug in waf-1.5.16's python detection, see
    29 # https://www.nsnam.org/bugzilla/show_bug.cgi?id=1250
    29     # https://www.nsnam.org/bugzilla/show_bug.cgi?id=1250
    30 import python
    30     import python
    31 python.FRAG_2 = """
    31     python.FRAG_2 = """
    32 #include <Python.h>
    32     #include <Python.h>
    33 """ + python.FRAG_2
    33     """ + python.FRAG_2
    34 del python
    34     del python
    35 
    35 
    36 
    36 
    37 
    37 
    38 def add_to_python_path(path):
    38 def add_to_python_path(path):
    39     if os.environ.get('PYTHONPATH', ''):
    39     if os.environ.get('PYTHONPATH', ''):
    84     enabled_modules.sort()
    84     enabled_modules.sort()
    85     available_modules = list(conf.env['NS3_MODULES'])
    85     available_modules = list(conf.env['NS3_MODULES'])
    86     available_modules.sort()
    86     available_modules.sort()
    87     all_modules_enabled = (enabled_modules == available_modules)
    87     all_modules_enabled = (enabled_modules == available_modules)
    88 
    88 
    89     conf.check_tool('misc')
    89     conf.check_tool('misc', tooldir=['waf-tools'])
    90 
    90 
    91     if sys.platform == 'cygwin':
    91     if sys.platform == 'cygwin':
    92         conf.report_optional_feature("python", "Python Bindings", False,
    92         conf.report_optional_feature("python", "Python Bindings", False,
    93                                      "unsupported platform 'cygwin'")
    93                                      "unsupported platform 'cygwin'")
    94         Logs.warn("Python is not supported in CygWin environment.  Try MingW instead.")
    94         Logs.warn("Python is not supported in CygWin environment.  Try MingW instead.")
   102     except Configure.ConfigurationError, ex:
   102     except Configure.ConfigurationError, ex:
   103         conf.report_optional_feature("python", "Python Bindings", False, str(ex))
   103         conf.report_optional_feature("python", "Python Bindings", False, str(ex))
   104         return
   104         return
   105 
   105 
   106 
   106 
   107     # alternative code to computing PYTHONDIR, that is more correct than the one in waf 1.5.16
   107     if 0:
   108     if 'PYTHONDIR' in conf.environ:
   108         # alternative code to computing PYTHONDIR, that is more correct than the one in waf 1.5.16
   109         pydir = conf.environ['PYTHONDIR']
   109         if 'PYTHONDIR' in conf.environ:
   110     else:
   110             pydir = conf.environ['PYTHONDIR']
   111         (pydir,) = _get_python_variables(conf.env['PYTHON'],
   111         else:
   112                                          ["get_python_lib(plat_specific=1, standard_lib=0, prefix=%r)" % conf.env['PREFIX']],
   112             (pydir,) = _get_python_variables(conf.env['PYTHON'],
   113                                          ['from distutils.sysconfig import get_python_lib'])
   113                                              ["get_python_lib(plat_specific=1, standard_lib=0, prefix=%r)" % conf.env['PREFIX']],
   114     if hasattr(conf, 'define'): # conf.define is added by the C tool, so may not exist
   114                                              ['from distutils.sysconfig import get_python_lib'])
   115         conf.define('PYTHONDIR', pydir)
   115         if hasattr(conf, 'define'): # conf.define is added by the C tool, so may not exist
   116     conf.env['PYTHONDIR'] = pydir
   116             conf.define('PYTHONDIR', pydir)
       
   117         conf.env['PYTHONDIR'] = pydir
   117 
   118 
   118 
   119 
   119     # -fvisibility=hidden optimization
   120     # -fvisibility=hidden optimization
   120     if (conf.env['CXX_NAME'] == 'gcc' and [int(x) for x in conf.env['CC_VERSION']] >= [4,0,0]
   121     if (conf.env['CXX_NAME'] == 'gcc' and [int(x) for x in conf.env['CC_VERSION']] >= [4,0,0]
   121         and conf.check_compilation_flag('-fvisibility=hidden')):
   122         and conf.check_compilation_flag('-fvisibility=hidden')):
   123         conf.env.append_value('CCFLAGS_PYEXT', '-fvisibility=hidden')
   124         conf.env.append_value('CCFLAGS_PYEXT', '-fvisibility=hidden')
   124 
   125 
   125     # Check for the location of pybindgen
   126     # Check for the location of pybindgen
   126     if Options.options.with_pybindgen is not None:
   127     if Options.options.with_pybindgen is not None:
   127         if os.path.isdir(Options.options.with_pybindgen):
   128         if os.path.isdir(Options.options.with_pybindgen):
   128             conf.check_message("pybindgen location", '', True, ("%s (given)" % Options.options.with_pybindgen))
   129             conf.msg("Checking for pybindgen location", ("%s (given)" % Options.options.with_pybindgen))
   129             conf.env['WITH_PYBINDGEN'] = os.path.abspath(Options.options.with_pybindgen)
   130             conf.env['WITH_PYBINDGEN'] = os.path.abspath(Options.options.with_pybindgen)
   130     else:
   131     else:
   131         # ns-3-dev uses ../pybindgen, while ns-3 releases use ../REQUIRED_PYBINDGEN_VERSION
   132         # ns-3-dev uses ../pybindgen, while ns-3 releases use ../REQUIRED_PYBINDGEN_VERSION
   132         pybindgen_dir = os.path.join('..', "pybindgen")
   133         pybindgen_dir = os.path.join('..', "pybindgen")
   133         pybindgen_release_str = "pybindgen-" + '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])
   134         pybindgen_release_str = "pybindgen-" + '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])
   134         pybindgen_release_dir = os.path.join('..', pybindgen_release_str)
   135         pybindgen_release_dir = os.path.join('..', pybindgen_release_str)
   135         if os.path.isdir(pybindgen_dir):
   136         if os.path.isdir(pybindgen_dir):
   136             conf.check_message("pybindgen location", '', True, ("%s (guessed)" % pybindgen_dir))
   137             conf.msg("Checking for pybindgen location", ("%s (guessed)" % pybindgen_dir))
   137             conf.env['WITH_PYBINDGEN'] = os.path.abspath(pybindgen_dir)
   138             conf.env['WITH_PYBINDGEN'] = os.path.abspath(pybindgen_dir)
   138         elif os.path.isdir(pybindgen_release_dir):
   139         elif os.path.isdir(pybindgen_release_dir):
   139             conf.check_message("pybindgen location", '', True, ("%s (guessed)" % pybindgen_release_dir))
   140             conf.msg("Checking for pybindgen location", ("%s (guessed)" % pybindgen_release_dir))
   140             conf.env['WITH_PYBINDGEN'] = os.path.abspath(pybindgen_release_dir)
   141             conf.env['WITH_PYBINDGEN'] = os.path.abspath(pybindgen_release_dir)
   141         del pybindgen_dir
   142         del pybindgen_dir
   142         del pybindgen_release_dir
   143         del pybindgen_release_dir
   143     if not conf.env['WITH_PYBINDGEN']:
   144     if not conf.env['WITH_PYBINDGEN']:
   144         conf.check_message("pybindgen location", '', False)
   145         conf.msg("pybindgen location", False)
   145 
   146 
   146     # Check for pybindgen
   147     # Check for pybindgen
   147 
   148 
   148     set_pybindgen_pythonpath(conf.env)
   149     set_pybindgen_pythonpath(conf.env)
   149 
   150 
   153         Logs.warn("pybindgen missing => no python bindings")
   154         Logs.warn("pybindgen missing => no python bindings")
   154         conf.report_optional_feature("python", "Python Bindings", False,
   155         conf.report_optional_feature("python", "Python Bindings", False,
   155                                      "PyBindGen missing")
   156                                      "PyBindGen missing")
   156         return
   157         return
   157     else:
   158     else:
   158         out = subprocess.Popen([conf.env['PYTHON'], "-c",
   159         out = subprocess.Popen([conf.env['PYTHON'][0], "-c",
   159                                 "import pybindgen.version; "
   160                                 "import pybindgen.version; "
   160                                 "print '.'.join([str(x) for x in pybindgen.version.__version__])"],
   161                                 "print '.'.join([str(x) for x in pybindgen.version.__version__])"],
   161                                 stdout=subprocess.PIPE).communicate()[0]
   162                                 stdout=subprocess.PIPE).communicate()[0]
   162         pybindgen_version_str = out.strip()
   163         pybindgen_version_str = out.strip()
   163         pybindgen_version = tuple([int(x) for x in pybindgen_version_str.split('.')])
   164         pybindgen_version = tuple([int(x) for x in pybindgen_version_str.split('.')])
   164         conf.check_message('pybindgen', 'version',
   165         conf.msg('Checking for pybindgen version', pybindgen_version_str)
   165                            (pybindgen_version == REQUIRED_PYBINDGEN_VERSION),
       
   166                            pybindgen_version_str)
       
   167         if not (pybindgen_version == REQUIRED_PYBINDGEN_VERSION):
   166         if not (pybindgen_version == REQUIRED_PYBINDGEN_VERSION):
   168             Logs.warn("pybindgen (found %s), (need %s)" %
   167             Logs.warn("pybindgen (found %s), (need %s)" %
   169                     (pybindgen_version_str,
   168                     (pybindgen_version_str,
   170                      '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])))
   169                      '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])))
   171             conf.report_optional_feature("python", "Python Bindings", False,
   170             conf.report_optional_feature("python", "Python Bindings", False,
   186 ''' % dict(type1=t1, type2=t2)
   185 ''' % dict(type1=t1, type2=t2)
   187 
   186 
   188         try:
   187         try:
   189             ret = conf.run_c_code(code=test_program,
   188             ret = conf.run_c_code(code=test_program,
   190                                   env=conf.env.copy(), compile_filename='test.cc',
   189                                   env=conf.env.copy(), compile_filename='test.cc',
   191                                   compile_mode='cxx',type='cprogram', execute=False)
   190                                   features='cxx cprogram', execute=False)
   192         except Configure.ConfigurationError:
   191         except Configure.ConfigurationError:
   193             ret = 1
   192             ret = 1
   194         conf.check_message_custom('types %s and %s' % (t1, t2), 'equivalency', (ret and 'no' or 'yes'))
   193         conf.msg('Checking for types %s and %s equivalence' % (t1, t2), (ret and 'no' or 'yes'))
   195         return not ret
   194         return not ret
   196 
   195 
   197     uint64_is_long = test("uint64_t", "unsigned long")
   196     uint64_is_long = test("uint64_t", "unsigned long")
   198     uint64_is_long_long = test("uint64_t", "unsigned long long")
   197     uint64_is_long_long = test("uint64_t", "unsigned long long")
   199 
   198 
   206     if conf.env['PYTHON_BINDINGS_APIDEFS'] is None:
   205     if conf.env['PYTHON_BINDINGS_APIDEFS'] is None:
   207         msg = 'none available'
   206         msg = 'none available'
   208     else:
   207     else:
   209         msg = conf.env['PYTHON_BINDINGS_APIDEFS']
   208         msg = conf.env['PYTHON_BINDINGS_APIDEFS']
   210 
   209 
   211     conf.check_message_custom('the apidefs that can be used for Python bindings', '', msg)
   210     conf.msg('Checking for the apidefs that can be used for Python bindings', msg)
   212 
   211 
   213     if conf.env['PYTHON_BINDINGS_APIDEFS'] is None:
   212     if conf.env['PYTHON_BINDINGS_APIDEFS'] is None:
   214         conf.report_optional_feature("python", "Python Bindings", False,
   213         conf.report_optional_feature("python", "Python Bindings", False,
   215                                      "No apidefs are available that can be used in this system")
   214                                      "No apidefs are available that can be used in this system")
   216         return
   215         return
   228 {
   227 {
   229    const abi::__si_class_type_info *_typeinfo  __attribute__((unused)) = NULL;
   228    const abi::__si_class_type_info *_typeinfo  __attribute__((unused)) = NULL;
   230    return 0;
   229    return 0;
   231 }
   230 }
   232 """
   231 """
   233     gcc_rtti_abi = conf.check(fragment=fragment, msg="Checking for internal GCC cxxabi",
   232     gcc_rtti_abi = conf.check_nonfatal(fragment=fragment, msg="Checking for internal GCC cxxabi",
   234                               okmsg="complete", errmsg='incomplete',
   233                                        okmsg="complete", errmsg='incomplete',
   235                               mandatory=False)
   234                                        mandatory=False)
   236     conf.env["GCC_RTTI_ABI_COMPLETE"] = str(bool(gcc_rtti_abi))
   235     conf.env["GCC_RTTI_ABI_COMPLETE"] = str(bool(gcc_rtti_abi))
   237 
   236 
   238 
   237 
   239 
   238 
   240     ## Check for pygccxml
   239     ## Check for pygccxml
   243     except Configure.ConfigurationError:
   242     except Configure.ConfigurationError:
   244         conf.report_optional_feature("pygccxml", "Python API Scanning Support", False,
   243         conf.report_optional_feature("pygccxml", "Python API Scanning Support", False,
   245                                      "Missing 'pygccxml' Python module")
   244                                      "Missing 'pygccxml' Python module")
   246         return
   245         return
   247 
   246 
   248     out = subprocess.Popen([conf.env['PYTHON'], "-c",
   247     out = subprocess.Popen([conf.env['PYTHON'][0], "-c",
   249                             "import pygccxml; print pygccxml.__version__"],
   248                             "import pygccxml; print pygccxml.__version__"],
   250                             stdout=subprocess.PIPE).communicate()[0]
   249                             stdout=subprocess.PIPE).communicate()[0]
   251     pygccxml_version_str = out.strip()
   250     pygccxml_version_str = out.strip()
   252     pygccxml_version = tuple([int(x) for x in pygccxml_version_str.split('.')])
   251     pygccxml_version = tuple([int(x) for x in pygccxml_version_str.split('.')])
   253     conf.check_message('pygccxml', 'version',
   252     conf.msg('Checking for pygccxml version', pygccxml_version_str)
   254                        (pygccxml_version >= REQUIRED_PYGCCXML_VERSION),
       
   255                        pygccxml_version_str)
       
   256     if not (pygccxml_version >= REQUIRED_PYGCCXML_VERSION):
   253     if not (pygccxml_version >= REQUIRED_PYGCCXML_VERSION):
   257         Logs.warn("pygccxml (found %s) is too old (need %s) => "
   254         Logs.warn("pygccxml (found %s) is too old (need %s) => "
   258                 "automatic scanning of API definitions will not be possible" %
   255                 "automatic scanning of API definitions will not be possible" %
   259                 (pygccxml_version_str,
   256                 (pygccxml_version_str,
   260                  '.'.join([str(x) for x in REQUIRED_PYGCCXML_VERSION])))
   257                  '.'.join([str(x) for x in REQUIRED_PYGCCXML_VERSION])))
   273 
   270 
   274     gccxml_version_line = os.popen(gccxml + " --version").readline().strip()
   271     gccxml_version_line = os.popen(gccxml + " --version").readline().strip()
   275     m = re.match( "^GCC-XML version (\d\.\d(\.\d)?)$", gccxml_version_line)
   272     m = re.match( "^GCC-XML version (\d\.\d(\.\d)?)$", gccxml_version_line)
   276     gccxml_version = m.group(1)
   273     gccxml_version = m.group(1)
   277     gccxml_version_ok = ([int(s) for s in gccxml_version.split('.')] >= [0, 9])
   274     gccxml_version_ok = ([int(s) for s in gccxml_version.split('.')] >= [0, 9])
   278     conf.check_message('gccxml', 'version', True, gccxml_version)
   275     conf.msg('Checking for gccxml version', gccxml_version)
   279     if not gccxml_version_ok:
   276     if not gccxml_version_ok:
   280         Logs.warn("gccxml too old, need version >= 0.9; automatic scanning of API definitions will not be possible")
   277         Logs.warn("gccxml too old, need version >= 0.9; automatic scanning of API definitions will not be possible")
   281         conf.report_optional_feature("pygccxml", "Python API Scanning Support", False,
   278         conf.report_optional_feature("pygccxml", "Python API Scanning Support", False,
   282                                      "gccxml too old")
   279                                      "gccxml too old")
   283         return
   280         return
   309     return ns3headers.path.abspath()
   306     return ns3headers.path.abspath()
   310 
   307 
   311 class apiscan_task(Task.TaskBase):
   308 class apiscan_task(Task.TaskBase):
   312     """Uses gccxml to scan the file 'everything.h' and extract API definitions.
   309     """Uses gccxml to scan the file 'everything.h' and extract API definitions.
   313     """
   310     """
   314     after = 'gen_ns3_module_header_task ns3header_task'
   311     after = 'gen_ns3_module_header ns3header'
   315     before = 'cc cxx gchx'
   312     before = 'cc cxx gchx'
   316     color = "BLUE"
   313     color = "BLUE"
   317     def __init__(self, curdirnode, env, bld, target, cflags, module):
   314     def __init__(self, curdirnode, env, bld, target, cflags, module):
   318         self.bld = bld
   315         self.bld = bld
   319         super(apiscan_task, self).__init__(generator=self)
   316         super(apiscan_task, self).__init__(generator=self)
   376         retval[module_name] = (list(module.module_deps), headers)
   373         retval[module_name] = (list(module.module_deps), headers)
   377     return retval
   374     return retval
   378 
   375 
   379 
   376 
   380 
   377 
   381 class python_scan_task(Task.TaskBase):
       
   382     """Uses gccxml to scan the file 'everything.h' and extract API definitions.
       
   383     """
       
   384     after = 'gen_everything_h_task'
       
   385     before = 'cc cxx gchx'
       
   386     color = "BLUE"
       
   387     def __init__(self, curdirnode, env, bld, target, cflags):
       
   388         self.bld = bld
       
   389         super(python_scan_task, self).__init__(generator=self)
       
   390         self.curdirnode = curdirnode
       
   391         self.env = env
       
   392         self.target = target
       
   393         self.cflags = cflags
       
   394 
       
   395     def display(self):
       
   396         return 'python-scan-%s\n' % (self.target,)
       
   397 
       
   398     def run(self):
       
   399         defsdir = os.path.join(self.curdirnode.abspath(), 'apidefs', self.target)
       
   400         try:
       
   401             os.mkdir(defsdir)
       
   402         except OSError:
       
   403             pass
       
   404         argv = [
       
   405             self.env['PYTHON'],
       
   406             os.path.join(self.curdirnode.abspath(), 'ns3modulescan.py'), # scanning script
       
   407             self.curdirnode.find_dir('../..').abspath(self.env), # include path (where the ns3 include dir is)
       
   408             self.curdirnode.find_or_declare('everything.h').abspath(self.env),
       
   409             os.path.join(defsdir, 'ns3modulegen_generated.py'), # output file
       
   410             self.cflags,
       
   411             ]
       
   412         scan = subprocess.Popen(argv, stdin=subprocess.PIPE)
       
   413         print >> scan.stdin, repr(get_modules_and_headers(self.bld))
       
   414         scan.stdin.close()
       
   415         retval = scan.wait()
       
   416         return retval
       
   417 
   378 
   418 class python_scan_task_collector(Task.TaskBase):
   379 class python_scan_task_collector(Task.TaskBase):
   419     """Tasks that waits for the python-scan-* tasks to complete and then signals WAF to exit
   380     """Tasks that waits for the python-scan-* tasks to complete and then signals WAF to exit
   420     """
   381     """
   421     after = 'python_scan_task apiscan_task'
   382     after = 'apiscan'
   422     before = 'cc cxx'
   383     before = 'cc cxx'
   423     color = "BLUE"
   384     color = "BLUE"
   424     def __init__(self, curdirnode, env, bld):
   385     def __init__(self, curdirnode, env, bld):
   425         self.bld = bld
   386         self.bld = bld
   426         super(python_scan_task_collector, self).__init__(generator=self)
   387         super(python_scan_task_collector, self).__init__(generator=self)
   510         python_scan_task_collector(bld.path, env, bld)
   471         python_scan_task_collector(bld.path, env, bld)
   511         return
   472         return
   512 
   473 
   513 
   474 
   514     if env['ENABLE_PYTHON_BINDINGS']:
   475     if env['ENABLE_PYTHON_BINDINGS']:
   515         task = gen_ns3_compat_pymod_task(env)
   476         task = gen_ns3_compat_pymod_task(env=env)
   516         task.set_outputs(bld.path.find_or_declare("ns3.py"))
   477         task.set_outputs(bld.path.find_or_declare("ns3.py"))
   517         task.dep_vars = ['PYTHON_MODULES_BUILT']
   478         task.dep_vars = ['PYTHON_MODULES_BUILT']
   518 
   479 
   519     # note: the actual build commands for the python bindings are in
   480     # note: the actual build commands for the python bindings are in
   520     # src/wscript, not here.
   481     # src/wscript, not here.