build.py
author Tom Henderson <tomh@tomh.org>
Fri, 18 May 2007 10:27:42 -0700
changeset 657 be551a3b07c6
parent 654 77ced4ddc0f7
permissions -rw-r--r--
minor changes due to documentation review

## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
# Copyright (c) 2006 INRIA
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation;
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>



import os
import os.path
import shutil
from SCons.Environment import Environment
from SCons.Builder import Builder
from SCons.Action import Action
import SCons

# hack stolen from wengo
# to get an ARGUMENTS defined correctly
try:
    ARGUMENTS = SCons.Script.ARGUMENTS
    COMMAND_LINE_TARGETS = SCons.Script.COMMAND_LINE_TARGETS
except AttributeError:
    from SCons.Script.SConscript import Arguments
    from SCons.Script.SConscript import CommandLineTargets
    ARGUMENTS = Arguments
    COMMAND_LINE_TARGETS = CommandLineTargets

class Ns3Module:
    def __init__(self, name, dir):
        self.sources = []
        self.inst_headers = []
        self.headers = []
        self.extra_dist = []
        self.deps = []
        self.external_deps = []
        self.config = []
        self.name = name
        self.dir = dir
        self.executable = False
        self.library = True
        self.ldflags = []
        self.header_inst_dir = ''
    def set_library(self):
        self.library = True
        self.executable = False
    def set_executable(self):
        self.library = False
        self.executable = True
    def add_config(self, config_fn):
        self.config.append(config_fn)
    def add_extra_dist(self, dist):
        self.extra_dist.append(dist)
    def add_external_dep(self, dep):
        self.external_deps.append(dep)
    def add_dep(self, dep):
        self.deps.append(dep)
    def add_deps(self, deps):
        self.deps.extend(deps)
    def add_source(self, source):
        self.sources.append(source)
    def add_sources(self, sources):
        self.sources.extend(sources)
    def add_header(self, header):
        self.headers.append(header)
    def add_headers(self, headers):
        self.headers.extend(headers)
    def add_inst_header(self, header):
        self.inst_headers.append(header)
    def add_inst_headers(self, headers):
        self.inst_headers.extend(headers)
    def add_ldflags (self, ldflags):
        self.ldflags.extend (ldflags)
    def add_ldflag (self, ldflag):
        self.add_ldflags ([ldflag])
    def set_header_inst_dir (self, inst_dir):
        self.header_inst_dir = inst_dir
        

def MyCopyAction(target, source, env):
    try:
        if len(target) == len(source):
            for i in range(len(target)):
                shutil.copy(source[i].path, target[i].path)
            return 0
        else:
            return 'invalid target/source match'
    except:
        print
        return 'exception'
def MyCopyActionPrint(target, source, env):
    if len(target) == len(source):
        output = ''
        for i in range(len(target)):
            output = output + 'copy \'' + source[i].path + '\' to \'' + target[i].path + '\''
            if i < len(target) - 1:
                output = output + '\n'
        return output
    else:
        return 'error in copy'
def GcxxEmitter(target, source, env):
    if os.path.exists(source[0].path):
        return [target, source]
    else:
        return [[], []]
def MyRmTree(target, source, env):
    shutil.rmtree(env['RM_DIR'])
    return 0
def MyRmTreePrint(target, source, env):
    return ''
def print_cmd_line(s, target, src, env):
    print 'Building ' + (' and '.join([str(x) for x in target])) + '...'

class Ns3BuildVariant:
    def __init__(self):
        self.static = False
        self.gcxx_deps = False
        self.gcxx_root = ''
        self.build_root = ''
        self.env = None

class Ns3:
    def __init__(self):
        self.__modules = []
        self.extra_dist = []
        self.build_dir = 'build'
        self.version = '0.0.1'
        self.name = 'noname'
        self.distname = 'noname'
        self.doxygen_config = ''
    def add(self, module):
        self.__modules.append(module)
    def add_extra_dist(self, dist):
        self.extra_dist.append(dist)
    def __get_module(self, name):
        for module in self.__modules:
            if module.name == name:
                return module
        return None
    def get_mod_output(self, module, variant):
        if module.executable:
            suffix = variant.env.subst(variant.env['PROGSUFFIX'])
            filename = os.path.join(variant.build_root, 'bin', 
                                    module.name + suffix)
        else:
            if variant.static:
                prefix = variant.env['LIBPREFIX']
                suffix = variant.env['LIBSUFFIX']
            else:
                prefix = variant.env['SHLIBPREFIX']
                suffix = variant.env['SHLIBSUFFIX']
            prefix = variant.env.subst(prefix)
            suffix = variant.env.subst(suffix)
            filename = os.path.join(variant.build_root, 'lib', 
                                     prefix + module.name + suffix)
        return filename
    def get_obj_builders(self, variant, module):
        env = variant.env.Copy ()
        objects = []
        hash = {}
        self.get_internal_deps (module, hash)
        for dep in hash.values ():
            if dep.header_inst_dir != '':
                inc_dir = os.path.join(variant.build_root, 'include', 
                                       self.name, dep.header_inst_dir)
                env.Append (CPPPATH = [inc_dir])
                
        if len(module.config) > 0:
            src_config_file = os.path.join(self.build_dir, 'config', module.name + '-config.h')
            tgt_config_file = os.path.join(variant.build_root, 'include', 
                                           self.name, module.name + '-config.h')

        for source in module.sources:
            obj_file = os.path.splitext(source)[0] + '.o'
            tgt = os.path.join(variant.build_root, module.dir, obj_file)
            src = os.path.join(module.dir, source)
            if variant.static:
                obj_builder = env.StaticObject(target = tgt, source = src)
            else:
                obj_builder = env.SharedObject(target = tgt, source = src)
            if len(module.config) > 0:
                config_file = env.MyCopyBuilder(target = [tgt_config_file], 
                                                    source = [src_config_file])
                env.Depends(obj_builder, config_file)
            if variant.gcxx_deps:
                gcno_tgt = os.path.join(variant.build_root, module.dir, 
                                        os.path.splitext(source)[0] + '.gcno')
                gcda_tgt = os.path.join(variant.build_root, module.dir, 
                                        os.path.splitext(source)[0] + '.gcda')
                gcda_src = os.path.join(variant.gcxx_root, module.dir, 
                                        os.path.splitext(source)[0] + '.gcda')
                gcno_src = os.path.join(variant.gcxx_root, module.dir, 
                                        os.path.splitext(source)[0] + '.gcno')
                gcno_builder = env.CopyGcxxBuilder(target = gcno_tgt, source = gcno_src)
                gcda_builder = env.CopyGcxxBuilder(target = gcda_tgt, source = gcda_src)
                env.Depends(obj_builder, gcda_builder)
                env.Depends(obj_builder, gcno_builder)
            objects.append(obj_builder)
        return objects
    def get_internal_deps(self, module, hash):
        for dep_name in module.deps:
            dep = self.__get_module(dep_name)
            hash[dep_name] = dep
            self.get_internal_deps(dep, hash)
    def get_external_deps(self, module):
        hash = {}
        self.get_internal_deps(module, hash)
        ext_hash = {}
        for mod in hash.values():
            for ext_dep in mod.external_deps:
                ext_hash[ext_dep] = 1
        return ext_hash.keys()
    def get_sorted_deps(self, module):
        h = {}
        self.get_internal_deps(module, h)
        modules = []
        for dep in h.keys():
            deps_copy = []
            mod = h[dep]
            deps_copy.extend(mod.deps)
            modules.append([mod, deps_copy])
        sorted = []
        while len(modules) > 0:
            to_remove = []
            for item in modules:
                if len(item[1]) == 0:
                    to_remove.append(item[0].name)
            for item in to_remove:
                for i in modules:
                    if item in i[1]:
                        i[1].remove(item)
            new_modules = []
            for mod in modules:
                found = False
                for i in to_remove:
                    if i == mod[0].name:
                        found = True
                        break
                if not found:
                    new_modules.append(mod)
            modules = new_modules
            sorted.extend(to_remove)
        sorted.reverse()
        # append external deps
        ext_deps = self.get_external_deps(module)
        for dep in ext_deps:
            sorted.append(dep)
        return sorted

    def gen_mod_dep(self, variant):
        build_root = variant.build_root
        cpp_path = os.path.join(variant.build_root, 'include')
        env = variant.env
        env.Append(CPPPATH = [cpp_path])
        header_dir = os.path.join(build_root, 'include', self.name)
        lib_path = os.path.join(build_root, 'lib')
        env.Append (LIBPATH = [lib_path])
        module_builders = []
        for module in self.__modules:
            my_env = env.Copy ();
            objects = self.get_obj_builders(variant, module)
            libs = self.get_sorted_deps(module)
            my_env.Append (LIBS = libs)
            my_env.Append (LINKFLAGS = module.ldflags)

            filename = self.get_mod_output(module, variant)
            if module.executable:
                module_builder = my_env.Program(target=filename, source=objects)
            else:
                if variant.static:
                    module_builder = my_env.StaticLibrary(target=filename, source=objects)
                else:
                    module_builder = my_env.SharedLibrary(target=filename, source=objects)

            for dep_name in module.deps:
                dep = self.__get_module(dep_name)
                my_env.Depends(module_builder, self.get_mod_output(dep, variant))

            for header in module.inst_headers:
                if module.header_inst_dir != '':
                    tgt = os.path.join(header_dir, module.header_inst_dir, header)
                else:
                    tgt = os.path.join(header_dir, header)
                src = os.path.join(module.dir, header)
                #builder = env.Install(target = tgt, source = src)
                header_builder = my_env.MyCopyBuilder(target=tgt, source=src)
                my_env.Depends(module_builder, header_builder)

            module_builders.append(module_builder)
        return module_builders
    def gen_mod_config(self, env):
        config_dir = os.path.join(self.build_dir, 'config')
        for module in self.__modules:
            if len(module.config) > 0:
                config_file = os.path.join(config_dir, module.name + '-config.h')
                config_file_guard = module.name + '_CONFIG_H'
                config_file_guard.upper()
                if not os.path.isfile(config_file):
                    if not os.path.isdir(config_dir):
                        os.makedirs(config_dir)
                    outfile = open(config_file, 'w')
                    outfile.write('#ifndef ' + config_file_guard + '\n')
                    outfile.write('#define ' + config_file_guard + '\n')
                    config = env.Configure()
                    for fn in module.config:
                        output = fn(env, config)
                        for o in output:
                            outfile.write(o)
                            outfile.write('\n')
                    outfile.write('#endif /*' + config_file_guard + '*/\n')
                    config.Finish()
    def generate_dependencies(self):
        inheritenv = (ARGUMENTS.get('inheritenv', 'n') in 'yY1')
        if inheritenv:
            env = Environment(ENV=os.environ)
        else:
            env = Environment()
        self.gen_mod_config(env)
        cc = env['CC']
        cxx = env.subst(env['CXX'])
        if cc == '':
            print "Missing C compiler."
            env.Exit (1);
        if cxx == '':
            print "Missing C++ compiler."
            env.Exit (1);
        common_flags = ARGUMENTS.get('cflags', '').split(' ')
        cxxflags = ARGUMENTS.get('cxxflags', '').split(' ')
        ldflags = ARGUMENTS.get('ldflags', '').split(' ')
        if cc == 'cl' and cxx == 'cl':
            env = Environment(tools=['mingw'])
            cc = env['CC']
            cxx = env.subst(env['CXX'])
        if cc == 'gcc' and cxx == 'g++':
            common_flags.extend(['-g3', '-Wall', '-Werror'])
            debug_flags = []
            opti_flags = ['-O3']
        elif cc == 'cl' and cxx == 'cl':
            env = Environment(ENV=os.environ)
            debug_flags = ['-W1', '-GX', '-EHsc', '-D_DEBUG', '/MDd']
            opti_flags = ['-O2', '-EHsc', '-DNDEBUG', '/MD']
        cc = ARGUMENTS.get ('cc', '')
        cxx = ARGUMENTS.get ('cxx', '')
        if cc != '':
            env.Replace (CC = cc)
        if cxx != '':
            env.Replace (CXX = cxx)
        env.Append(CCFLAGS = common_flags, 
                    CPPDEFINES = ['RUN_SELF_TESTS'], 
                    TARFLAGS = '-c -z', 
                    CPPFLAGS = cxxflags, 
                    LINKFLAGS = ldflags)
        if env['PLATFORM'] == 'posix':
            env.Append(LINKFLAGS = ' -z origin')
            env.Append(RPATH=env.Literal(os.path.join('\\$$ORIGIN', os.pardir, 'lib')))
        verbose = ARGUMENTS.get('verbose', 'n')
        if verbose == 'n':
            env['PRINT_CMD_LINE_FUNC'] = print_cmd_line
        header_builder = Builder(action = Action(MyCopyAction, strfunction=MyCopyActionPrint))
        env.Append(BUILDERS = {'MyCopyBuilder':header_builder})
        gcxx_builder = Builder(action = Action(MyCopyAction, strfunction=MyCopyActionPrint), 
                                emitter = GcxxEmitter)
        env.Append(BUILDERS = {'CopyGcxxBuilder':gcxx_builder})
        variant = Ns3BuildVariant()
        builders = []


        gcov_env = env.Copy()
        gcov_env.Append(CFLAGS = ['-fprofile-arcs', '-ftest-coverage'], 
                         CXXFLAGS = ['-fprofile-arcs', '-ftest-coverage'], 
                         LINKFLAGS = ['-fprofile-arcs'])
        # code coverage analysis
        variant.static = False
        variant.env = gcov_env
        variant.build_root = os.path.join(self.build_dir, 'gcov')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            gcov_env.Alias('gcov', builder)
        gcov_env.Alias('lcov-report')
        if 'lcov-report' in COMMAND_LINE_TARGETS:
            lcov_report_dir = os.path.join(self.build_dir, 'lcov-report')
            create_dir_command = "rm -rf " + lcov_report_dir
            create_dir_command += " && mkdir " + lcov_report_dir + ";"
            gcov_env.Execute(create_dir_command)
            info_file = os.path.join(lcov_report_dir, self.name + '.info')
            lcov_command = "utils/lcov/lcov -c -d . -o " + info_file
            lcov_command += " --source-dirs=" + os.getcwd()
            lcov_command += ":" + os.path.join(os.getcwd(), 
                                                variant.build_root, 
                                                'include')
            gcov_env.Execute(lcov_command)
            genhtml_command = "utils/lcov/genhtml -o " + lcov_report_dir
            genhtml_command += " " + info_file
            gcov_env.Execute(genhtml_command)



        opt_env = env.Copy()
        opt_env.Append(CFLAGS=opti_flags, 
                        CXXFLAGS=opti_flags, 
                        CPPDEFINES=['NDEBUG'])
        # optimized static support
        variant.static = True
        variant.env = opt_env
        variant.build_root = os.path.join(self.build_dir, 'opt-static')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            opt_env.Alias('opt-static', builder)


        opt_env = env.Copy()
        opt_env.Append(CFLAGS = opti_flags, 
                        CXXFLAGS = opti_flags, 
                        CPPDEFINES = ['NDEBUG'])
        # optimized shared support
        variant.static = False
        variant.env = opt_env
        variant.build_root = os.path.join(self.build_dir, 'opt-shared')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            opt_env.Alias('opt-shared', builder)


        arc_env = env.Copy()
        arc_env.Append(CFLAGS=opti_flags, 
                        CXXFLAGS=opti_flags, 
                        CPPDEFINES=['NDEBUG'])
        arc_env.Append(CFLAGS=['-frandom-seed=0', '-fprofile-generate'], 
                        CXXFLAGS=['-frandom-seed=0', '-fprofile-generate'], 
                        LINKFLAGS=['-frandom-seed=0', '-fprofile-generate'])
        # arc profiling
        variant.static = False
        variant.env = arc_env
        variant.build_root = os.path.join(self.build_dir, 'opt-arc')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            arc_env.Alias('opt-arc', builder)


        arcrebuild_env = env.Copy()
        arcrebuild_env.Append(CFLAGS=opti_flags, 
                               CXXFLAGS=opti_flags, 
                               CPPDEFINES=['NDEBUG'])
        arcrebuild_env.Append(CFLAGS=['-frandom-seed=0', '-fprofile-use'], 
                               CXXFLAGS=['-frandom-seed=0', '-fprofile-use'], 
                               LINKFLAGS=['-frandom-seed=0', '-fprofile-use'])
        # arc rebuild
        variant.static = False
        variant.env = arcrebuild_env
        variant.gcxx_deps = True
        variant.gcxx_root = os.path.join(self.build_dir, 'opt-arc')
        variant.build_root = os.path.join(self.build_dir, 'opt-arc-rebuild')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            arcrebuild_env.Alias('opt-arc-rebuild', builder)
        variant.gcxx_deps = False




        dbg_env = env.Copy()
        dbg_env.Append(CFLAGS = debug_flags, 
                       CXXFLAGS = debug_flags,
                       CPPDEFINES = ['NS3_DEBUG_ENABLE',
                                     'NS3_ASSERT_ENABLE'])
        # debug static support
        variant.static = True
        variant.env = dbg_env
        variant.build_root = os.path.join(self.build_dir, 'dbg-static')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            dbg_env.Alias('dbg-static', builder)

        dbg_env = env.Copy()
        dbg_env.Append(CFLAGS=debug_flags, 
                       CXXFLAGS=debug_flags,
                       CPPDEFINES = ['NS3_DEBUG_ENABLE',
                                     'NS3_ASSERT_ENABLE'])
        # debug shared support
        variant.static = False
        variant.env = dbg_env
        variant.build_root = os.path.join(self.build_dir, 'dbg-shared')
        builders = self.gen_mod_dep(variant)
        for builder in builders:
            dbg_env.Alias('dbg-shared', builder)

        env.Alias('dbg', 'dbg-shared')
        env.Alias('opt', 'opt-shared')
        env.Default('dbg')
        env.Alias('all', ['dbg-shared', 'dbg-static', 'opt-shared', 'opt-static'])


        # dist support
        dist_env = env.Copy()
        if dist_env['PLATFORM'] == 'posix' or dist_env['PLATFORM'] == 'darwin':
            dist_list = []
            for module in self.__modules:
                for f in module.sources:
                    dist_list.append(os.path.join(module.dir, f))
                for f in module.headers:
                    dist_list.append(os.path.join(module.dir, f))
                for f in module.inst_headers:
                    dist_list.append(os.path.join(module.dir, f))
                for f in module.extra_dist:
                    dist_list.append(os.path.join(module.dir, f))
            for f in self.extra_dist:
                dist_list.append(f)
            dist_list.append (self.doxygen_config)
            dist_list.append('SConstruct')
            dist_list.append('build.py')

            targets = []
            basename = self.distname + '-' + self.version
            for src in dist_list:
                tgt = os.path.join(basename, src)
                targets.append(dist_env.MyCopyBuilder(target=tgt, source=src))
            tar = basename + '.tar.gz'
            zip = basename + '.zip'
            tmp_tar = os.path.join(self.build_dir, tar)
            tmp_zip = os.path.join(self.build_dir, zip)
            dist_tgt = [tar, zip]
            dist_src = [tmp_tar, tmp_zip]
            dist_env.Tar(tmp_tar, targets)
            dist_env.Zip(tmp_zip, targets)
            dist_builder = dist_env.MyCopyBuilder(target=dist_tgt, source=dist_src)
            dist_env.Alias('dist', dist_builder)
            dist_env.Append(RM_DIR=basename)
            dist_env.AddPostAction(dist_builder, dist_env.Action(MyRmTree, 
                                                                 strfunction=MyRmTreePrint))
            dist_builder = dist_env.MyCopyBuilder(target=dist_tgt, source=dist_src)
            dist_env.Alias('fastdist', dist_tgt)

            # distcheck
            distcheck_list = []
            for src in dist_list:
                tgt = os.path.join(self.build_dir, basename, src)
                distcheck_list.append(tgt)
            untar = env.Command(distcheck_list, tar, 
                                ['cd ' + self.build_dir + ' && tar -zxf ../' + tar])
            scons_dir = os.path.join(self.build_dir, basename)
            distcheck_builder = env.Command('x', distcheck_list, 
                                            ['cd ' + scons_dir + ' && scons'])
            env.AlwaysBuild(distcheck_builder)
            env.Alias('distcheck', distcheck_builder)
        if self.doxygen_config != '':
            doxy = env.Command('doc/html/*', self.doxygen_config, 
                               ['doxygen ' + self.doxygen_config])
            env.AlwaysBuild(doxy)
            env.Alias('doc', doxy)