src/wscript
author Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
Mon, 26 Sep 2011 15:31:06 +0100
changeset 7545 ac0569e8cb7d
parent 7541 8f7dbc2acba9
child 7583 e42004e38839
child 8253 6faee3d1d1d0
permissions -rw-r--r--
Avoid installation of ns3modulegen-modular.py as a python module

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

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

from waflib.Errors import WafError

import TaskGen
import Task
import Options
import Build
import Utils

import wutils

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


all_modules = []
for dirname in os.listdir('src'):
    if dirname.startswith('.') or dir == 'CVS':
        continue
    path = os.path.join('src', dirname)
    if not os.path.isdir(path):
        continue
    if os.path.exists(os.path.join(path, 'wscript')):
        all_modules.append(dirname)
all_modules.sort()



def options(opt):
    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')

    for module in all_modules:
        opt.sub_options(module, mandatory=False)


def configure(conf):
    for module in all_modules:
        conf.sub_config(module, mandatory=False)

    blddir = os.path.abspath(os.path.join(conf.bldnode.abspath(), conf.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]



# we need the 'ns3module' waf "feature" to be created because code
# elsewhere looks for it to find the ns3 module objects.
@TaskGen.feature('ns3module')
def _add_test_code(module):
    pass


def create_ns3_module(bld, name, dependencies=(), test=False):
    static = bool(bld.env.ENABLE_STATIC_NS3)
    # Create a separate library for this module.
    if static:
        module = bld.new_task_gen(features=['cxx', 'cxxstlib', 'ns3module'])
    else:
        module = bld.new_task_gen(features=['cxx', 'cxxshlib', 'ns3module'])
    module.target = '%s/ns3-%s' % (bld.srcnode.relpath_gen(module.path), name)
    linkflags = []
    cxxflags = []
    ccflags = []
    if not static:
        cxxflags = module.env['shlib_CXXFLAGS']
        ccflags = module.env['shlib_CXXFLAGS']
        # Turn on the link flags for shared libraries if we have the
        # proper compiler and platform.
        if module.env['CXX_NAME'] in ['gcc', 'icc'] and module.env['WL_SONAME_SUPPORTED']:
            # Get the module library name without any relative paths
            # at its beginning because all of the libraries will end
            # up in the same directory.
            module_library_name = module.env.cshlib_PATTERN % (os.path.basename(module.target),)
            linkflags = '-Wl,--soname=' + module_library_name
    cxxdefines = ["NS3_MODULE_COMPILATION"]
    ccdefines = ["NS3_MODULE_COMPILATION"]

    module.env.append_value('CXXFLAGS', cxxflags)
    module.env.append_value('CCFLAGS', ccflags)
    module.env.append_value('LINKFLAGS', linkflags)
    module.env.append_value('CXXDEFINES', cxxdefines)
    module.env.append_value('CCDEFINES', ccdefines)

    module.is_static = static
    module.vnum = wutils.VNUM
    # Add the proper path to the module's name.
    # Set the libraries this module depends on.  
    module.module_deps = list(dependencies)

    module.install_path = "${LIBDIR}"

    module.name = "ns3-" + name
    module.dependencies = dependencies
    # Initially create an empty value for this because the pcfile
    # writing task assumes every module has a uselib attribute.
    module.uselib = ''
    module.use = ['ns3-' + dep for dep in dependencies]
    module.test = test
    module.is_ns3_module = True
    module.ns3_dir_location = bld.path.relpath_gen(bld.srcnode)

    module.env.append_value("INCLUDES", '#')

    pcfilegen = bld(features='ns3pcfile')
    pcfilegen.module = module
    
    return module

@TaskGen.feature("ns3testlib")
@TaskGen.before_method("apply_incpaths")
def apply_incpaths_ns3testlib(self):
    if not self.source:
        return
    testdir = self.source[-1].parent.relpath_gen(self.bld.srcnode)
    self.env.append_value("DEFINES", 'NS_TEST_SOURCEDIR="%s"' % (testdir,))


def create_ns3_module_test_library(bld, name):
    # Create an ns3 module for the test library that depends only on
    # the module being tested.
    library_name = name + "-test"
    library = bld.create_ns3_module(library_name, [name], test=True)
    library.features.append("ns3testlib")

    # Modify attributes for the test library that are different from a
    # normal module.
    del library.is_ns3_module
    library.is_ns3_module_test_library = True
    library.module_name = 'ns3-' + name

    # Add this module and test library to the list.
    bld.env.append_value('NS3_MODULES_WITH_TEST_LIBRARIES', [(library.module_name, library.name)])

    # Set the include path from the build directory to modules. 
    relative_path_from_build_to_here = bld.path.relpath_gen(bld.bldnode)
    include_flag = '-I' + relative_path_from_build_to_here
    library.env.append_value('CXXFLAGS', include_flag)
    library.env.append_value('CCFLAGS',  include_flag)

    return library

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):
    if Options.options.apiscan:
        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)
    env = bld.env
    env.append_value("MODULAR_BINDINGS_MODULES", "ns3-"+module)

    if not env['ENABLE_PYTHON_BINDINGS']:
        return

    bindings_dir = bld.path.find_dir("bindings")
    if bindings_dir is None or not os.path.exists(bindings_dir.abspath()):
        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

    if ("ns3-%s" % (module,)) not in env.NS3_ENABLED_MODULES:
        #print "bindings for module %s which is not enabled, skip" % module
        return

    env.append_value('PYTHON_MODULES_BUILT', module)
    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]

    if bindings_dir.find_resource("modulegen_customizations.py") is not None:
        source.append("bindings/modulegen_customizations.py")

    # the local customization file may or not exist
    if bld.path.find_resource("bindings/modulegen_local.py"):
        source.append("bindings/modulegen_local.py")

    module_py_name = module.replace('-', '_')
    module_target_dir = bld.srcnode.find_dir("bindings/python/ns").relpath_gen(bld.path)

    # if bindings/<module>.py exists, it becomes the module frontend, and the C extension befomes _<module>
    if bld.path.find_resource("bindings/%s.py" % (module_py_name,)) is not None:
        bld.new_task_gen(
            features='copy',
            source=("bindings/%s.py" % (module_py_name,)),
            target=('%s/%s.py' % (module_target_dir, module_py_name)))
        extension_name = '_%s' % (module_py_name,)
        bld.install_files('${PYTHONDIR}/ns', ["bindings/%s.py" % (module_py_name,)])
    else:
        extension_name = module_py_name

    target = ['bindings/ns3module.cc', 'bindings/ns3module.h', 'bindings/ns3modulegen.log']
    #if not debug:
    #    target.append('ns3modulegen.log')

    argv = ['NS3_ENABLED_FEATURES=${FEATURES}',
            'GCC_RTTI_ABI_COMPLETE=${GCC_RTTI_ABI_COMPLETE}',
            '${PYTHON}']
    #if debug:
    #    argv.extend(["-m", "pdb"])
    
    argv.extend(['${SRC[0]}', module_abs_src_path, apidefs, extension_name, '${TGT[0]}'])

    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(features=['command'], source=source, target=target, command=argv)
    bindgen.env['FEATURES'] = ','.join(features)
    bindgen.dep_vars = ['FEATURES', "GCC_RTTI_ABI_COMPLETE"]
    bindgen.before = 'cxx'
    bindgen.after = 'gen_ns3_module_header'
    bindgen.name = "pybindgen(ns3 module %s)" % module
    bindgen.install_path = None

    # generate the extension module
    pymod = bld.new_task_gen(features='cxx cxxshlib pyext')
    pymod.source = ['bindings/ns3module.cc']
    pymod.target = '%s/%s' % (module_target_dir, extension_name)
    pymod.name = 'ns3module_%s' % module
    pymod.use = ["%s" % mod for mod in pymod.env['NS3_ENABLED_MODULES']] #  Should be '"ns3-"+module', but see bug 1117
    if pymod.env['ENABLE_STATIC_NS3']:
        if sys.platform == 'darwin':
            pymod.env.append_value('LINKFLAGS', '-Wl,-all_load')
            for mod in pymod.usel:
                #mod = mod.split("--lib")[0]
                pymod.env.append_value('LINKFLAGS', '-l' + mod)
        else:
            pymod.env.append_value('LINKFLAGS', '-Wl,--whole-archive,-Bstatic')
            for mod in pymod.use:
                #mod = mod.split("--lib")[0]
                pymod.env.append_value('LINKFLAGS', '-l' + mod)
            pymod.env.append_value('LINKFLAGS', '-Wl,-Bdynamic,--no-whole-archive')
    defines = list(pymod.env['DEFINES'])
    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['DEFINES'] = defines
    pymod.includes = '# bindings'
    pymod.install_path = '${PYTHONDIR}/ns'
    return pymod


def build(bld):
    bld.create_ns3_module = types.MethodType(create_ns3_module, bld)
    bld.create_ns3_module_test_library = types.MethodType(create_ns3_module_test_library, bld)
    bld.create_obj = types.MethodType(create_obj, bld)
    bld.ns3_python_bindings = types.MethodType(ns3_python_bindings, bld)
    
    # Remove these modules from the list of all modules.
    for not_built in bld.env['MODULES_NOT_BUILT']:

        # XXX Becaue these modules are located in subdirectories of
        # test, their names in the all_modules list include the extra
        # relative path "test/".  If these modules are moved into the
        # src directory, then this if block should be removed.
        if not_built == 'ns3tcp' or not_built == 'ns3wifi':
            not_built = 'test/' + not_built

        if not_built in all_modules:
            all_modules.remove(not_built)

    bld.add_subdirs(list(all_modules))

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

class ns3pcfile_task(Task.Task):
    after = 'cc cxx'

    def __str__(self):
        "string to display to the user"
        tgt_str = ' '.join([a.nice_path(self.env) for a in self.outputs])
        return 'pcfile: %s\n' % (tgt_str)

    def runnable_status(self):
        return super(ns3pcfile_task, self).runnable_status()

    def _self_libs(self, env, name, libdir):
        if env['ENABLE_STATIC_NS3']:
            path_st = 'STLIBPATH_ST'
            lib_st = 'STLIB_ST'
            lib_marker = 'STLIB_MARKER'
        else:
            path_st = 'LIBPATH_ST'
            lib_st = 'LIB_ST'
            lib_marker = 'SHLIB_MARKER'
        retval = [env[path_st] % libdir]
        if env[lib_marker]:
            retval.append(env[lib_marker])
        retval.append(env[lib_st] % name)
        return retval

    def _lib(self, env, dep):
        libpath = env['LIBPATH_%s' % dep]
        linkflags = env['LINKFLAGS_%s' % dep]
        libs = env['LIB_%s' % dep]
        retval = []
        for path in libpath:
            retval.append(env['LIBPATH_ST'] % path)
            retval = retval + linkflags
        for lib in libs:
            retval.append(env['LIB_ST'] % lib)
        return retval

    def _listify(self, v):
        if isinstance(v, list):
            return v
        else:
            return [v]

    def _cflags(self, dep):
        flags = self.env['CFLAGS_%s' % dep]
        return self._listify(flags)

    def _cxxflags(self, dep):
        return self._listify(self.env['CXXFLAGS_%s' % dep])

    def _defines(self, dep):
        return [self.env['DEFINES_ST'] % define for define in self.env['DEFINES_%s' % dep]] 

    def _includes(self, dep):
        includes = self.env['INCLUDES_%s' % dep]
        return [self.env['CPPPATH_ST'] % include for include in includes]

    def _generate_pcfile(self, name, use, env, outfilename):
        outfile = open(outfilename, 'wt')
        prefix = env.PREFIX
        includedir = env.INCLUDEDIR
        libdir = env.LIBDIR
        libs = self._self_libs(env, name, '${libdir}')
        for dep in use:
            libs += self._lib(env, dep)
        for dep in env.LIBS:
            libs += self.env['LIB_ST'] % dep
        cflags = [self.env['CPPPATH_ST'] % '${includedir}']
        requires = []
        for dep in use:
            cflags = cflags + self._cflags(dep) + self._cxxflags(dep) + \
                self._defines(dep) + self._includes(dep)
            if dep.startswith('ns3-'):
                requires.append("lib"+dep)
        print >> outfile, """
prefix=%s
libdir=%s
includedir=%s

Name: lib%s
Description: ns-3 module %s
Version: devel
Libs: %s
Cflags: %s
Requires: %s
""" % (prefix, libdir, includedir,
       name, name, ' '.join(libs), ' '.join(cflags), ' '.join(requires))
        outfile.close()

    def run(self):
        output_filename = self.outputs[0].abspath()
        self._generate_pcfile(self.module.name, 
                              self.module.to_list(self.module.use),
                              self.env, output_filename)


@TaskGen.feature('ns3pcfile')
@TaskGen.after_method('process_rule')
def apply(self):
    output_filename = 'lib%s.pc' % self.module.name
    output_node = self.path.find_or_declare(output_filename)
    assert output_node is not None, str(self)
    task = self.create_task('ns3pcfile')
    self.bld.install_files('${LIBDIR}/pkgconfig', output_node)
    task.set_outputs([output_node])
    task.module = self.module



@TaskGen.feature('ns3header')
@TaskGen.after_method('process_rule')
def apply_ns3header(self):
    if self.module is None:
        raise WafError("'module' missing on ns3headers object %s" % self)
    ns3_dir_node = self.bld.path.find_dir("ns3")
    for filename in set(self.to_list(self.source)):
        src_node = self.path.find_resource(filename)
        if src_node is None:
            raise WafError("source ns3 header file %s not found" % (filename,))
        dst_node = ns3_dir_node.find_or_declare(src_node.name)
        assert dst_node is not None
        task = self.create_task('ns3header')
        task.mode = getattr(self, 'mode', 'install')
        if task.mode == 'install':
            self.bld.install_files('${PREFIX}/include/ns3', [src_node])
            task.set_inputs([src_node])
            task.set_outputs([dst_node])
        else:
            task.header_to_remove = dst_node
    self.headers = set(self.to_list(self.source))
    self.source = '' # tell WAF not to process these files further


class ns3header_task(Task.Task):
    before = 'cc cxx gen_ns3_module_header'
    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.abspath(),)
        return 'install-ns3-header: %s%s%s\n' % (src_str, sep, tgt_str)

    def __repr__(self):
        return str(self)

    def uid(self):
        try:
            return self.uid_
        except AttributeError:
            m = Utils.md5()
            up = m.update
            up(self.__class__.__name__.encode())
            for x in self.inputs + self.outputs:
                up(x.abspath().encode())
            up(self.mode)
            if self.mode == 'remove':
                up(self.header_to_remove.abspath().encode())
            self.uid_ = m.digest()
            return self.uid_

    def runnable_status(self):
        if self.mode == 'remove':
            if os.path.exists(self.header_to_remove.abspath()):
                return Task.RUN_ME
            else:
                return Task.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.abspath() for node in self.inputs]
            outputs = [node.abspath() 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.abspath()
            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'
    color = 'BLUE'

    def runnable_status(self):
        if self.mode == 'remove':
            if os.path.exists(self.header_to_remove.abspath()):
                return Task.RUN_ME
            else:
                return Task.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.abspath(),)
        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.abspath()
            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].get_bld().abspath()#self.env)
        header_files = [os.path.basename(node.abspath()) 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() 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


# Generates a 'ns3/foo-module.h' header file that includes all public
# ns3 headers of a certain module.
@TaskGen.feature('ns3moduleheader')
@TaskGen.after_method('process_rule')
def apply_ns3moduleheader(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 'ns3header' in getattr(ns3headers, "features", []):
            if ns3headers.module != self.module:
                continue
            found_the_module = True
            for source in ns3headers.headers:
                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 WafError("error finding headers for module %s" % self.module)
    if not all_headers_inputs:
        return

    try:
        module_obj = self.bld.get_tgen_by_name("ns3-" + self.module)
    except WafError: # maybe the module was disabled, and therefore removed
        return

    all_headers_outputs = [ns3_dir_node.find_or_declare("%s-module.h" % self.module)]
    task = self.create_task('gen_ns3_module_header')
    task.module = self.module
    task.mode = getattr(self, "mode", "install")
    if task.mode == 'install':
        assert module_obj is not None, self.module
        self.bld.install_files('${PREFIX}/include/ns3', 
                               ns3_dir_node.find_or_declare("%s-module.h" % self.module))
        task.set_inputs(all_headers_inputs)
        task.set_outputs(all_headers_outputs)
        task.module_deps = module_obj.module_deps
    else:
        task.header_to_remove = all_headers_outputs[0]