wscript
author Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
Fri Sep 04 12:06:54 2009 +0100 (2009-09-04)
changeset 4751 386247c6625a
parent 4626 a7b70048bd4a
child 4760 f774ff724ee4
permissions -rw-r--r--
Bug #620: Build system --run target seems to forget copying updated headers
     1 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
     2 
     3 # python lib modules
     4 import sys
     5 import shutil
     6 import types
     7 import optparse
     8 import os.path
     9 import re
    10 
    11 # WAF modules
    12 import pproc as subprocess
    13 import Options
    14 
    15 import Logs
    16 import TaskGen
    17 import Constants
    18 
    19 import ccroot
    20 ccroot.USE_TOP_LEVEL = True
    21 
    22 import Task
    23 Task.algotype = Constants.JOBCONTROL # so that Task.maxjobs=1 takes effect
    24 
    25 import Utils
    26 import Build
    27 import Configure
    28 import Scripting
    29 
    30 import cflags # override the build profiles from waf
    31 cflags.profiles = {
    32 	# profile name: [optimization_level, warnings_level, debug_level]
    33 	'debug':     [0, 2, 3],
    34 	'optimized': [3, 2, 1],
    35 	}
    36 cflags.default_profile = 'debug'
    37 
    38 # local modules
    39 import wutils
    40 import regression
    41 
    42 Configure.autoconfig = 1
    43 
    44 # the following two variables are used by the target "waf dist"
    45 VERSION = file("VERSION", "rt").read().strip()
    46 APPNAME = 'ns'
    47 
    48 wutils.VERSION = VERSION
    49 wutils.APPNAME = APPNAME
    50 
    51 #
    52 # The last part of the path name to use to find the regression traces.  The
    53 # path will be APPNAME + '-' + VERSION + REGRESSION_SUFFIX, e.g.,
    54 # ns-3-dev-ref-traces
    55 #
    56 REGRESSION_SUFFIX = "-ref-traces"
    57 
    58 
    59 # these variables are mandatory ('/' are converted automatically)
    60 srcdir = '.'
    61 blddir = 'build'
    62 
    63 def load_env():
    64     bld_cls = getattr(Utils.g_module, 'build_context', Utils.Context)
    65     bld_ctx = bld_cls()
    66     bld_ctx.load_dirs(os.path.abspath(os.path.join (srcdir,'..')),
    67                       os.path.abspath(os.path.join (srcdir,'..', blddir)))
    68     bld_ctx.load_envs()
    69     env = bld_ctx.get_env()
    70     return env
    71 
    72 def get_files(base_dir):
    73     retval = []
    74     reference=os.path.dirname(base_dir)
    75     for root, dirs, files in os.walk(base_dir):
    76         if root.find('.hg') != -1:
    77             continue
    78         for file in files:
    79             if file.find('.hg') != -1:
    80                 continue
    81             fullname = os.path.join(root,file)
    82             # we can't use os.path.relpath because it's new in python 2.6
    83             relname = fullname.replace(reference + '/','')
    84             retval.append([fullname,relname])
    85     return retval
    86 
    87 
    88 def dist_hook():
    89     import tarfile
    90     shutil.rmtree("doc/html", True)
    91     shutil.rmtree("doc/latex", True)
    92     shutil.rmtree("nsc", True)
    93 
    94     ## build the name of the traces subdirectory.  Will be something like
    95     ## ns-3-dev-ref-traces
    96     traces_name = APPNAME + '-' + VERSION + REGRESSION_SUFFIX
    97     ## Create a tar.bz2 file with the traces
    98     env = load_env()
    99     regression_dir = env['REGRESSION_TRACES']
   100     if not os.path.isdir(regression_dir):
   101         Logs.warn("Not creating traces archive: the %s directory does not exist" % regression_dir)
   102     else:
   103         traceball = traces_name + wutils.TRACEBALL_SUFFIX
   104         tar = tarfile.open(os.path.join("..", traceball), 'w:bz2')
   105         files = get_files(regression_dir)
   106         for fullfilename,relfilename in files:
   107             tar.add(fullfilename,arcname=relfilename)
   108         tar.close()
   109 
   110 def set_options(opt):
   111     # options provided by the modules
   112     opt.tool_options('compiler_cxx')
   113     opt.tool_options('cflags')
   114 
   115     opt.add_option('--cwd',
   116                    help=('Set the working directory for a program.'),
   117                    action="store", type="string", default=None,
   118                    dest='cwd_launch')
   119 
   120     opt.add_option('--enable-gcov',
   121                    help=('Enable code coverage analysis.'
   122                          ' WARNING: this option only has effect '
   123                          'with the configure command.'),
   124                    action="store_true", default=False,
   125                    dest='enable_gcov')
   126 
   127     opt.add_option('--no-task-lines',
   128                    help=("Don't print task lines, i.e. messages saying which tasks are being executed by WAF."
   129                          "  Coupled with a single -v will cause WAF to output only the executed commands,"
   130                          " just like 'make' does by default."),
   131                    action="store_true", default=False,
   132                    dest='no_task_lines')
   133 
   134     opt.add_option('--lcov-report',
   135                    help=('Generate a code coverage report '
   136                          '(use this option at build time, not in configure)'),
   137                    action="store_true", default=False,
   138                    dest='lcov_report')
   139 
   140     opt.add_option('--doxygen',
   141                    help=('Run doxygen to generate html documentation from source comments'),
   142                    action="store_true", default=False,
   143                    dest='doxygen')
   144 
   145     opt.add_option('--run',
   146                    help=('Run a locally built program; argument can be a program name,'
   147                          ' or a command starting with the program name.'),
   148                    type="string", default='', dest='run')
   149     opt.add_option('--command-template',
   150                    help=('Template of the command used to run the program given by --run;'
   151                          ' It should be a shell command string containing %s inside,'
   152                          ' which will be replaced by the actual program.'),
   153                    type="string", default=None, dest='command_template')
   154     opt.add_option('--pyrun',
   155                    help=('Run a python program using locally built ns3 python module;'
   156                          ' argument is the path to the python program, optionally followed'
   157                          ' by command-line options that are passed to the program.'),
   158                    type="string", default='', dest='pyrun')
   159     opt.add_option('--valgrind',
   160                    help=('Change the default command template to run programs and unit tests with valgrind'),
   161                    action="store_true", default=False,
   162                    dest='valgrind')
   163     opt.add_option('--shell',
   164                    help=('DEPRECATED (run ./waf shell)'),
   165                    action="store_true", default=False,
   166                    dest='shell')
   167     opt.add_option('--enable-sudo',
   168                    help=('Use sudo to setup suid bits on ns3 executables.'),
   169                    dest='enable_sudo', action='store_true',
   170                    default=False)
   171     opt.add_option('--regression',
   172                    help=("Enable regression testing; only used for the 'check' target"),
   173                    default=False, dest='regression', action="store_true")
   174     opt.add_option('--check',
   175                    help=("Enable unit testing"),
   176                    default=False, dest='check', action="store_true")
   177     opt.add_option('--regression-generate',
   178                    help=("Generate new regression test traces."),
   179                    default=False, dest='regression_generate', action="store_true")
   180     opt.add_option('--regression-tests',
   181                    help=('For regression testing, only run/generate the indicated regression tests, '
   182                          'specified as a comma separated list of test names'),
   183                    dest='regression_tests', type="string")
   184     opt.add_option('--with-regression-traces',
   185                    help=('Path to the regression reference traces directory'),
   186                    default=None,
   187                    dest='regression_traces', type="string")
   188     opt.add_option('--enable-static',
   189                    help=('Compile NS-3 statically: works only on linux, without python'),
   190                    dest='enable_static', action='store_true',
   191                    default=False)
   192 
   193     # options provided in a script in a subdirectory named "src"
   194     opt.sub_options('src')
   195     opt.sub_options('bindings/python')
   196     opt.sub_options('src/internet-stack')
   197 
   198 
   199 def _check_compilation_flag(conf, flag):
   200     """
   201     Checks if the C++ compiler accepts a certain compilation flag or flags
   202     flag: can be a string or a list of strings
   203     """
   204 
   205     env = conf.env.copy()
   206     env.append_value('CXXFLAGS', flag)
   207     try:
   208         retval = conf.run_c_code(code='#include <stdio.h>\nint main() { return 0; }\n',
   209                                  env=env, compile_filename='test.cc',
   210                                  compile_mode='cxx',type='cprogram', execute=False)
   211     except Configure.ConfigurationError:
   212         ok = False
   213     else:
   214         ok = (retval == 0)
   215     conf.check_message_custom(flag, 'support', (ok and 'yes' or 'no'))
   216     return ok
   217 
   218     
   219 def report_optional_feature(conf, name, caption, was_enabled, reason_not_enabled):
   220     conf.env.append_value('NS3_OPTIONAL_FEATURES', (name, caption, was_enabled, reason_not_enabled))
   221 
   222 def configure(conf):
   223     # attach some extra methods
   224     conf.check_compilation_flag = types.MethodType(_check_compilation_flag, conf)
   225     conf.report_optional_feature = types.MethodType(report_optional_feature, conf)
   226     conf.env['NS3_OPTIONAL_FEATURES'] = []
   227 
   228     conf.env['NS3_BUILDDIR'] = conf.blddir
   229     conf.check_tool('compiler_cxx')
   230     conf.check_tool('cflags')
   231     try:
   232         conf.check_tool('pkgconfig')
   233     except Configure.ConfigurationError:
   234         pass
   235     conf.check_tool('command')
   236 
   237     # Check for the location of regression reference traces
   238     if Options.options.regression_traces is not None:
   239         if os.path.isdir(Options.options.regression_traces):
   240             conf.check_message("regression traces location", '', True, ("%s (given)" % Options.options.regression_traces))
   241             conf.env['REGRESSION_TRACES'] = os.path.abspath(Options.options.regression_traces)
   242     else:
   243         traces = os.path.join('..', "%s-%s%s" % (APPNAME, VERSION, REGRESSION_SUFFIX))
   244         if os.path.isdir(traces):
   245             conf.check_message("regression reference traces", '', True, ("%s (guessed)" % traces))
   246             conf.env['REGRESSION_TRACES'] = os.path.abspath(traces)
   247         del traces
   248     if not conf.env['REGRESSION_TRACES']:
   249         conf.check_message("regression reference traces", '', False)
   250 
   251     # create the second environment, set the variant and set its name
   252     variant_env = conf.env.copy()
   253     variant_name = Options.options.build_profile
   254 
   255     if Options.options.enable_gcov:
   256         variant_name += '-gcov'
   257         variant_env.append_value('CCFLAGS', '-fprofile-arcs')
   258         variant_env.append_value('CCFLAGS', '-ftest-coverage')
   259         variant_env.append_value('CXXFLAGS', '-fprofile-arcs')
   260         variant_env.append_value('CXXFLAGS', '-ftest-coverage')
   261         variant_env.append_value('LINKFLAGS', '-fprofile-arcs')
   262     
   263     conf.env['NS3_ACTIVE_VARIANT'] = variant_name
   264     variant_env['NS3_ACTIVE_VARIANT'] = variant_name
   265     variant_env.set_variant(variant_name)
   266     conf.set_env_name(variant_name, variant_env)
   267     conf.setenv(variant_name)
   268     env = variant_env
   269 
   270     env.append_value('CXXDEFINES', 'RUN_SELF_TESTS')
   271     
   272     if env['COMPILER_CXX'] == 'g++' and 'CXXFLAGS' not in os.environ:
   273         if conf.check_compilation_flag('-Wno-error=deprecated-declarations'):
   274             env.append_value('CXXFLAGS', '-Wno-error=deprecated-declarations')
   275         
   276     if Options.options.build_profile == 'debug':
   277         env.append_value('CXXDEFINES', 'NS3_ASSERT_ENABLE')
   278         env.append_value('CXXDEFINES', 'NS3_LOG_ENABLE')
   279 
   280     env['PLATFORM'] = sys.platform
   281 
   282     if conf.env['CXX_NAME'] == 'gcc':
   283         if sys.platform == 'win32':
   284             env.append_value("LINKFLAGS", "-Wl,--enable-runtime-pseudo-reloc")
   285         elif sys.platform == 'cygwin':
   286             env.append_value("LINKFLAGS", "-Wl,--enable-auto-import")
   287 
   288         cxx, = env['CXX']
   289 
   290         p = subprocess.Popen([cxx, '-print-file-name=libstdc++.so'], stdout=subprocess.PIPE)
   291         libstdcxx_location = os.path.dirname(p.stdout.read().strip())
   292         p.wait()
   293         if libstdcxx_location:
   294             conf.env.append_value('NS3_MODULE_PATH', libstdcxx_location)
   295 
   296         if Options.platform in ['linux']:
   297             if conf.check_compilation_flag('-Wl,--soname=foo'):
   298                 env['WL_SONAME_SUPPORTED'] = True
   299 
   300     conf.sub_config('src')
   301     conf.sub_config('bindings/python')
   302 
   303     if Options.options.enable_modules:
   304         conf.env['NS3_ENABLED_MODULES'] = ['ns3-'+mod for mod in
   305                                            Options.options.enable_modules.split(',')]
   306 
   307     # for suid bits
   308     conf.find_program('sudo', var='SUDO')
   309 
   310     why_not_sudo = "because we like it"
   311     if Options.options.enable_sudo and conf.env['SUDO']:
   312         env['ENABLE_SUDO'] = True
   313     else:
   314         env['ENABLE_SUDO'] = False
   315         if Options.options.enable_sudo:
   316             why_not_sudo = "program sudo not found"
   317         else:
   318             why_not_sudo = "option --enable-sudo not selected"
   319 
   320     conf.report_optional_feature("ENABLE_SUDO", "Use sudo to set suid bit", env['ENABLE_SUDO'], why_not_sudo)
   321 
   322     # we cannot pull regression traces without mercurial
   323     conf.find_program('hg', var='MERCURIAL')
   324 
   325     conf.find_program('valgrind', var='VALGRIND')
   326 
   327     env['ENABLE_STATIC_NS3'] = False
   328     if Options.options.enable_static:
   329         if env['PLATFORM'].startswith('linux') and \
   330                 env['CXX_NAME'] == 'gcc':
   331             if re.match('i[3-6]86', os.uname()[4]):
   332                 conf.report_optional_feature("static", "Static build", True, '')
   333                 env['ENABLE_STATIC_NS3'] = True
   334             elif os.uname()[4] == 'x86_64':
   335                 if env['ENABLE_PYTHON_BINDINGS'] and \
   336                         not conf.check_compilation_flag('-mcmodel=large'):
   337                     conf.report_optional_feature("static", "Static build", False,
   338                                                  "Can't enable static builds because " + \
   339                                                      "no -mcmodel=large compiler " \
   340                                                      "option. Try --disable-python or upgrade your " \
   341                                                      "compiler to at least gcc 4.3.x.")
   342                 else:
   343                     conf.report_optional_feature("static", "Static build", True, '')
   344                     env['ENABLE_STATIC_NS3'] = True                    
   345         elif env['CXX_NAME'] == 'gcc' and \
   346                 (env['PLATFORM'].startswith('darwin') or \
   347                      env['PLATFORM'].startswith('cygwin')):
   348                 conf.report_optional_feature("static", "Static build", True, '')
   349                 env['ENABLE_STATIC_NS3'] = True
   350         else:
   351             conf.report_optional_feature("static", "Static build", False,
   352                                          "Unsupported platform")
   353     else:
   354         conf.report_optional_feature("static", "Static build", False,
   355                                      "option --enable-static not selected")
   356 
   357 
   358 
   359     # Write a summary of optional features status
   360     print "---- Summary of optional NS-3 features:"
   361     for (name, caption, was_enabled, reason_not_enabled) in conf.env['NS3_OPTIONAL_FEATURES']:
   362         if was_enabled:
   363             status = 'enabled'
   364         else:
   365             status = 'not enabled (%s)' % reason_not_enabled
   366         print "%-30s: %s" % (caption, status)
   367 
   368 
   369 class SuidBuildTask(Task.TaskBase):
   370     """task that makes a binary Suid
   371     """
   372     after = 'cxx_link cc_link'
   373     maxjobs = 1
   374     def __init__(self, bld, program):
   375         self.bld = bld
   376         self.m_display = 'build-suid'
   377         self.__program = program
   378         self.__env = bld.env.copy ()
   379         super(SuidBuildTask, self).__init__(generator=self)
   380         try:
   381             program_obj = wutils.find_program(self.__program.target, self.__env)
   382         except ValueError, ex:
   383             raise Utils.WafError(str(ex))
   384         program_node = program_obj.path.find_or_declare(ccroot.get_target_name(program_obj))
   385         self.filename = program_node.abspath(self.__env)
   386 
   387 
   388     def run(self):
   389         print >> sys.stderr, 'setting suid bit on executable ' + self.filename
   390         if subprocess.Popen(['sudo', 'chown', 'root', self.filename]).wait():
   391             return 1
   392         if subprocess.Popen(['sudo', 'chmod', 'u+s', self.filename]).wait():
   393             return 1
   394         return 0
   395 
   396     def runnable_status(self):
   397         "RUN_ME SKIP_ME or ASK_LATER"
   398         st = os.stat(self.filename)
   399         if st.st_uid == 0:
   400             return Constants.SKIP_ME
   401         else:
   402             return Constants.RUN_ME
   403 
   404 
   405 def create_suid_program(bld, name):
   406     program = bld.new_task_gen('cxx', 'program')
   407     program.is_ns3_program = True
   408     program.module_deps = list()
   409     program.name = name
   410     program.target = name
   411 
   412     if bld.env['ENABLE_SUDO']:
   413         SuidBuildTask(bld, program)
   414 
   415     return program
   416 
   417 def create_ns3_program(bld, name, dependencies=('simulator',)):
   418     program = bld.new_task_gen('cxx', 'program')
   419     program.is_ns3_program = True
   420     program.name = name
   421     program.target = program.name
   422     program.uselib_local = 'ns3'
   423     program.ns3_module_dependencies = ['ns3-'+dep for dep in dependencies]
   424     if program.env['ENABLE_STATIC_NS3']:
   425         if sys.platform == 'darwin':
   426             program.env.append_value('LINKFLAGS', '-Wl,-all_load')
   427             program.env.append_value('LINKFLAGS', '-lns3')
   428         else:
   429             program.env.append_value('LINKFLAGS', '-Wl,--whole-archive,-Bstatic')
   430             program.env.append_value('LINKFLAGS', '-lns3')
   431             program.env.append_value('LINKFLAGS', '-Wl,-Bdynamic,--no-whole-archive')
   432     return program
   433 
   434 def add_scratch_programs(bld):
   435     all_modules = [mod[len("ns3-"):] for mod in bld.env['NS3_MODULES']]
   436     for filename in os.listdir("scratch"):
   437         if filename.startswith('.') or filename == 'CVS':
   438 	    continue
   439         if os.path.isdir(os.path.join("scratch", filename)):
   440             obj = bld.create_ns3_program(filename, all_modules)
   441             obj.path = obj.path.find_dir('scratch').find_dir(filename)
   442             obj.find_sources_in_dirs('.')
   443             obj.target = filename
   444             obj.name = obj.target
   445         elif filename.endswith(".cc"):
   446             name = filename[:-len(".cc")]
   447             obj = bld.create_ns3_program(name, all_modules)
   448             obj.path = obj.path.find_dir('scratch')
   449             obj.source = filename
   450             obj.target = name
   451             obj.name = obj.target
   452 
   453 
   454 def build(bld):
   455     wutils.bld = bld
   456     if Options.options.no_task_lines:
   457         import Runner
   458         def null_printout(s):
   459             pass
   460         Runner.printout = null_printout
   461 
   462     Options.cwd_launch = bld.path.abspath()
   463     bld.create_ns3_program = types.MethodType(create_ns3_program, bld)
   464     bld.create_suid_program = types.MethodType(create_suid_program, bld)
   465 
   466     # switch default variant to the one matching our debug level
   467     variant_name = bld.env_of_name('default')['NS3_ACTIVE_VARIANT']
   468     variant_env = bld.env_of_name(variant_name)
   469     bld.all_envs['default'] = variant_env
   470 
   471     # process subfolders from here
   472     bld.add_subdirs('src')
   473     bld.add_subdirs('samples utils examples')
   474 
   475     add_scratch_programs(bld)
   476 
   477     ## if --enabled-modules option was given, we disable building the
   478     ## modules that were not enabled, and programs that depend on
   479     ## disabled modules.
   480     env = bld.env
   481 
   482     if Options.options.enable_modules:
   483         Logs.warn("the option --enable-modules is being applied to this build only;"
   484                        " to make it permanent it needs to be given to waf configure.")
   485         env['NS3_ENABLED_MODULES'] = ['ns3-'+mod for mod in
   486                                       Options.options.enable_modules.split(',')]
   487 
   488     if env['NS3_ENABLED_MODULES']:
   489         modules = env['NS3_ENABLED_MODULES']
   490         changed = True
   491         while changed:
   492             changed = False
   493             for module in modules:
   494                 module_obj = Object.name_to_obj(module)
   495                 if module_obj is None:
   496                     raise ValueError("module %s not found" % module)
   497                 for dep in module_obj.add_objects:
   498                     if not dep.startswith('ns3-'):
   499                         continue
   500                     if dep not in modules:
   501                         modules.append(dep)
   502                         changed = True
   503 
   504         ## remove objects that depend on modules not listed
   505         for obj in list(bld.all_task_gen):
   506             if hasattr(obj, 'ns3_module_dependencies'):
   507                 for dep in obj.ns3_module_dependencies:
   508                     if dep not in modules:
   509                         bld.all_task_gen.remove(obj)
   510                         break
   511             if obj.name in env['NS3_MODULES'] and obj.name not in modules:
   512                 bld.all_task_gen.remove(obj)
   513 
   514     ## Create a single ns3 library containing all enabled modules
   515     if env['ENABLE_STATIC_NS3']:
   516         lib = bld.new_task_gen('cxx', 'staticlib')
   517         lib.name = 'ns3'
   518         lib.target = 'ns3'
   519     else:
   520         lib = bld.new_task_gen('cxx', 'shlib')
   521         lib.name = 'ns3'
   522         lib.target = 'ns3'
   523         if lib.env['CXX_NAME'] == 'gcc' and env['WL_SONAME_SUPPORTED']:
   524             lib.env.append_value('LINKFLAGS', '-Wl,--soname=%s' % ccroot.get_target_name(lib))
   525         if sys.platform == 'cygwin':
   526             lib.features.append('implib') # workaround for WAF bug #472
   527 
   528     if env['NS3_ENABLED_MODULES']:
   529         lib.add_objects = list(modules)
   530         env['NS3_ENABLED_MODULES'] = list(modules)
   531         lib.uselib_local = list(modules)
   532     else:
   533         lib.add_objects = list(env['NS3_MODULES'])
   534         lib.uselib_local = list(env['NS3_MODULES'])
   535 
   536     bld.add_subdirs('bindings/python')
   537 
   538     if Options.options.run:
   539         # Check that the requested program name is valid
   540         program_name, dummy_program_argv = wutils.get_run_program(Options.options.run, wutils.get_command_template(env))
   541 
   542         # When --run'ing a program, tell WAF to only build that program,
   543         # nothing more; this greatly speeds up compilation when all you
   544         # want to do is run a test program.
   545         if not Options.options.compile_targets:
   546             Options.options.compile_targets = os.path.basename(program_name)
   547             for gen in bld.all_task_gen:
   548                 if type(gen).__name__ in ['ns3header_taskgen', 'ns3moduleheader_taskgen']:
   549                     gen.post()
   550 
   551     if Options.options.regression or Options.options.regression_generate:
   552         regression_traces = env['REGRESSION_TRACES']
   553         if not regression_traces:
   554             raise Utils.WafError("Cannot run regression tests: reference traces directory not given"
   555                                  " (--with-regression-traces configure option)")
   556         regression.run_regression(bld, regression_traces)
   557 
   558     if Options.options.check:
   559         _run_check(bld)
   560 
   561 
   562 def shutdown(ctx):
   563     bld = wutils.bld
   564     if wutils.bld is None:
   565         return
   566     env = bld.env
   567 
   568     #if Options.commands['check']:
   569     #    _run_waf_check()
   570 
   571     if Options.options.lcov_report:
   572         lcov_report()
   573 
   574     if Options.options.run:
   575         wutils.run_program(Options.options.run, env, wutils.get_command_template(env))
   576         raise SystemExit(0)
   577 
   578     if Options.options.pyrun:
   579         wutils.run_python_program(Options.options.pyrun, env)
   580         raise SystemExit(0)
   581 
   582     if Options.options.shell:
   583         raise Utils.WafError("Run `./waf shell' now, instead of `./waf shell'")
   584 
   585     if Options.options.doxygen:
   586         doxygen()
   587         raise SystemExit(0)
   588 
   589     check_shell(bld)
   590 
   591     if Options.options.doxygen:
   592         doxygen()
   593         raise SystemExit(0)
   594 
   595 
   596 check_context = Build.BuildContext
   597 def check(bld):
   598     """run the NS-3 unit tests (deprecated in favour of --check option)"""
   599     raise Utils.WafError("Please run `./waf --check' instead.")
   600 
   601 
   602 class print_introspected_doxygen_task(Task.TaskBase):
   603     after = 'cc cxx cc_link cxx_link'
   604     color = 'BLUE'
   605 
   606     def __init__(self, bld):
   607         self.bld = bld
   608         super(print_introspected_doxygen_task, self).__init__(generator=self)
   609         
   610     def __str__(self):
   611         return 'print-introspected-doxygen\n'
   612 
   613     def runnable_status(self):
   614         return Task.RUN_ME
   615 
   616     def run(self):
   617         ## generate the trace sources list docs
   618         env = wutils.bld.env
   619         proc_env = wutils.get_proc_env()
   620         try:
   621             program_obj = wutils.find_program('print-introspected-doxygen', env)
   622         except ValueError: # could happen if print-introspected-doxygen is
   623                            # not built because of waf configure
   624                            # --enable-modules=xxx
   625             pass
   626         else:
   627             prog = program_obj.path.find_or_declare(ccroot.get_target_name(program_obj)).abspath(env)
   628             out = open(os.path.join('..', 'doc', 'introspected-doxygen.h'), 'w')
   629             if subprocess.Popen([prog], stdout=out, env=proc_env).wait():
   630                 raise SystemExit(1)
   631             out.close()
   632 
   633 class run_python_unit_tests_task(Task.TaskBase):
   634     after = 'cc cxx cc_link cxx_link'
   635     color = 'BLUE'
   636 
   637     def __init__(self, bld):
   638         self.bld = bld
   639         super(run_python_unit_tests_task, self).__init__(generator=self)
   640         
   641     def __str__(self):
   642         return 'run-python-unit-tests\n'
   643 
   644     def runnable_status(self):
   645         return Task.RUN_ME
   646 
   647     def run(self):
   648         proc_env = wutils.get_proc_env()
   649         wutils.run_argv([self.bld.env['PYTHON'], os.path.join("..", "utils", "python-unit-tests.py")],
   650                         self.bld.env, proc_env, force_no_valgrind=True)
   651 
   652 
   653 class run_a_unit_test_task(Task.TaskBase):
   654     after = 'cc cxx cc_link cxx_link'
   655     color = 'BLUE'
   656 
   657     def __init__(self, bld, name_of_test):
   658         self.bld = bld
   659         super(run_a_unit_test_task, self).__init__(generator=self)
   660         self.name_of_test = name_of_test
   661         try:
   662             program_obj = wutils.find_program("run-tests", self.bld.env)
   663         except ValueError, ex:
   664             raise Utils.WafError(str(ex))
   665         program_node = program_obj.path.find_or_declare(ccroot.get_target_name(program_obj))
   666         self.program_path = program_node.abspath(self.bld.env)
   667 
   668     def __str__(self):
   669         return 'run-unit-test(%s)\n' % self.name_of_test
   670 
   671     def runnable_status(self):
   672         return Task.RUN_ME
   673 
   674     def run(self):
   675         #print repr([self.program_path, self.name_of_test])
   676         try:
   677             self.retval = wutils.run_argv([self.program_path, self.name_of_test], self.bld.env)
   678         except Utils.WafError:
   679             self.retval = 1
   680         #print "running test %s: exit with %i" % (self.name_of_test, retval)
   681         return 0
   682 
   683 class get_list_of_unit_tests_task(Task.TaskBase):
   684     after = 'cc cxx cc_link cxx_link'
   685     color = 'BLUE'
   686 
   687     def __init__(self, bld):
   688         self.bld = bld
   689         super(get_list_of_unit_tests_task, self).__init__(generator=self)
   690         self.tests = []
   691 
   692     def __str__(self):
   693         return 'get-unit-tests-list\n'
   694 
   695     def runnable_status(self):
   696         return Task.RUN_ME
   697 
   698     def run(self):
   699         try:
   700             program_obj = wutils.find_program("run-tests", self.bld.env)
   701         except ValueError, ex:
   702             raise Utils.WafError(str(ex))
   703         program_node = program_obj.path.find_or_declare(ccroot.get_target_name(program_obj))
   704         program_path = program_node.abspath(self.bld.env)
   705         proc = subprocess.Popen([program_path, "--ListTests"], stdout=subprocess.PIPE,
   706                                 env=wutils.get_proc_env())
   707         self.tests = [l.rstrip() for l in proc.stdout.readlines()]
   708         retval = proc.wait()
   709         if retval:
   710             return retval
   711         test_tasks = []
   712         for name_of_test in self.tests:
   713             test_tasks.append(run_a_unit_test_task(self.bld, name_of_test))
   714         collector = collect_unit_test_results_task(self.bld, list(test_tasks))
   715         collector.run_after = list(test_tasks)
   716         self.more_tasks = [collector] + test_tasks
   717         
   718 
   719 class collect_unit_test_results_task(Task.TaskBase):
   720     after = 'run_a_unit_test_task'
   721     color = 'BLUE'
   722 
   723     def __init__(self, bld, test_tasks):
   724         self.bld = bld
   725         super(collect_unit_test_results_task, self).__init__(generator=self)
   726         self.test_tasks = test_tasks
   727 
   728     def __str__(self):
   729         return 'collect-unit-tests-results\n'
   730 
   731     def runnable_status(self):
   732         for t in self.run_after:
   733             if not t.hasrun:
   734                 return Task.ASK_LATER
   735         return Task.RUN_ME
   736 
   737     def run(self):
   738         failed_tasks = []
   739         for task in self.test_tasks:
   740             if task.retval:
   741                 failed_tasks.append(task)
   742         if failed_tasks:
   743             print "C++ UNIT TESTS: %i tests passed, %i failed (%s)." % \
   744                 (len(self.test_tasks) - len(failed_tasks), len(failed_tasks),
   745                  ', '.join(t.name_of_test for t in failed_tasks))
   746             return 1
   747         else:
   748             print "C++ UNIT TESTS: all %i tests passed." % (len(self.test_tasks),)
   749             return 0
   750 
   751 
   752 def _run_check(bld):
   753     task = get_list_of_unit_tests_task(bld)
   754     print_introspected_doxygen_task(bld)
   755     if bld.env['ENABLE_PYTHON_BINDINGS']:
   756         run_python_unit_tests_task(bld)
   757 
   758 
   759 def check_shell(bld):
   760     if 'NS3_MODULE_PATH' not in os.environ:
   761         return
   762     env = bld.env
   763     correct_modpath = os.pathsep.join(env['NS3_MODULE_PATH'])
   764     found_modpath = os.environ['NS3_MODULE_PATH']
   765     if found_modpath != correct_modpath:
   766         msg = ("Detected shell (./waf shell) with incorrect configuration\n"
   767                "=========================================================\n"
   768                "Possible reasons for this problem:\n"
   769                "  1. You switched to another ns-3 tree from inside this shell\n"
   770                "  2. You switched ns-3 debug level (waf configure --debug)\n"
   771                "  3. You modified the list of built ns-3 modules\n"
   772                "You should correct this situation before running any program.  Possible solutions:\n"
   773                "  1. Exit this shell, and start a new one\n"
   774                "  2. Run a new nested shell")
   775         raise Utils.WafError(msg)
   776 
   777 
   778 shell_context = Build.BuildContext
   779 def shell(ctx):
   780     """run a shell with an environment suitably modified to run locally built programs"""
   781 
   782     #make sure we build first"
   783     Scripting.build(ctx)
   784 
   785     if sys.platform == 'win32':
   786         shell = os.environ.get("COMSPEC", "cmd.exe")
   787     else:
   788         shell = os.environ.get("SHELL", "/bin/sh")
   789 
   790     env = wutils.bld.env
   791     wutils.run_argv([shell], env, {'NS3_MODULE_PATH': os.pathsep.join(env['NS3_MODULE_PATH'])})
   792 
   793 def doxygen():
   794     if not os.path.exists('doc/introspected-doxygen.h'):
   795         Logs.warn("doc/introspected-doxygen.h does not exist; run waf check to generate it.")
   796 
   797     ## run doxygen
   798     doxygen_config = os.path.join('doc', 'doxygen.conf')
   799     if subprocess.Popen(['doxygen', doxygen_config]).wait():
   800         raise SystemExit(1)
   801 
   802 def lcov_report():
   803     env = Build.bld.env
   804     variant_name = env['NS3_ACTIVE_VARIANT']
   805 
   806     if 'gcov' not in variant_name:
   807         raise Utils.WafError("project not configured for code coverage;"
   808                      " reconfigure with --enable-gcov")
   809 
   810     os.chdir(blddir)
   811     try:
   812         lcov_report_dir = os.path.join(variant_name, 'lcov-report')
   813         create_dir_command = "rm -rf " + lcov_report_dir
   814         create_dir_command += " && mkdir " + lcov_report_dir + ";"
   815 
   816         if subprocess.Popen(create_dir_command, shell=True).wait():
   817             raise SystemExit(1)
   818 
   819         info_file = os.path.join(lcov_report_dir, variant_name + '.info')
   820         lcov_command = "../utils/lcov/lcov -c -d . -o " + info_file
   821         lcov_command += " --source-dirs=" + os.getcwd()
   822         lcov_command += ":" + os.path.join(
   823             os.getcwd(), variant_name, 'include')
   824         if subprocess.Popen(lcov_command, shell=True).wait():
   825             raise SystemExit(1)
   826 
   827         genhtml_command = "../utils/lcov/genhtml -o " + lcov_report_dir
   828         genhtml_command += " " + info_file
   829         if subprocess.Popen(genhtml_command, shell=True).wait():
   830             raise SystemExit(1)
   831     finally:
   832         os.chdir("..")
   833 
   834 
   835 
   836 
   837 ##
   838 ## The default WAF DistDir implementation is rather slow, because it
   839 ## first copies everything and only later removes unwanted files and
   840 ## directories; this means that it needless copies the full build dir
   841 ## and the .hg repository tree.  Here we provide a replacement DistDir
   842 ## implementation that is more efficient.
   843 ##
   844 import Scripting
   845 from Scripting import dist_exts, excludes, BLDDIR
   846 import Utils
   847 import os
   848 
   849 def _copytree(src, dst, symlinks=False, excludes=(), build_dir=None):
   850     """Recursively copy a directory tree using copy2().
   851 
   852     The destination directory must not already exist.
   853     If exception(s) occur, an Error is raised with a list of reasons.
   854 
   855     If the optional symlinks flag is true, symbolic links in the
   856     source tree result in symbolic links in the destination tree; if
   857     it is false, the contents of the files pointed to by symbolic
   858     links are copied.
   859 
   860     XXX Consider this example code rather than the ultimate tool.
   861 
   862     Note: this is a modified version of shutil.copytree from python
   863     2.5.2 library; modified for WAF purposes to exclude dot dirs and
   864     another list of files.
   865     """
   866     names = os.listdir(src)
   867     os.makedirs(dst)
   868     errors = []
   869     for name in names:
   870         srcname = os.path.join(src, name)
   871         dstname = os.path.join(dst, name)
   872         try:
   873             if symlinks and os.path.islink(srcname):
   874                 linkto = os.readlink(srcname)
   875                 os.symlink(linkto, dstname)
   876             elif os.path.isdir(srcname):
   877                 if name in excludes:
   878                     continue
   879                 elif name.startswith('.') or name.startswith(',,') or name.startswith('++') or name.startswith('CVS'):
   880                     continue
   881                 elif name == build_dir:
   882                     continue
   883                 else:
   884                     ## build_dir is not passed into the recursive
   885                     ## copytree, but that is intentional; it is a
   886                     ## directory name valid only at the top level.
   887                     copytree(srcname, dstname, symlinks, excludes)
   888             else:
   889                 ends = name.endswith
   890                 to_remove = False
   891                 if name.startswith('.') or name.startswith('++'):
   892                     to_remove = True
   893                 else:
   894                     for x in dist_exts:
   895                         if ends(x):
   896                             to_remove = True
   897                             break
   898                 if not to_remove:
   899                     shutil.copy2(srcname, dstname)
   900             # XXX What about devices, sockets etc.?
   901         except (IOError, os.error), why:
   902             errors.append((srcname, dstname, str(why)))
   903         # catch the Error from the recursive copytree so that we can
   904         # continue with other files
   905         except shutil.Error, err:
   906             errors.extend(err.args[0])
   907     try:
   908         shutil.copystat(src, dst)
   909     except WindowsError:
   910         # can't copy file access times on Windows
   911         pass
   912     except OSError, why:
   913         errors.extend((src, dst, str(why)))
   914     if errors:
   915         raise shutil.Error, errors
   916 
   917 
   918 def DistDir(appname, version):
   919     #"make a distribution directory with all the sources in it"
   920     import shutil
   921 
   922     # Our temporary folder where to put our files
   923     TMPFOLDER=appname+'-'+version
   924 
   925     # Remove an old package directory
   926     if os.path.exists(TMPFOLDER): shutil.rmtree(TMPFOLDER)
   927 
   928     global g_dist_exts, g_excludes
   929 
   930     # Remove the Build dir
   931     build_dir = getattr(Utils.g_module, BLDDIR, None)
   932 
   933     # Copy everything into the new folder
   934     _copytree('.', TMPFOLDER, excludes=excludes, build_dir=build_dir)
   935 
   936     # TODO undocumented hook
   937     dist_hook = getattr(Utils.g_module, 'dist_hook', None)
   938     if dist_hook:
   939         os.chdir(TMPFOLDER)
   940         try:
   941             dist_hook()
   942         finally:
   943             # go back to the root directory
   944             os.chdir('..')
   945     return TMPFOLDER
   946 
   947 Scripting.DistDir = DistDir
   948 
   949