Start of work on modular Python bindings, unstable
authorGustavo J. A. M. Carneiro <gjc@inescporto.pt>
Sun, 07 Nov 2010 23:17:52 +0000
changeset 6874 c7537e62f2fa
parent 6651 4bd42b7fbb3b
child 6875 106abe4a32f0
Start of work on modular Python bindings, unstable
bindings/python/ns/__init__.py
bindings/python/ns3modulegen-modular.py
bindings/python/ns3modulescan-modular.py
bindings/python/wscript
src/common/wscript
src/core/wscript
src/node/wscript
src/simulator/wscript
src/wscript
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/ns/__init__.py	Sun Nov 07 23:17:52 2010 +0000
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/ns3modulegen-modular.py	Sun Nov 07 23:17:52 2010 +0000
@@ -0,0 +1,54 @@
+import warnings
+import sys
+import os
+import pybindgen.settings
+from pybindgen import Module, FileCodeSink, param, retval, cppclass, typehandlers
+import ns3modulegen_core_customizations
+
+class ErrorHandler(pybindgen.settings.ErrorHandler):
+    def handle_error(self, wrapper, exception, traceback_):
+        warnings.warn("exception %r in wrapper %s" % (exception, wrapper))
+        return True
+pybindgen.settings.error_handler = ErrorHandler()
+
+
+
+
+def main(argv):
+    assert len(argv) == 3
+    module_abs_src_path, target = argv[1], argv[2]
+    module_name = os.path.basename(module_abs_src_path)
+
+    sys.path.insert(0, os.path.join(module_abs_src_path, "bindings"))
+    try:
+        module_apidefs = __import__("modulegen__%s" % target)
+        del sys.modules["modulegen__%s" % target]
+        try:
+            module_customization = __import__("modulegen_local")
+            del sys.modules["modulegen_local"]
+        except ImportError:
+            module_customization = None
+    finally:
+        sys.path.pop(0)
+    
+    out = FileCodeSink(sys.stdout)
+    root_module = module_apidefs.module_init()
+    root_module.add_include('"ns3/%s-module.h"' % module_name)
+    module_apidefs.register_types(root_module)
+
+    module_apidefs.register_methods(root_module)
+    module_apidefs.register_functions(root_module)
+    
+    ns3modulegen_core_customizations.Object_customizations(root_module)
+    ns3modulegen_core_customizations.Attribute_customizations(root_module)
+
+    if module_customization is not None:
+        module_customization.customize(root_module)
+
+    root_module.generate(out)
+
+if __name__ == '__main__':
+    import sys
+    main(sys.argv)
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/ns3modulescan-modular.py	Sun Nov 07 23:17:52 2010 +0000
@@ -0,0 +1,301 @@
+#! /usr/bin/env python
+
+import sys
+import os.path
+
+import pybindgen.settings
+from pybindgen.gccxmlparser import ModuleParser, PygenClassifier, PygenSection, WrapperWarning
+from pybindgen.typehandlers.codesink import FileCodeSink
+from pygccxml.declarations import templates
+from pygccxml.declarations.enumeration import enumeration_t
+from pygccxml.declarations.class_declaration import class_t
+from pygccxml.declarations.calldef import free_function_t, member_function_t, constructor_t, calldef_t
+
+
+## we need the smart pointer type transformation to be active even
+## during gccxml scanning.
+import ns3modulegen_core_customizations
+
+
+## silence gccxmlparser errors; we only want error handling in the
+## generated python script, not while scanning.
+class ErrorHandler(pybindgen.settings.ErrorHandler):
+    def handle_error(self, dummy_wrapper, dummy_exception, dummy_traceback_):
+        return True
+pybindgen.settings.error_handler = ErrorHandler()
+import warnings
+warnings.filterwarnings(category=WrapperWarning, action='ignore')
+
+type_annotations = {
+    '::ns3::AttributeChecker': {
+        'automatic_type_narrowing': 'true',
+        'allow_subclassing': 'false',
+        },
+    '::ns3::AttributeValue': {
+        'automatic_type_narrowing': 'true',
+        'allow_subclassing': 'false',
+        },
+
+    '::ns3::CommandLine': {
+        'allow_subclassing': 'true', # needed so that AddValue is able to set attributes on the object
+        },
+
+    '::ns3::NscTcpL4Protocol': {
+        'ignore': 'true', # this class is implementation detail
+        },
+
+
+    'ns3::RandomVariable::RandomVariable(ns3::RandomVariableBase const & variable) [constructor]': {
+        'ignore': None,
+        },
+    'ns3::RandomVariableBase * ns3::RandomVariable::Peek() const [member function]': {
+        'ignore': None,
+        },
+    'void ns3::RandomVariable::GetSeed(uint32_t * seed) const [member function]': {
+        'params': {'seed':{'direction':'out',
+                           'array_length':'6'}}
+        },
+    'bool ns3::TypeId::LookupAttributeByName(std::string name, ns3::TypeId::AttributeInfo * info) const [member function]': {
+        'params': {'info':{'transfer_ownership': 'false'}}
+        },
+    'static bool ns3::TypeId::LookupByNameFailSafe(std::string name, ns3::TypeId * tid) [member function]': {
+        'ignore': None, # manually wrapped in 
+        },
+    'bool ns3::TraceSourceAccessor::ConnectWithoutContext(ns3::ObjectBase * obj, ns3::CallbackBase const & cb) const [member function]': {
+        'params': {'obj': {'transfer_ownership':'false'}}
+        },
+    'bool ns3::TraceSourceAccessor::Connect(ns3::ObjectBase * obj, std::string context, ns3::CallbackBase const & cb) const [member function]': {
+        'params': {'obj': {'transfer_ownership':'false'}}
+        },
+    'bool ns3::TraceSourceAccessor::DisconnectWithoutContext(ns3::ObjectBase * obj, ns3::CallbackBase const & cb) const [member function]': {
+        'params': {'obj': {'transfer_ownership':'false'}}
+        },
+    'bool ns3::TraceSourceAccessor::Disconnect(ns3::ObjectBase * obj, std::string context, ns3::CallbackBase const & cb) const [member function]': {
+        'params': {'obj': {'transfer_ownership':'false'}}
+        },
+    'bool ns3::AttributeAccessor::Set(ns3::ObjectBase * object, ns3::AttributeValue const & value) const [member function]': {
+        'params': {'object': {'transfer_ownership':'false'}}
+        },
+    'ns3::EmpiricalVariable::EmpiricalVariable(ns3::RandomVariableBase const & variable) [constructor]': {
+        'ignore': None
+        },
+    'static ns3::AttributeList * ns3::AttributeList::GetGlobal() [member function]': {
+        'caller_owns_return': 'false'
+        },
+    'void ns3::CommandLine::Parse(int argc, char * * argv) const [member function]': {
+        'ignore': None # manually wrapped
+        },
+    'extern void ns3::PythonCompleteConstruct(ns3::Ptr<ns3::Object> object, ns3::TypeId typeId, ns3::AttributeList const & attributes) [free function]': {
+        'ignore': None # used transparently by, should not be wrapped
+        },
+
+    'ns3::Ptr<ns3::Ipv4RoutingProtocol> ns3::Ipv4ListRouting::GetRoutingProtocol(uint32_t index, int16_t & priority) const [member function]': {
+        'params': {'priority':{'direction':'out'}}
+        },
+    'ns3::Ipv4RoutingTableEntry * ns3::GlobalRouter::GetInjectedRoute(uint32_t i) [member function]': {
+        'params': {'return': { 'caller_owns_return': 'false',}},
+        },
+    'ns3::Ipv4RoutingTableEntry * ns3::Ipv4GlobalRouting::GetRoute(uint32_t i) [member function]': {
+        'params': {'return': { 'caller_owns_return': 'false',}},
+        },
+    
+    }
+
+def get_ns3_relative_path(path):
+    l = []
+    head = path
+    while head:
+        head, tail = os.path.split(head)
+        if tail == 'ns3':
+            return os.path.join(*l)
+        l.insert(0, tail)
+    raise AssertionError("is the path %r inside ns3?!" % path)
+
+class PreScanHook:
+
+    def __init__(self, headers_map, module):
+        self.headers_map = headers_map
+        self.module = module
+
+    def __call__(self, dummy_module_parser,
+                 pygccxml_definition,
+                 global_annotations,
+                 parameter_annotations):
+        ns3_header = get_ns3_relative_path(pygccxml_definition.location.file_name)
+        definition_module = self.headers_map[ns3_header]
+
+        ## Note: we don't include line numbers in the comments because
+        ## those numbers are very likely to change frequently, which would
+        ## cause needless changes, since the generated python files are
+        ## kept under version control.
+
+        #global_annotations['pygen_comment'] = "%s:%i: %s" % \
+        #    (ns3_header, pygccxml_definition.location.line, pygccxml_definition)
+        global_annotations['pygen_comment'] = "%s (module %r): %s" % \
+            (ns3_header, definition_module, pygccxml_definition)
+
+
+        ## handle ns3::Object::GetObject (left to its own devices,
+        ## pybindgen will generate a mangled name containing the template
+        ## argument type name).
+        if isinstance(pygccxml_definition, member_function_t) \
+                and pygccxml_definition.parent.name == 'Object' \
+                and pygccxml_definition.name == 'GetObject':
+            template_args = templates.args(pygccxml_definition.demangled_name)
+            if template_args == ['ns3::Object']:
+                global_annotations['template_instance_names'] = 'ns3::Object=>GetObject'
+
+        ## Don't wrap Simulator::Schedule* (manually wrapped)
+        if isinstance(pygccxml_definition, member_function_t) \
+                and pygccxml_definition.parent.name == 'Simulator' \
+                and pygccxml_definition.name.startswith('Schedule'):
+            global_annotations['ignore'] = None
+
+        # manually wrapped
+        if isinstance(pygccxml_definition, member_function_t) \
+                and pygccxml_definition.parent.name == 'Simulator' \
+                and pygccxml_definition.name == 'Run':
+            global_annotations['ignore'] = True
+
+        ## http://www.gccxml.org/Bug/view.php?id=9915
+        if isinstance(pygccxml_definition, calldef_t):
+            for arg in pygccxml_definition.arguments:
+                if arg.default_value is None:
+                    continue
+                if "ns3::MilliSeconds( )" == arg.default_value:
+                    arg.default_value = "ns3::MilliSeconds(0)"
+
+        ## classes
+        if isinstance(pygccxml_definition, class_t):
+            # no need for helper classes to allow subclassing in Python, I think...
+            #if pygccxml_definition.name.endswith('Helper'):
+            #    global_annotations['allow_subclassing'] = 'false'
+
+            if definition_module != self.module:
+                global_annotations['import_from_module'] = 'ns.%s' % definition_module
+
+            if pygccxml_definition.decl_string.startswith('::ns3::SimpleRefCount<'):
+                global_annotations['incref_method'] = 'Ref'
+                global_annotations['decref_method'] = 'Unref'
+                global_annotations['peekref_method'] = 'GetReferenceCount'
+                global_annotations['automatic_type_narrowing'] = 'true'
+                return
+
+            if pygccxml_definition.decl_string.startswith('::ns3::Callback<'):
+                # manually handled in ns3modulegen_core_customizations.py
+                global_annotations['ignore'] = None
+                return
+
+            if pygccxml_definition.decl_string.startswith('::ns3::TracedCallback<'):
+                global_annotations['ignore'] = None
+                return
+
+            if pygccxml_definition.decl_string.startswith('::ns3::Ptr<'):
+                # handled by pybindgen "type transformation"
+                global_annotations['ignore'] = None
+                return
+
+            # table driven class customization
+            try:
+                annotations = type_annotations[pygccxml_definition.decl_string]
+            except KeyError:
+                pass
+            else:
+                global_annotations.update(annotations)
+
+        ## enums
+        if isinstance(pygccxml_definition, enumeration_t):
+            if definition_module != self.module:
+                global_annotations['import_from_module'] = 'ns.%s' % definition_module
+
+        ## free functions
+        if isinstance(pygccxml_definition, free_function_t):
+
+            if definition_module != self.module:
+                global_annotations['ignore'] = None
+                return
+
+            if pygccxml_definition.name == 'PeekPointer':
+                global_annotations['ignore'] = None
+                return
+
+        ## table driven methods/constructors/functions customization
+        if isinstance(pygccxml_definition, (free_function_t, member_function_t, constructor_t)):
+            try:
+                annotations = type_annotations[str(pygccxml_definition)]
+            except KeyError:
+                pass
+            else:
+                for key,value in annotations.items():
+                    if key == 'params':
+                        parameter_annotations.update (value)
+                        del annotations['params']
+                global_annotations.update(annotations)
+
+
+# def post_scan_hook(dummy_module_parser, dummy_pygccxml_definition, pybindgen_wrapper):
+#     ## classes
+#     if isinstance(pybindgen_wrapper, CppClass):
+#         if pybindgen_wrapper.name.endswith('Checker'):
+#             print >> sys.stderr, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", pybindgen_wrapper
+#             #pybindgen_wrapper.set_instance_creation_function(AttributeChecker_instance_creation_function)
+
+
+def scan_callback_classes(module_parser, callback_classes_file):
+    callback_classes_file.write("callback_classes = [\n")
+    for cls in module_parser.module_namespace.classes(function=module_parser.location_filter,
+                                                      recursive=False):
+        if not cls.name.startswith("Callback<"):
+            continue
+        assert templates.is_instantiation(cls.decl_string), "%s is not a template instantiation" % cls
+        dummy_cls_name, template_parameters = templates.split(cls.decl_string)
+        callback_classes_file.write("    %r,\n" % template_parameters)
+    callback_classes_file.write("]\n")
+
+
+def ns3_module_scan(top_builddir, module_name, headers_map, output_file_name, cflags):
+    module_parser = ModuleParser('ns.%s' % module_name, 'ns3')
+    module_parser.add_pre_scan_hook(PreScanHook(headers_map, module_name))
+    #module_parser.add_post_scan_hook(post_scan_hook)
+
+    gccxml_options = dict(
+        include_paths=[top_builddir],
+         define_symbols={
+            #'NS3_ASSERT_ENABLE': None,
+            #'NS3_LOG_ENABLE': None,
+            },
+        cflags=('--gccxml-cxxflags %r' % (cflags,))
+        )
+
+    try:
+        os.unlink(output_file_name)
+    except OSError:
+        pass
+    output_file = open(output_file_name, "wt")
+    output_sink = FileCodeSink(output_file)
+    module_parser.parse_init([os.path.join(top_builddir, "ns3", "%s-module.h" % module_name)],
+                             None, whitelist_paths=[top_builddir],
+                             #includes=['"ns3/everything.h"'],
+                             pygen_sink=output_sink,
+                             gccxml_options=gccxml_options)
+    module_parser.scan_types()
+
+    #callback_classes_file = open(os.path.join(os.path.dirname(pygen_file_name), "callbacks_list.py"), "wt")
+    #scan_callback_classes(module_parser, callback_classes_file)
+    #callback_classes_file.close()
+
+
+    module_parser.scan_methods()
+    module_parser.scan_functions()
+    module_parser.parse_finalize()
+
+    output_file.close()
+    os.chmod(output_file_name, 0400)
+
+
+if __name__ == '__main__':
+    if len(sys.argv) != 6:
+        print "ns3modulescan2.py top_builddir module_path module_headers output_file_name cflags"
+        sys.exit(1)
+    ns3_module_scan(sys.argv[1], sys.argv[2], eval(sys.argv[3]), sys.argv[4], sys.argv[5])
+    sys.exit(0)
--- a/bindings/python/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/bindings/python/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -1,5 +1,5 @@
 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
-
+import types
 import re
 import os
 import pproc as subprocess
@@ -15,7 +15,7 @@
 import Utils
 
 ## https://launchpad.net/pybindgen/
-REQUIRED_PYBINDGEN_VERSION = (0, 15, 0)
+REQUIRED_PYBINDGEN_VERSION = (0, 15, 0, 770)
 REQUIRED_PYGCCXML_VERSION = (0, 9, 5)
 
 
@@ -52,6 +52,11 @@
 
 def set_options(opt):
     opt.tool_options('python')
+    opt.add_option('--bindings-type', type="choice",
+                   choices=("monolithic", "modular", "both"),
+                   help=("Type of Python bindings to build"),
+                   default="monolithic",
+                   dest='bindings_type')
     opt.add_option('--disable-python',
                    help=("Don't build Python bindings."),
                    action="store_true", default=False,
@@ -60,6 +65,9 @@
                    help=("Rescan Python bindings.  Needs working GCCXML / pygccxml environment."),
                    action="store_true", default=False,
                    dest='python_scan')
+    opt.add_option('--apiscan',
+                   help=("EXPERIMENTAL: Rescan module API, for Python bindings.  Needs working GCCXML / pygccxml environment."),
+                   default=None, dest='apiscan', metavar="MODULE[,MODULE...]")
     opt.add_option('--with-pybindgen',
                    help=('Path to an existing pybindgen source tree to use.'),
                    default=None,
@@ -198,7 +206,12 @@
     conf.env['ENABLE_PYTHON_BINDINGS'] = True
     conf.report_optional_feature("python", "Python Bindings", True, None)
 
-    
+    conf.env['BINDINGS_TYPE'] = Options.options.bindings_type
+    if conf.env['BINDINGS_TYPE'] == 'both':
+        msg = "monolithic and modular"
+    else:
+        msg = conf.env['BINDINGS_TYPE']
+    conf.check_message_custom('type of bindings to build', '', msg)
 
     ## Check for pygccxml
     try:
@@ -244,12 +257,73 @@
         conf.report_optional_feature("pygccxml", "Python API Scanning Support", False,
                                      "gccxml too old")
         return
-    
+
     ## If we reached
     conf.env['ENABLE_PYTHON_SCANNING'] = True
     conf.report_optional_feature("pygccxml", "Python API Scanning Support", True, None)
 
+    
 
+
+# ---------------------
+
+def get_headers_map(bld):
+    headers_map = {} # header => module
+    for ns3headers in bld.all_task_gen:
+        if type(ns3headers).__name__ == 'ns3header_taskgen': # XXX: find less hackish way to compare
+            for h in ns3headers.to_list(ns3headers.source):
+                headers_map[os.path.basename(h)] = ns3headers.module
+    return headers_map
+
+def get_module_path(bld, module):
+    for ns3headers in bld.all_task_gen:
+        if type(ns3headers).__name__ == 'ns3header_taskgen': # XXX: find less hackish way to compare
+            if ns3headers.module == module:
+                break
+
+    else:
+        raise ValueError("Module %r not found" % module)
+    return ns3headers.path.abspath()
+
+class apiscan_task(Task.TaskBase):
+    """Uses gccxml to scan the file 'everything.h' and extract API definitions.
+    """
+    after = 'gen_everything_h_task'
+    before = 'cc cxx gchx'
+    color = "BLUE"
+    def __init__(self, curdirnode, env, bld, target, cflags, module):
+        self.bld = bld
+        super(apiscan_task, self).__init__(generator=self)
+        self.curdirnode = curdirnode
+        self.env = env
+        self.target = target
+        self.cflags = cflags
+        self.module = module
+
+    def display(self):
+        return 'api-scan-%s\n' % (self.target,)
+
+    def run(self):
+        top_builddir = self.curdirnode.find_dir('../..').abspath(self.env)
+        module_path = get_module_path(self.bld, self.module)
+        print module_path
+        headers_map = get_headers_map(self.bld)
+        argv = [
+            self.env['PYTHON'],
+            os.path.join(self.curdirnode.abspath(), 'ns3modulescan-modular.py'), # scanning script
+            top_builddir,
+            self.module,
+            repr(get_headers_map(self.bld)),
+            os.path.join(module_path, "bindings", 'modulegen__%s.py' % (self.target)), # output file
+            self.cflags,
+            ]
+        scan = subprocess.Popen(argv, stdin=subprocess.PIPE)
+        retval = scan.wait()
+        return retval
+
+
+
+# --------------
 prio_headers = {
     -2: (
         "string.h", # work around http://www.gccxml.org/Bug/view.php?id=6682
@@ -452,7 +526,7 @@
 class python_scan_task_collector(Task.TaskBase):
     """Tasks that waits for the python-scan-* tasks to complete and then signals WAF to exit
     """
-    after = 'python_scan_task'
+    after = 'python_scan_task apiscan_task'
     before = 'cc cxx'
     color = "BLUE"
     def __init__(self, curdirnode, env, bld):
@@ -481,7 +555,28 @@
 
     set_pybindgen_pythonpath(env)
 
-    if env['ENABLE_PYTHON_BINDINGS']:
+
+
+    # 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.path.find_dir('ns')
+    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)
+
+
+    if env['ENABLE_PYTHON_BINDINGS'] and env['BINDINGS_TYPE'] in ('monolithic', 'both'):
         obj = bld.new_task_gen('all_ns3_headers')
 
     if Options.options.python_scan:
@@ -503,7 +598,28 @@
         python_scan_task_collector(bld.path, env, bld)
         return
 
-    if env['ENABLE_PYTHON_BINDINGS']:
+    if Options.options.apiscan:
+        if not env['ENABLE_PYTHON_SCANNING']:
+            raise Utils.WafError("Cannot re-scan python bindings: (py)gccxml not available")
+        scan_targets = []
+        if sys.platform == 'cygwin':
+            scan_targets.append(('gcc_cygwin', ''))
+        else:
+            import struct
+            if struct.calcsize('I') == 4 and struct.calcsize('L') == 8 and struct.calcsize('P') == 8:
+                scan_targets.extend([('gcc_ILP32', '-m32'), ('gcc_LP64', '-m64')])
+            elif struct.calcsize('I') == 4 and struct.calcsize('L') == 4 and struct.calcsize('P') == 4:
+                scan_targets.append(('gcc_ILP32', ''))
+            else:
+                raise Utils.WafError("Cannot scan python bindings for unsupported data model")
+        for target, cflags in scan_targets:
+            for module in Options.options.apiscan.split(','):
+                apiscan_task(bld.path, env, bld, target, cflags, module)
+        python_scan_task_collector(bld.path, env, bld)
+        return
+
+
+    if env['ENABLE_PYTHON_BINDINGS'] and env['BINDINGS_TYPE'] in ('monolithic', 'both'):
         apidefs = env['PYTHON_BINDINGS_APIDEFS']
 
         ## Get a list of scanned modules; the set of scanned modules
@@ -518,7 +634,7 @@
             if name.endswith("__local"):
                 continue
             scanned_modules.append(name)
-
+        print "scanned_modules:", scanned_modules
         debug = ('PYBINDGEN_DEBUG' in os.environ)
         source = [
             'ns3modulegen.py',
@@ -610,3 +726,5 @@
                 pass
             print "%r -> %r" % (src, dst)
             shutil.copy2(src, dst)
+
+
--- a/src/common/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/src/common/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -71,3 +71,5 @@
         'friis-spectrum-propagation-loss.h',
         'sequence-number.h',
         ]
+
+    bld.ns3_python_bindings()
--- a/src/core/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/src/core/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -157,3 +157,5 @@
     if bld.env['ENABLE_GSL']:
         core.uselib = 'GSL GSLCBLAS M'
         core.source.extend(['rng-test-suite.cc'])
+
+    bld.ns3_python_bindings()
--- a/src/node/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/src/node/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -108,3 +108,5 @@
         'ipv4-packet-info-tag.h',
         'ipv6-packet-info-tag.h',
         ]
+
+    bld.ns3_python_bindings()
--- a/src/simulator/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/src/simulator/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -127,3 +127,4 @@
         sim.uselib = 'RT'
 
 
+    bld.ns3_python_bindings()
--- a/src/wscript	Mon Nov 01 12:23:07 2010 -0400
+++ b/src/wscript	Sun Nov 07 23:17:52 2010 +0000
@@ -125,9 +125,79 @@
                   DeprecationWarning, stacklevel=2)
     return bld.new_task_gen(*args)
 
+
+def ns3_python_bindings(bld):
+    env = bld.env
+    if not env['ENABLE_PYTHON_BINDINGS']:
+        return
+    if env['BINDINGS_TYPE'] not in ('modular', 'both'):
+        return
+
+    # this method is called from a module wscript, so remember bld.path is not bindings/python!
+    module_abs_src_path = bld.path.abspath()
+    module = os.path.basename(module_abs_src_path)
+    apidefs = env['PYTHON_BINDINGS_APIDEFS'].replace("-", "_")
+
+    #debug = ('PYBINDGEN_DEBUG' in os.environ)
+    debug = True # XXX
+    source = [bld.srcnode.find_resource('bindings/python/ns3modulegen-modular.py').relpath_gen(bld.path),
+              "bindings/modulegen__%s.py" % apidefs]
+
+    # the local customization file may or not exist
+    if bld.path.find_resource("bindings/modulegen_local.py"):
+        source.append("bindings/modulegen_local.py")
+
+    target = ['bindings/module.cc']
+    #if not debug:
+    #    target.append('ns3modulegen.log')
+
+    argv = ['NS3_ENABLED_FEATURES=${FEATURES}', '${PYTHON}']
+    #if debug:
+    #    argv.extend(["-m", "pdb"])
+    
+    argv.extend(['${SRC[0]}', module_abs_src_path, apidefs, '>', '${TGT[0]}'])
+    #if not debug:
+    #    argv.extend(['2>', '${TGT[2]}']) # 2> ns3modulegen.log
+
+    features = []
+    for (name, caption, was_enabled, reason_not_enabled) in env['NS3_OPTIONAL_FEATURES']:
+        if was_enabled:
+            features.append(name)
+
+    bindgen = bld.new_task_gen('command', source=source, target=target, command=argv)
+    bindgen.env['FEATURES'] = ','.join(features)
+    bindgen.dep_vars = ['FEATURES']
+    bindgen.before = 'cxx'
+    bindgen.after = 'gen_ns3_module_header_task'
+    bindgen.name = "pybindgen(ns3 module %s)" % module
+
+    pymod = bld.new_task_gen(features='cxx cshlib pyext')
+    pymod.source = ['bindings/module.cc']
+    pymod.target = '%s/%s' % (bld.srcnode.find_dir("bindings/python/ns").relpath_gen(bld.path), module)
+    pymod.name = 'ns3module_%s' % module
+    pymod.uselib_local = "ns3"
+    if pymod.env['ENABLE_STATIC_NS3']:
+        if sys.platform == 'darwin':
+            pymod.env.append_value('LINKFLAGS', '-Wl,-all_load')
+            pymod.env.append_value('LINKFLAGS', '-lns3')
+        else:
+            pymod.env.append_value('LINKFLAGS', '-Wl,--whole-archive,-Bstatic')
+            pymod.env.append_value('LINKFLAGS', '-lns3')
+            pymod.env.append_value('LINKFLAGS', '-Wl,-Bdynamic,--no-whole-archive')
+    defines = list(pymod.env['CXXDEFINES'])
+    defines.extend(['NS_DEPRECATED=', 'NS3_DEPRECATED_H'])
+    if Options.platform == 'win32':
+        try:
+            defines.remove('_DEBUG') # causes undefined symbols on win32
+        except ValueError:
+            pass
+    pymod.env['CXXDEFINES'] = defines
+
+
 def build(bld):
     bld.create_ns3_module = types.MethodType(create_ns3_module, bld)
     bld.create_obj = types.MethodType(create_obj, bld)
+    bld.ns3_python_bindings = types.MethodType(ns3_python_bindings, bld)
     
     bld.add_subdirs(list(all_modules))