bindings/python/wscript
changeset 3408 2cc40b3e4fa5
child 3409 94ac3e381075
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/wscript	Tue Jul 08 10:43:58 2008 -0700
@@ -0,0 +1,367 @@
+## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+
+import re
+import Params
+import Configure
+import Object
+import Action
+import os
+import Task
+import pproc as subprocess
+from Params import fatal, warning
+import shutil
+
+## Adjust python path to look for our local copy of pybindgen
+LOCAL_PYBINDGEN_PATH = os.path.join(os.getcwd(), "bindings", "python", "pybindgen")
+#PYBINDGEN_BRANCH = 'lp:pybindgen'
+PYBINDGEN_BRANCH = 'https://launchpad.net/pybindgen'
+if os.environ.get('PYTHONPATH', ''):
+    os.environ['PYTHONPATH'] = LOCAL_PYBINDGEN_PATH + os.pathsep + os.environ.get('PYTHONPATH')
+else:
+    os.environ['PYTHONPATH'] = LOCAL_PYBINDGEN_PATH
+
+## https://launchpad.net/pybindgen/
+REQUIRED_PYBINDGEN_VERSION = (0, 8, 0, 475)
+REQUIRED_PYGCCXML_VERSION = (0, 9, 5)
+
+
+def set_options(opt):
+    opt.tool_options('python')
+    opt.add_option('--python-disable',
+                   help=("Don't build Python bindings."),
+                   action="store_true", default=False,
+                   dest='python_disable')
+    opt.add_option('--python-scan',
+                   help=("Rescan Python bindings.  Needs working GCCXML / pygccxml environment."),
+                   action="store_true", default=False,
+                   dest='python_scan')
+    opt.add_option('--pybindgen-checkout',
+                   help=("During configure, force checkout of pybingen inside ns-3, "
+                         "instead of using the system installed version."),
+                   action="store_true", default=False,
+                   dest='pybindgen_checkout')
+
+def fetch_pybindgen(conf):
+    """
+    Fetches pybindgen from launchpad as bindings/python/pybindgen.
+    Returns True if successful, False it not.
+    """
+    bzr = conf.find_program("bzr")
+    if not bzr:
+        warning("the program 'bzr' is needed in order to fetch pybindgen")
+        return False
+    if len(REQUIRED_PYBINDGEN_VERSION) == 4:
+        rev = "-rrevno:%i" % REQUIRED_PYBINDGEN_VERSION[3]
+    else:
+        rev = "-rtag:%s" % '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])
+    if os.path.exists(LOCAL_PYBINDGEN_PATH):
+        print "Trying to update pybindgen; this will fail if no network connection is available."
+
+        cmd = [bzr, "pull", rev, PYBINDGEN_BRANCH]
+        print " => ", ' '.join(cmd)
+        if subprocess.Popen(cmd, cwd=LOCAL_PYBINDGEN_PATH).wait():
+            return False
+        print "Update was successful."
+    else:
+        print "Trying to fetch pybindgen; this will fail if no network connection is available."
+        cmd = [bzr, "checkout", rev, PYBINDGEN_BRANCH, LOCAL_PYBINDGEN_PATH]
+        print " => ", ' '.join(cmd)
+        if subprocess.Popen(cmd).wait():
+            return False
+        print "Fetch was successful."
+
+    ## generate a fake version.py file in pybindgen it's safer this
+    ## way, since the normal version generation process requires
+    ## bazaar python bindings, which may not be available.
+    vfile = open(os.path.join(LOCAL_PYBINDGEN_PATH, "pybindgen", "version.py"), "wt")
+    vfile.write("""
+# (fake version generated by ns-3)
+__version__ = %r
+""" % list(REQUIRED_PYBINDGEN_VERSION))
+    vfile.close()
+
+    return True
+
+
+def configure(conf):
+    conf.env['ENABLE_PYTHON_BINDINGS'] = False
+    if Params.g_options.python_disable:
+        return
+
+    conf.check_tool('misc')
+
+    ## Check for Python
+    try:
+        conf.check_tool('python')
+        conf.check_python_version((2,4,2))
+        conf.check_python_headers()
+    except Configure.ConfigurationError:
+        return
+
+    ## Check for pybindgen
+    if Params.g_options.pybindgen_checkout:
+        fetch_pybindgen(conf)
+
+    try:
+        conf.check_python_module('pybindgen')
+    except Configure.ConfigurationError:
+        warning("pybindgen missing")
+        if not fetch_pybindgen(conf):
+            return
+    else:
+        out = subprocess.Popen([conf.env['PYTHON'], "-c",
+                                "import pybindgen.version; "
+                                "print '.'.join([str(x) for x in pybindgen.version.__version__])"],
+                                stdout=subprocess.PIPE).communicate()[0]
+        pybindgen_version_str = out.strip()
+        pybindgen_version = tuple([int(x) for x in pybindgen_version_str.split('.')])
+        conf.check_message('pybindgen', 'version',
+                           (pybindgen_version >= REQUIRED_PYBINDGEN_VERSION),
+                           pybindgen_version_str)
+        if not (pybindgen_version >= REQUIRED_PYBINDGEN_VERSION):
+            warning("pybindgen (found %s) is too old (need %s)" %
+                    (pybindgen_version_str,
+                     '.'.join([str(x) for x in REQUIRED_PYBINDGEN_VERSION])))
+            if not fetch_pybindgen(conf):
+                return
+
+    ## If all has gone well, we finally enable the Python bindings
+    conf.env['ENABLE_PYTHON_BINDINGS'] = True
+
+    ## Check for pygccxml
+    try:
+        conf.check_python_module('pygccxml')
+    except Configure.ConfigurationError:
+        return
+
+    out = subprocess.Popen([conf.env['PYTHON'], "-c",
+                            "import pygccxml; print pygccxml.__version__"],
+                            stdout=subprocess.PIPE).communicate()[0]
+    pygccxml_version_str = out.strip()
+    pygccxml_version = tuple([int(x) for x in pygccxml_version_str.split('.')])
+    conf.check_message('pygccxml', 'version',
+                       (pygccxml_version >= REQUIRED_PYGCCXML_VERSION),
+                       pygccxml_version_str)
+    if not (pygccxml_version >= REQUIRED_PYGCCXML_VERSION):
+        warning("pygccxml (found %s) is too old (need %s) => "
+                "automatic scanning of API definitions will not be possible" %
+                (pygccxml_version_str,
+                 '.'.join([str(x) for x in REQUIRED_PYGCCXML_VERSION])))
+        return
+    
+
+    ## Check gccxml version
+    gccxml = conf.find_program('gccxml', var='GCCXML')
+    if not gccxml:
+        warning("gccxml missing; automatic scanning of API definitions will not be possible")
+        return
+
+    gccxml_version_line = os.popen(gccxml + " --version").readline().strip()
+    m = re.match( "^GCC-XML version (\d\.\d(\.\d)?)$", gccxml_version_line)
+    gccxml_version = m.group(1)
+    gccxml_version_ok = ([int(s) for s in gccxml_version.split('.')] >= [0, 9])
+    conf.check_message('gccxml', 'version', True, gccxml_version)
+    if not gccxml_version_ok:
+        warning("gccxml too old, need version >= 0.9; automatic scanning of API definitions will not be possible")
+        return
+    
+    ## If we reached
+    conf.env['ENABLE_PYTHON_SCANNING'] = True
+
+
+prio_headers = {
+    -2: (
+        "string.h", # work around http://www.gccxml.org/Bug/view.php?id=6682
+        ),
+    -1: (
+        "propagation-delay-model.h",
+        "propagation-loss-model.h",
+        "net-device.h",
+        )
+     }
+
+def get_header_prio(header):
+    for prio, headers in prio_headers.iteritems():
+        if header in headers:
+            return prio
+    return 1
+
+def gen_ns3_metaheader(task):
+    assert len(task.m_outputs) == 1
+    header_files = [os.path.basename(node.abspath(task.m_env)) for node in task.m_inputs]
+    outfile = file(task.m_outputs[0].bldpath(task.m_env), "w")
+
+    def sort_func(h1, h2):
+        return cmp((get_header_prio(h1), h1), (get_header_prio(h1), h2))
+
+    header_files.sort(sort_func)
+
+    for header in header_files:
+        print >> outfile, "#include \"ns3/%s\"" % (header,)
+
+    print >> outfile, """
+namespace ns3 {
+static inline Ptr<Object>
+__dummy_function_to_force_template_instantiation (Ptr<Object> obj, TypeId typeId)
+{
+   return obj->GetObject<Object> (typeId);
+}
+
+}
+"""
+    outfile.close()
+    return 0
+
+
+class all_ns3_headers_taskgen(Object.task_gen):
+    """Generates a 'everything.h' header file that includes some/all public ns3 headers.
+    This single header file is to be parsed only once by gccxml, for greater efficiency.
+    """
+    def __init__(self, *features):
+        Object.task_gen.__init__(self, *features)
+        self.inst_var = 'INCLUDEDIR'
+        self.inst_dir = 'ns3'
+
+    def apply(self):
+        ## get all of the ns3 headers
+        ns3_dir_node = Params.g_build.m_srcnode.find_dir("ns3")
+        all_headers_inputs = []
+
+        for filename in self.to_list(self.source):
+            src_node = ns3_dir_node.find_build(filename)
+            if src_node is None:
+                Params.fatal("source ns3 header file %s not found" % (filename,))
+            all_headers_inputs.append(src_node)
+
+        ## if self.source was empty, include all ns3 headers in enabled modules
+        if not all_headers_inputs:
+            for ns3headers in Object.g_allobjs:
+                if type(ns3headers).__name__ == 'ns3header_taskgen': # XXX: find less hackish way to compare
+                    ## skip headers not part of enabled modules
+                    if self.env['NS3_ENABLED_MODULES']:
+                        if ("ns3-%s" % ns3headers.module) not in self.env['NS3_ENABLED_MODULES']:
+                            continue
+
+                    for source in ns3headers.to_list(ns3headers.source):
+                        source = os.path.basename(source)
+                        node = ns3_dir_node.find_build(os.path.basename(source))
+                        if node is None:
+                            fatal("missing header file %s" % (source,))
+                        all_headers_inputs.append(node)
+        assert all_headers_inputs
+        all_headers_outputs = [ns3_dir_node.find_build("everything.h")]
+        task = self.create_task('gen-ns3-metaheader', self.env, 4)
+        task.set_inputs(all_headers_inputs)
+        task.set_outputs(all_headers_outputs)
+
+    def install(self):
+        pass
+
+
+def get_modules_and_headers():
+    """
+    Gets a dict of
+       module_name => ([module_dep1, module_dep2, ...], [module_header1, module_header2, ...])
+    tuples, one for each module.
+    """
+
+    retval = {}
+    for module in Object.g_allobjs:
+        if not module.name.startswith('ns3-'):
+            continue
+        module_name = module.name[4:] # strip the ns3- prefix
+        ## find the headers object for this module
+        headers = []
+        for ns3headers in Object.g_allobjs:
+            if type(ns3headers).__name__ != 'ns3header_taskgen': # XXX: find less hackish way to compare
+                continue
+            if ns3headers.module != module_name:
+                continue
+            for source in ns3headers.to_list(ns3headers.source):
+                headers.append(source)
+        retval[module_name] = (list(module.module_deps), headers)
+    return retval
+
+
+def build(bld):
+    if Params.g_options.python_disable:
+        return
+
+    env = bld.env_of_name('default')
+    #Object.register('all-ns3-headers', AllNs3Headers)
+    Action.Action('gen-ns3-metaheader', func=gen_ns3_metaheader, color='BLUE')
+
+    obj = bld.create_obj('all_ns3_headers')
+
+    if Params.g_options.python_scan:
+        if not env['ENABLE_PYTHON_SCANNING']:
+            Params.fatal("Cannot re-scan python bindings: (py)gccxml not available")
+        print "Rescanning the python bindings..."
+        curdir = bld.m_curdirnode.abspath()
+        argv = [
+            env['PYTHON'],
+            os.path.join(curdir, 'ns3modulescan.py'), # scanning script
+            bld.m_curdirnode.find_dir('../..').abspath(env), # include path (where the ns3 include dir is)
+            os.path.join(curdir, 'ns3modulegen_generated.py'), # output file
+            ]
+        scan = subprocess.Popen(argv, stdin=subprocess.PIPE)
+        scan.stdin.write(repr(get_modules_and_headers()))
+        scan.stdin.close()
+        if scan.wait():
+            raise SystemExit(1)
+        print "Rescanning the python bindings done."
+        raise SystemExit
+
+    if env['ENABLE_PYTHON_BINDINGS']:
+        bindgen = bld.create_obj('command-output')
+        bindgen.name = 'pybindgen'
+
+        bindgen.command = env['PYTHON']
+        bindgen.command_is_external = True
+        bindgen.argv = [
+            bindgen.input_file("ns3modulegen.py"),
+            bindgen.output_file("ns3module.cc"),
+            ]
+        bindgen.argv.extend(get_modules_and_headers().iterkeys())
+        bindgen.hidden_inputs = ['../../ns3/everything.h',
+                                 'ns3modulegen_generated.py',
+                                 'ns3modulegen_core_customizations.py']
+        for module in get_modules_and_headers().iterkeys():
+            bindgen.hidden_inputs.append("ns3_module_%s.py" % module.replace('-', '_'))
+        bindgen.hidden_outputs = ['ns3module.h']
+        for module in get_modules_and_headers().iterkeys():
+            bindgen.hidden_outputs.append("ns3_module_%s.cc" % module.replace('-', '_'))
+        bindgen.prio = 50
+
+
+    ## we build python bindings if either we have the tools to
+    ## generate them or if the pregenerated source file is already
+    ## present in the source dir.
+    if env['ENABLE_PYTHON_BINDINGS'] \
+            or os.path.exists(os.path.join(bld.m_curdirnode.abspath(), 'ns3module.cc')):
+        pymod = bld.create_obj('cpp', 'shlib', 'pyext')
+        pymod.source = ['ns3module.cc', 'ns3module_helpers.cc']
+        pymod.includes = '.'
+        for module in get_modules_and_headers().iterkeys():
+            pymod.source.append("ns3_module_%s.cc" % module.replace('-', '_'))
+        pymod.target = 'ns3/_ns3'
+        pymod.name = 'ns3module'
+        pymod.uselib_local = "ns3"
+
+        # copy the __init__.py file to the build dir waf can't handle
+        # this, it's against waf's principles to have build dir files
+        # with the same name as source dir files, apparently.
+        dirnode = bld.m_curdirnode.find_dir('ns3')
+        src = os.path.join(dirnode.abspath(), '__init__.py')
+        dst = os.path.join(dirnode.abspath(env), '__init__.py')
+        try:
+            need_copy = os.stat(src).st_mtime > os.stat(dst).st_mtime
+        except OSError:
+            need_copy = True
+        if need_copy:
+            try:
+                os.mkdir(os.path.dirname(dst))
+            except OSError:
+                pass
+            print "%r -> %r" % (src, dst)
+            shutil.copy2(src, dst)