src/wscript
author Josh Pelkey <jpelkey@gatech.edu>
Fri, 11 Mar 2011 15:21:50 -0500
changeset 6890 e5da7045526e
parent 6882 20221fbd189e
child 6894 0c7a09810b07
permissions -rw-r--r--
Merge OpenFlow

## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-

import os, os.path
import shutil
import types
import warnings

import TaskGen
import Task
import Options
import Build
import Utils
import Constants

try:
    set
except NameError:
    from sets import Set as set # Python 2.3 fallback

all_modules = (
    'core',
    'network',
    'contrib',
    'internet',
    'propagation',
    'point-to-point',
    'csma',
    'emu',
    'bridge',
    'tap-bridge',
    'virtual-net-device',
    'applications',
    'nix-vector-routing',
    'olsr',
    'aodv',
    'dsdv',
    'click',
    'openflow',
    'mobility',
    'wifi',
    'netanim',
    'stats',
    'uan',
    'spectrum',
    'mesh',   
    'test',
    'test/perf',
    'test/ns3tcp',
    'test/nsctcp',
    'test/ns3wifi',
    'contrib/flow-monitor',
    'wimax',
    'lte',
    'mpi',
    'topology-read',
    'contrib/energy',
    'tools/visualizer',
    )

def set_options(opt):
    opt.sub_options('core')
    opt.sub_options('click')
    opt.sub_options('openflow')

    opt.add_option('--enable-rpath',
                   help=("Link programs with rpath"
                         " (normally not needed, see "
                         " --run and --shell; moreover, only works in some"
                         " specific platforms, such as Linux and Solaris)"),
                   action="store_true", dest='enable_rpath', default=False)
    
    opt.add_option('--enable-modules',
                   help=("Build only these modules (and dependencies)"),
                   dest='enable_modules')

def configure(conf):
    conf.sub_config('core')
    conf.sub_config('emu')
    conf.sub_config('tap-bridge')
    conf.sub_config('contrib')
    conf.sub_config('internet')
    conf.sub_config('netanim')
    conf.sub_config('test')
    conf.sub_config('click')
    conf.sub_config('openflow')

    blddir = os.path.abspath(os.path.join(conf.blddir, conf.env.variant()))
    conf.env.append_value('NS3_MODULE_PATH', blddir)
    if Options.options.enable_rpath:
        conf.env.append_value('RPATH', '-Wl,-rpath=%s' % (os.path.join(blddir),))

    ## Used to link the 'test-runner' program with all of ns-3 code
    conf.env['NS3_MODULES'] = ['ns3-' + module.split('/')[-1] for module in all_modules]


def create_ns3_module(bld, name, dependencies=()):
    module = bld.new_task_gen('cxx', 'cc')
    module.is_ns3_module = True
    module.name = 'ns3-' + name
    module.target = module.name
    module.add_objects = ['ns3-' + dep for dep in dependencies]
    module.module_deps = list(dependencies)
    if not module.env['ENABLE_STATIC_NS3']:
        module.env.append_value('CXXFLAGS', module.env['shlib_CXXFLAGS'])
        module.env.append_value('CCFLAGS', module.env['shlib_CXXFLAGS'])
    elif module.env['CXX_NAME'] in ['gcc', 'icc'] and \
            os.uname()[4] == 'x86_64' and \
            module.env['ENABLE_PYTHON_BINDINGS']:
        # enable that flag for static builds only on x86-64 platforms
        # when gcc is present and only when we want python bindings
        # (it's more efficient to not use this option if we can avoid it)
        module.env.append_value('CXXFLAGS', '-mcmodel=large')
        module.env.append_value('CCFLAGS', '-mcmodel=large')
        
    module.env.append_value('CXXDEFINES', "NS3_MODULE_COMPILATION")
    module.env.append_value('CCDEFINES', "NS3_MODULE_COMPILATION")
    return module

def create_obj(bld, *args):
    warnings.warn("(in %s) Use bld.new_task_gen(...) now, instead of bld.create_obj(...)" % str(bld.path),
                  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

    if not bld.path.find_dir("bindings"):
        warnings.warn("(in %s) Requested to build modular python bindings, but apidefs dir not found "
                      "=> skipped the bindings." % str(bld.path),
                      Warning, stacklevel=2)
        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))

    for module in all_modules:
        modheader = bld.new_task_gen('ns3moduleheader')
        modheader.module = module.split('/')[-1]


class ns3header_taskgen(TaskGen.task_gen):
    """A set of NS-3 header files"""
    COLOR = 'BLUE'
    def __init__(self, *args, **kwargs):
        super(ns3header_taskgen, self).__init__(*args, **kwargs)
        self.install_path = None
        self.sub_dir = None # if not None, header files will be published as ns3/sub_dir/file.h
        self.module = None # module name
        self.mode = 'install'

    def apply(self):
        if self.module is None:
            raise Utils.WafError("'module' missing on ns3headers object %s" % self)
        ns3_dir_node = self.bld.path.find_dir("ns3")
        if self.sub_dir is not None:
            ns3_dir_node = ns3_dir_node.find_dir(self.sub_dir)
        for filename in set(self.to_list(self.source)):
            src_node = self.path.find_resource(filename)
            if src_node is None:
                raise Utils.WafError("source ns3 header file %s not found" % (filename,))
            dst_node = ns3_dir_node.find_or_declare(os.path.basename(filename))
            assert dst_node is not None
            task = self.create_task('ns3header', env=self.env)
            task.mode = self.mode
            if self.mode == 'install':
                task.set_inputs([src_node])
                task.set_outputs([dst_node])
            else:
                task.header_to_remove = dst_node

class ns3header_task(Task.Task):
    before = 'cc cxx gen_ns3_module_header_task'
    color = 'BLUE'

    def __str__(self):
        "string to display to the user"
        env = self.env
        src_str = ' '.join([a.nice_path(env) for a in self.inputs])
        tgt_str = ' '.join([a.nice_path(env) for a in self.outputs])
        if self.outputs: sep = ' -> '
        else: sep = ''
        if self.mode == 'remove':
            return 'rm-ns3-header %s\n' % (self.header_to_remove.bldpath(self.env),)
        return 'install-ns3-header: %s%s%s\n' % (src_str, sep, tgt_str)

    def runnable_status(self):
        if self.mode == 'remove':
            if os.path.exists(self.header_to_remove.bldpath(self.env)):
                return Constants.RUN_ME
            else:
                return Constants.SKIP_ME
        else:
            return super(ns3header_task, self).runnable_status()

    def run(self):
        if self.mode == 'install':
            assert len(self.inputs) == len(self.outputs)
            inputs = [node.srcpath(self.env) for node in self.inputs]
            outputs = [node.bldpath(self.env) for node in self.outputs]
            for src, dst in zip(inputs, outputs):
                try:
                    os.chmod(dst, 0600)
                except OSError:
                    pass
                shutil.copy2(src, dst)
                ## make the headers in builddir read-only, to prevent
                ## accidental modification
                os.chmod(dst, 0400)
            return 0
        else:
            assert len(self.inputs) == 0
            assert len(self.outputs) == 0
            out_file_name = self.header_to_remove.bldpath(self.env)
            try:
                os.unlink(out_file_name)
            except OSError, ex:
                if ex.errno != 2:
                    raise
            return 0
            


class gen_ns3_module_header_task(Task.Task):
    before = 'cc cxx'
    after = 'ns3header_task'
    color = 'BLUE'

    def runnable_status(self):
        if self.mode == 'remove':
            if os.path.exists(self.header_to_remove.bldpath(self.env)):
                return Constants.RUN_ME
            else:
                return Constants.SKIP_ME
        else:
            return super(gen_ns3_module_header_task, self).runnable_status()

    def __str__(self):
        "string to display to the user"
        env = self.env
        src_str = ' '.join([a.nice_path(env) for a in self.inputs])
        tgt_str = ' '.join([a.nice_path(env) for a in self.outputs])
        if self.outputs: sep = ' -> '
        else: sep = ''
        if self.mode == 'remove':
            return 'rm-module-header %s\n' % (self.header_to_remove.bldpath(self.env),)
        return 'gen-module-header: %s%s%s\n' % (src_str, sep, tgt_str)

    def run(self):
        if self.mode == 'remove':
            assert len(self.inputs) == 0
            assert len(self.outputs) == 0
            out_file_name = self.header_to_remove.bldpath(self.env)
            try:
                os.unlink(out_file_name)
            except OSError, ex:
                if ex.errno != 2:
                    raise
            return 0
        
        assert len(self.outputs) == 1
        out_file_name = self.outputs[0].bldpath(self.env)
        header_files = [os.path.basename(node.abspath(self.env)) for node in self.inputs]
        outfile = file(out_file_name, "w")
        header_files.sort()

        print >> outfile, """
#ifdef NS3_MODULE_COMPILATION
# error "Do not include ns3 module aggregator headers from other modules; these are meant only for end user scripts."
#endif

#ifndef NS3_MODULE_%s
    """ % (self.module.upper().replace('-', '_'),)

    #     if self.module_deps:
    #         print >> outfile, "// Module dependencies:"
    #     for dep in self.module_deps:
    #         print >> outfile, "#include \"%s-module.h\"" % dep

        print >> outfile
        print >> outfile, "// Module headers:"
        for header in header_files:
            print >> outfile, "#include \"%s\"" % (header,)

        print >> outfile, "#endif"

        outfile.close()
        return 0

    def sig_explicit_deps(self):
        self.m.update('\n'.join([node.abspath(self.env) for node in self.inputs]))
        return self.m.digest()

    def unique_id(self):
        try:
            return self.uid
        except AttributeError:
            "this is not a real hot zone, but we want to avoid surprizes here"
            m = Utils.md5()
            m.update("ns-3-module-header-%s" % self.module)
            self.uid = m.digest()
            return self.uid


class ns3moduleheader_taskgen(TaskGen.task_gen):
    """
    Generates a 'ns3/foo-module.h' header file that includes all
    public ns3 headers of a certain module.
    """
    COLOR = 'BLUE'
    def __init__(self, *args, **kwargs):
        super(ns3moduleheader_taskgen, self).__init__(*args, **kwargs)
        self.mode = 'install'

    def apply(self):
        ## get all of the ns3 headers
        ns3_dir_node = self.bld.path.find_dir("ns3")
        all_headers_inputs = []
        found_the_module = False
        for ns3headers in self.bld.all_task_gen:
            if isinstance(ns3headers, ns3header_taskgen):
                if ns3headers.module != self.module:
                    continue
                found_the_module = True
                for source in set(ns3headers.to_list(ns3headers.source)):
                    source = os.path.basename(source)
                    node = ns3_dir_node.find_or_declare(os.path.basename(source))
                    if node is None:
                        fatal("missing header file %s" % (source,))
                    all_headers_inputs.append(node)
        if not found_the_module:
            raise Utils.WscriptError("error finding headers for module %s" % self.module)
        if not all_headers_inputs:
            return
        all_headers_outputs = [ns3_dir_node.find_or_declare("%s-module.h" % self.module)]
        task = self.create_task('gen_ns3_module_header', env=self.env)
        task.module = self.module
        task.mode = self.mode
        if self.mode == 'install':
            task.set_inputs(all_headers_inputs)
            task.set_outputs(all_headers_outputs)
            module_obj = self.bld.name_to_obj("ns3-" + self.module, self.env)
            assert module_obj is not None, self.module
            task.module_deps = module_obj.module_deps
        else:
            task.header_to_remove = all_headers_outputs[0]

    def install(self):
        pass