wscript
author Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
Wed Jun 18 15:21:08 2008 +0100 (2008-06-18)
changeset 3282 5e57c2f1cfb7
parent 3281 bc61af712d6e
child 3283 9dd59253d39d
child 3284 510fed881852
permissions -rw-r--r--
Add a WAF workaround for the 'Input line is too long.' error in win32/mingw.
     1 ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
     2 import sys
     3 import shlex
     4 import shutil
     5 import types
     6 import optparse
     7 import os.path
     8 
     9 import pproc as subprocess
    10 
    11 import Params
    12 import Object
    13 import ccroot
    14 
    15 Params.g_autoconfig = 1
    16 
    17 
    18 # the following two variables are used by the target "waf dist"
    19 VERSION = file("VERSION").read().strip()
    20 APPNAME = 'ns'
    21 
    22 # these variables are mandatory ('/' are converted automatically)
    23 srcdir = '.'
    24 blddir = 'build'
    25 
    26 #
    27 # The directory in which the tarball of the reference traces lives.  This is
    28 # used if Mercurial is not on the system.
    29 #
    30 REGRESSION_TRACES_URL = "http://www.nsnam.org/releases/"
    31 
    32 #
    33 # The name of the tarball to find the reference traces in if there is no
    34 # mercurial on the system.  It is expected to be created using tar -cjf and
    35 # will be extracted using tar -xjf
    36 #
    37 REGRESSION_TRACES_TAR_NAME  = "ns-3.1-RC1-ref-traces.tar.bz2"
    38 
    39 #
    40 # The path to the Mercurial repository used to find the reference traces if
    41 # we find "hg" on the system.  We expect that the repository will be named
    42 # identically to refDirName below
    43 #
    44 REGRESSION_TRACES_REPO = "http://code.nsnam.org/"
    45 
    46 #
    47 # The local directory name (relative to the 'regression' dir) into
    48 # which the reference traces will go in either case (net or hg).
    49 #
    50 REGRESSION_TRACES_DIR_NAME = "ns-3-dev-ref-traces"
    51 
    52 
    53 def dist_hook():
    54     import tarfile
    55     shutil.rmtree("doc/html", True)
    56     shutil.rmtree("doc/latex", True)
    57 
    58     ## Create a tar.bz2 file with the traces
    59     traces_dir = os.path.join("regression", "ns-3-dev-ref-traces")
    60     if not os.path.isdir(traces_dir):
    61         Params.warning("Not creating traces archive: the %s directory does not exist" % traces_dir)
    62     else:
    63         tar = tarfile.open(os.path.join("..", "ns-%s-ref-traces.tar.bz2" % VERSION), 'w:bz2')
    64         tar.add(traces_dir, "ns-3-dev-ref-traces")
    65         tar.close()
    66         ## Now remove it; we do not ship the traces with the main tarball...
    67         shutil.rmtree(traces_dir, True)
    68 
    69 
    70 def set_options(opt):
    71 
    72     def debug_option_callback(option, opt, value, parser):
    73         if value == 'debug':
    74             setattr(parser.values, option.dest, 'ultradebug')
    75         elif value == 'optimized':
    76             setattr(parser.values, option.dest, 'optimized')
    77         else:
    78             raise optparse.OptionValueError("allowed --debug-level values"
    79                                             " are debug, optimized.")
    80 
    81     opt.add_option('-d', '--debug-level',
    82                    action='callback',
    83                    type="string", dest='debug_level', default='ultradebug',
    84                    help=('Specify the debug level, does nothing if CFLAGS is set'
    85                          ' in the environment. [Allowed Values: debug, optimized].'
    86                          ' WARNING: this option only has effect '
    87                          'with the configure command.'),
    88                    callback=debug_option_callback)
    89     
    90     # options provided by the modules
    91     opt.tool_options('compiler_cxx')
    92 
    93     opt.add_option('--cwd',
    94                    help=('Set the working directory for a program.'),
    95                    action="store", type="string", default=None,
    96                    dest='cwd_launch')
    97 
    98     opt.add_option('--enable-gcov',
    99                    help=('Enable code coverage analysis.'
   100                          ' WARNING: this option only has effect '
   101                          'with the configure command.'),
   102                    action="store_true", default=False,
   103                    dest='enable_gcov')
   104 
   105     opt.add_option('--no-task-lines',
   106                    help=("Don't print task lines, i.e. messages saying which tasks are being executed by WAF."
   107                          "  Coupled with a single -v will cause WAF to output only the executed commands,"
   108                          " just like 'make' does by default."),
   109                    action="store_true", default=False,
   110                    dest='no_task_lines')
   111 
   112     opt.add_option('--lcov-report',
   113                    help=('Generate a code coverage report '
   114                          '(use this option at build time, not in configure)'),
   115                    action="store_true", default=False,
   116                    dest='lcov_report')
   117 
   118     opt.add_option('--doxygen',
   119                    help=('Run doxygen to generate html documentation from source comments'),
   120                    action="store_true", default=False,
   121                    dest='doxygen')
   122 
   123     opt.add_option('--run',
   124                    help=('Run a locally built program; argument can be a program name,'
   125                          ' or a command starting with the program name.'),
   126                    type="string", default='', dest='run')
   127     opt.add_option('--command-template',
   128                    help=('Template of the command used to run the program given by --run;'
   129                          ' It should be a shell command string containing %s inside,'
   130                          ' which will be replaced by the actual program.'),
   131                    type="string", default=None, dest='command_template')
   132     opt.add_option('--valgrind',
   133                    help=('Change the default command template to run programs and unit tests with valgrind'),
   134                    action="store_true", default=False,
   135                    dest='valgrind')
   136     opt.add_option('--shell',
   137                    help=('Run a shell with an environment suitably modified to run locally built programs'),
   138                    action="store_true", default=False,
   139                    dest='shell')
   140 
   141     opt.add_option('--regression',
   142                    help=("Enable regression testing; only used for the 'check' target"),
   143                    default=False, dest='regression', action="store_true")
   144     opt.add_option('--regression-generate',
   145                    help=("Generate new regression test traces."),
   146                    default=False, dest='regression_generate', action="store_true")
   147     opt.add_option('--regression-tests',
   148                    help=('For regression testing, only run/generate the indicated regression tests, '
   149                          'specified as a comma separated list of test names'),
   150                    dest='regression_tests', type="string")
   151 
   152     # options provided in a script in a subdirectory named "src"
   153     opt.sub_options('src')
   154 
   155 
   156 def configure(conf):
   157     conf.check_tool('compiler_cxx')
   158 
   159     # create the second environment, set the variant and set its name
   160     variant_env = conf.env.copy()
   161     debug_level = Params.g_options.debug_level.lower()
   162     if debug_level == 'ultradebug':
   163         variant_name = 'debug'
   164     else:
   165         variant_name = debug_level
   166 
   167     variant_env['INCLUDEDIR'] = os.path.join(variant_env['PREFIX'], 'include')
   168 
   169     if Params.g_options.enable_gcov:
   170         variant_name += '-gcov'
   171         variant_env.append_value('CCFLAGS', '-fprofile-arcs')
   172         variant_env.append_value('CCFLAGS', '-ftest-coverage')
   173         variant_env.append_value('CXXFLAGS', '-fprofile-arcs')
   174         variant_env.append_value('CXXFLAGS', '-ftest-coverage')
   175         variant_env.append_value('LINKFLAGS', '-fprofile-arcs')
   176     
   177     conf.env['NS3_ACTIVE_VARIANT'] = variant_name
   178     variant_env['NS3_ACTIVE_VARIANT'] = variant_name
   179     variant_env.set_variant(variant_name)
   180     conf.set_env_name(variant_name, variant_env)
   181     conf.setenv(variant_name)
   182 
   183     variant_env.append_value('CXXDEFINES', 'RUN_SELF_TESTS')
   184     
   185     if (os.path.basename(conf.env['CXX']).startswith("g++")
   186         and 'CXXFLAGS' not in os.environ):
   187         variant_env.append_value('CXXFLAGS', ['-Werror'])
   188 
   189     if 'debug' in Params.g_options.debug_level.lower():
   190         variant_env.append_value('CXXDEFINES', 'NS3_ASSERT_ENABLE')
   191         variant_env.append_value('CXXDEFINES', 'NS3_LOG_ENABLE')
   192 
   193     ## In optimized builds we still want debugging symbols, e.g. for
   194     ## profiling, and at least partially usable stack traces.
   195     if ('optimized' in Params.g_options.debug_level.lower() 
   196         and 'CXXFLAGS' not in os.environ):
   197         for flag in variant_env['CXXFLAGS_DEBUG']:
   198             ## this probably doesn't work for MSVC
   199             if flag.startswith('-g'):
   200                 variant_env.append_value('CXXFLAGS', flag)
   201 
   202     ## in optimized builds, replace -O2 with -O3
   203     if 'optimized' in Params.g_options.debug_level.lower():
   204         lst = variant_env['CXXFLAGS']
   205         for i, flag in enumerate(lst):
   206             if flag == '-O2':
   207                 lst[i] = '-O3'
   208 
   209     if sys.platform == 'win32':
   210         if os.path.basename(conf.env['CXX']).startswith("g++"):
   211             variant_env.append_value("LINKFLAGS", "-Wl,--enable-runtime-pseudo-reloc")
   212 
   213     conf.sub_config('src')
   214     conf.sub_config('utils')
   215 
   216     if Params.g_options.enable_modules:
   217         conf.env['NS3_ENABLED_MODULES'] = ['ns3-'+mod for mod in
   218                                            Params.g_options.enable_modules.split(',')]
   219 
   220     ## we cannot run regression tests without diff
   221     conf.find_program('diff', var='DIFF')
   222 
   223 
   224 def create_ns3_program(bld, name, dependencies=('simulator',)):
   225     program = bld.create_obj('cpp', 'program')
   226     program.is_ns3_program = True
   227     program.name = name
   228     program.target = program.name
   229     program.uselib_local = 'ns3'
   230     program.ns3_module_dependencies = ['ns3-'+dep for dep in dependencies]
   231     return program
   232 
   233 def add_scratch_programs(bld):
   234     all_modules = [mod[len("ns3-"):] for mod in bld.env()['NS3_MODULES']]
   235     for filename in os.listdir("scratch"):
   236         if os.path.isdir(os.path.join("scratch", filename)):
   237             obj = bld.create_ns3_program(filename, all_modules)
   238             obj.path = obj.path.find_dir('scratch')
   239             obj.find_sources_in_dirs(filename)
   240             obj.target = os.path.join(filename, filename)
   241         elif filename.endswith(".cc"):
   242             name = filename[:-len(".cc")]
   243             obj = bld.create_ns3_program(name, all_modules)
   244             obj.source = "scratch/%s" % filename
   245             obj.target = "scratch/%s" % name
   246 
   247 
   248 ##
   249 ## This replacement spawn function increases the maximum command line length to 32k
   250 ##
   251 def _exec_command_interact_win32(s):
   252     if Params.g_verbose:
   253         print s
   254     startupinfo = subprocess.STARTUPINFO()
   255     startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
   256     proc = subprocess.Popen(s, shell=False, startupinfo=startupinfo)
   257     stat = proc.wait()
   258     if stat & 0xff:
   259         return stat | 0x80
   260     return stat >> 8
   261 
   262 
   263 def build(bld):
   264     if Params.g_options.no_task_lines:
   265         import Runner
   266         def null_printout(s):
   267             pass
   268         Runner.printout = null_printout
   269 
   270     if sys.platform == 'win32':
   271         import Runner
   272         Runner.exec_command = _exec_command_interact_win32
   273 
   274     Params.g_cwd_launch = Params.g_build.m_curdirnode.abspath()
   275     bld.create_ns3_program = types.MethodType(create_ns3_program, bld)
   276     variant_name = bld.env_of_name('default')['NS3_ACTIVE_VARIANT']
   277     variant_env = bld.env_of_name(variant_name)
   278     bld.m_allenvs['default'] = variant_env # switch to the active variant
   279 
   280     if Params.g_options.shell:
   281         run_shell()
   282         raise SystemExit(0)
   283 
   284     if Params.g_options.doxygen:
   285         doxygen()
   286         raise SystemExit(0)
   287 
   288     check_shell()
   289 
   290     if Params.g_options.doxygen:
   291         doxygen()
   292         raise SystemExit(0)
   293 
   294     print "Entering directory `%s'" % os.path.join(Params.g_build.m_curdirnode.abspath(), 'build')
   295     # process subfolders from here
   296     bld.add_subdirs('src')
   297     bld.add_subdirs('samples utils examples tutorial')
   298 
   299     add_scratch_programs(bld)
   300 
   301     ## if --enabled-modules option was given, we disable building the
   302     ## modules that were not enabled, and programs that depend on
   303     ## disabled modules.
   304     env = bld.env()
   305 
   306     if Params.g_options.enable_modules:
   307         Params.warning("the option --enable-modules is being applied to this build only;"
   308                        " to make it permanent it needs to be given to waf configure.")
   309         env['NS3_ENABLED_MODULES'] = ['ns3-'+mod for mod in
   310                                       Params.g_options.enable_modules.split(',')]
   311 
   312     if env['NS3_ENABLED_MODULES']:
   313         modules = env['NS3_ENABLED_MODULES']
   314         changed = True
   315         while changed:
   316             changed = False
   317             for module in modules:
   318                 module_obj = Object.name_to_obj(module)
   319                 if module_obj is None:
   320                     raise ValueError("module %s not found" % module)
   321                 for dep in module_obj.add_objects:
   322                     if not dep.startswith('ns3-'):
   323                         continue
   324                     if dep not in modules:
   325                         modules.append(dep)
   326                         changed = True
   327 
   328         ## remove objects that depend on modules not listed
   329         for obj in list(Object.g_allobjs):
   330             if hasattr(obj, 'ns3_module_dependencies'):
   331                 for dep in obj.ns3_module_dependencies:
   332                     if dep not in modules:
   333                         Object.g_allobjs.remove(obj)
   334                         break
   335             if obj.name in env['NS3_MODULES'] and obj.name not in modules:
   336                 Object.g_allobjs.remove(obj)
   337 
   338     ## Create a single ns3 library containing all enabled modules
   339     lib = bld.create_obj('cpp', 'shlib')
   340     lib.name = 'ns3'
   341     lib.target = 'ns3'
   342     if env['NS3_ENABLED_MODULES']:
   343         lib.add_objects = list(modules)
   344         lib.uselib_local = list(modules)
   345     else:
   346         lib.add_objects = list(env['NS3_MODULES'])
   347         lib.uselib_local = list(env['NS3_MODULES'])
   348 
   349 
   350 def get_command_template():
   351     if Params.g_options.valgrind:
   352         if Params.g_options.command_template:
   353             Params.fatal("Options --command-template and --valgrind are conflicting")
   354         return "valgrind --leak-check=full %s"
   355     else:
   356         return (Params.g_options.command_template or '%s')
   357 
   358 
   359 def shutdown():
   360     #import UnitTest
   361     #ut = UnitTest.unit_test()
   362     #ut.change_to_testfile_dir = True
   363     #ut.want_to_see_test_output = True
   364     #ut.want_to_see_test_error = True
   365     #ut.run()
   366     #ut.print_results()
   367     env = Params.g_build.env_of_name('default')
   368 
   369     if Params.g_commands['check']:
   370         _run_waf_check()
   371 
   372     if Params.g_options.regression or Params.g_options.regression_generate:
   373         if not env['DIFF']:
   374             Params.fatal("Cannot run regression tests: the 'diff' program is not installed.")
   375         _dir = os.getcwd()
   376         os.chdir("regression")
   377         try:
   378             run_regression()
   379         finally:
   380             os.chdir(_dir)
   381 
   382     if Params.g_options.lcov_report:
   383         lcov_report()
   384 
   385     if Params.g_options.run:
   386         run_program(Params.g_options.run, get_command_template())
   387         raise SystemExit(0)
   388 
   389 def _run_waf_check():
   390     ## generate the trace sources list docs
   391     env = Params.g_build.env_of_name('default')
   392     proc_env = _get_proc_env()
   393     try:
   394         program_obj = _find_program('print-introspected-doxygen', env)
   395     except ValueError: # could happen if print-introspected-doxygen is
   396                        # not built because of waf configure
   397                        # --enable-modules=xxx
   398         pass
   399     else:
   400         prog = program_obj.path.find_build(ccroot.get_target_name(program_obj)).abspath(env)
   401         out = open('doc/introspected-doxygen.h', 'w')
   402         if subprocess.Popen([prog], stdout=out, env=proc_env).wait():
   403             raise SystemExit(1)
   404         out.close()
   405 
   406     run_program('run-tests', get_command_template())
   407 
   408 def _find_program(program_name, env):
   409     launch_dir = os.path.abspath(Params.g_cwd_launch)
   410     found_programs = []
   411     for obj in Object.g_allobjs:
   412         if not getattr(obj, 'is_ns3_program', False):
   413             continue
   414 
   415         ## filter out programs not in the subtree starting at the launch dir
   416         if not (obj.path.abspath().startswith(launch_dir)
   417                 or obj.path.abspath(env).startswith(launch_dir)):
   418             continue
   419         
   420         found_programs.append(obj.target)
   421         if obj.target == program_name:
   422             return obj
   423     raise ValueError("program '%s' not found; available programs are: %r"
   424                      % (program_name, found_programs))
   425 
   426 def _get_proc_env(os_env=None):
   427     env = Params.g_build.env_of_name('default')
   428     if sys.platform == 'linux2':
   429         pathvar = 'LD_LIBRARY_PATH'
   430     elif sys.platform == 'darwin':
   431         pathvar = 'DYLD_LIBRARY_PATH'
   432     elif sys.platform == 'win32':
   433         pathvar = 'PATH'
   434     elif sys.platform == 'cygwin':
   435         pathvar = 'PATH'
   436     else:
   437         Params.warning(("Don't know how to configure "
   438                         "dynamic library path for the platform '%s'") % (sys.platform,))
   439         pathvar = None
   440 
   441     proc_env = dict(os.environ)
   442     if os_env is not None:
   443         proc_env.update(os_env)
   444 
   445     if pathvar is not None:
   446         if pathvar in proc_env:
   447             proc_env[pathvar] = os.pathsep.join(list(env['NS3_MODULE_PATH']) + [proc_env[pathvar]])
   448         else:
   449             proc_env[pathvar] = os.pathsep.join(list(env['NS3_MODULE_PATH']))
   450     return proc_env
   451 
   452 def _run_argv(argv, os_env=None):
   453     proc_env = _get_proc_env(os_env)
   454     retval = subprocess.Popen(argv, env=proc_env).wait()
   455     if retval:
   456         Params.fatal("Command %s exited with code %i" % (argv, retval))
   457 
   458 
   459 def run_program(program_string, command_template=None):
   460     """
   461     if command_template is not None, then program_string == program
   462     name and argv is given by command_template with %s replaced by the
   463     full path to the program.  Else, program_string is interpreted as
   464     a shell command with first name being the program name.
   465     """
   466     env = Params.g_build.env_of_name('default')
   467 
   468     if command_template in (None, '%s'):
   469         argv = shlex.split(program_string)
   470         program_name = argv[0]
   471 
   472         try:
   473             program_obj = _find_program(program_name, env)
   474         except ValueError, ex:
   475             Params.fatal(str(ex))
   476 
   477         try:
   478             program_node = program_obj.path.find_build(ccroot.get_target_name(program_obj))
   479         except AttributeError:
   480             Params.fatal("%s does not appear to be a program" % (program_name,))
   481 
   482         execvec = [program_node.abspath(env)] + argv[1:]
   483 
   484     else:
   485 
   486         program_name = program_string
   487         try:
   488             program_obj = _find_program(program_name, env)
   489         except ValueError, ex:
   490             Params.fatal(str(ex))
   491         try:
   492             program_node = program_obj.path.find_build(ccroot.get_target_name(program_obj))
   493         except AttributeError:
   494             Params.fatal("%s does not appear to be a program" % (program_name,))
   495 
   496         execvec = shlex.split(command_template % (program_node.abspath(env),))
   497 
   498     former_cwd = os.getcwd()
   499     if (Params.g_options.cwd_launch):
   500         os.chdir(Params.g_options.cwd_launch)
   501     else:
   502         os.chdir(Params.g_cwd_launch)
   503     try:
   504         retval = _run_argv(execvec)
   505     finally:
   506         os.chdir(former_cwd)
   507 
   508     return retval
   509 
   510 def check_shell():
   511     if 'NS3_MODULE_PATH' not in os.environ:
   512         return
   513     env = Params.g_build.env_of_name('default')
   514     correct_modpath = os.pathsep.join(env['NS3_MODULE_PATH'])
   515     found_modpath = os.environ['NS3_MODULE_PATH']
   516     if found_modpath != correct_modpath:
   517         msg = ("Detected shell (waf --shell) with incorrect configuration\n"
   518                "=========================================================\n"
   519                "Possible reasons for this problem:\n"
   520                "  1. You switched to another ns-3 tree from inside this shell\n"
   521                "  2. You switched ns-3 debug level (waf configure --debug)\n"
   522                "  3. You modified the list of built ns-3 modules\n"
   523                "You should correct this situation before running any program.  Possible solutions:\n"
   524                "  1. Exit this shell, and start a new one\n"
   525                "  2. Run a new nested shell")
   526         Params.fatal(msg)
   527 
   528 
   529 def run_shell():
   530     if sys.platform == 'win32':
   531         shell = os.environ.get("COMSPEC", "cmd.exe")
   532     else:
   533         shell = os.environ.get("SHELL", "/bin/sh")
   534 
   535     env = Params.g_build.env_of_name('default')
   536     _run_argv([shell], {'NS3_MODULE_PATH': os.pathsep.join(env['NS3_MODULE_PATH'])})
   537 
   538 def doxygen():
   539     if not os.path.exists('doc/introspected-doxygen.h'):
   540         Params.warning("doc/introspected-doxygen.h does not exist; run waf check to generate it.")
   541 
   542     ## run doxygen
   543     doxygen_config = os.path.join('doc', 'doxygen.conf')
   544     if subprocess.Popen(['doxygen', doxygen_config]).wait():
   545         raise SystemExit(1)
   546 
   547 def lcov_report():
   548     env = Params.g_build.env_of_name('default')
   549     variant_name = env['NS3_ACTIVE_VARIANT']
   550 
   551     if 'gcov' not in variant_name:
   552         Params.fatal("project not configured for code coverage;"
   553                      " reconfigure with --enable-gcov")
   554 
   555     os.chdir(blddir)
   556     try:
   557         lcov_report_dir = os.path.join(variant_name, 'lcov-report')
   558         create_dir_command = "rm -rf " + lcov_report_dir
   559         create_dir_command += " && mkdir " + lcov_report_dir + ";"
   560 
   561         if subprocess.Popen(create_dir_command, shell=True).wait():
   562             raise SystemExit(1)
   563 
   564         info_file = os.path.join(lcov_report_dir, variant_name + '.info')
   565         lcov_command = "../utils/lcov/lcov -c -d . -o " + info_file
   566         lcov_command += " --source-dirs=" + os.getcwd()
   567         lcov_command += ":" + os.path.join(
   568             os.getcwd(), variant_name, 'include')
   569         if subprocess.Popen(lcov_command, shell=True).wait():
   570             raise SystemExit(1)
   571 
   572         genhtml_command = "../utils/lcov/genhtml -o " + lcov_report_dir
   573         genhtml_command += " " + info_file
   574         if subprocess.Popen(genhtml_command, shell=True).wait():
   575             raise SystemExit(1)
   576     finally:
   577         os.chdir("..")
   578 
   579 
   580 
   581 
   582 ##
   583 ## The default WAF DistDir implementation is rather slow, because it
   584 ## first copies everything and only later removes unwanted files and
   585 ## directories; this means that it needless copies the full build dir
   586 ## and the .hg repository tree.  Here we provide a replacement DistDir
   587 ## implementation that is more efficient.
   588 ##
   589 import Scripting
   590 from Scripting import g_dist_exts, g_excludes, BLDDIR
   591 import Utils
   592 import os
   593 
   594 def copytree(src, dst, symlinks=False, excludes=(), build_dir=None):
   595     """Recursively copy a directory tree using copy2().
   596 
   597     The destination directory must not already exist.
   598     If exception(s) occur, an Error is raised with a list of reasons.
   599 
   600     If the optional symlinks flag is true, symbolic links in the
   601     source tree result in symbolic links in the destination tree; if
   602     it is false, the contents of the files pointed to by symbolic
   603     links are copied.
   604 
   605     XXX Consider this example code rather than the ultimate tool.
   606 
   607     Note: this is a modified version of shutil.copytree from python
   608     2.5.2 library; modified for WAF purposes to exclude dot dirs and
   609     another list of files.
   610     """
   611     names = os.listdir(src)
   612     os.makedirs(dst)
   613     errors = []
   614     for name in names:
   615         srcname = os.path.join(src, name)
   616         dstname = os.path.join(dst, name)
   617         try:
   618             if symlinks and os.path.islink(srcname):
   619                 linkto = os.readlink(srcname)
   620                 os.symlink(linkto, dstname)
   621             elif os.path.isdir(srcname):
   622                 if name in excludes:
   623                     continue
   624                 elif name.startswith('.') or name.startswith(',,') or name.startswith('++'):
   625                     continue
   626                 elif name == build_dir:
   627                     continue
   628                 else:
   629                     ## build_dir is not passed into the recursive
   630                     ## copytree, but that is intentional; it is a
   631                     ## directory name valid only at the top level.
   632                     copytree(srcname, dstname, symlinks, excludes)
   633             else:
   634                 ends = name.endswith
   635                 to_remove = False
   636                 if name.startswith('.') or name.startswith('++'):
   637                     to_remove = True
   638                 else:
   639                     for x in g_dist_exts:
   640                         if ends(x):
   641                             to_remove = True
   642                             break
   643                 if not to_remove:
   644                     shutil.copy2(srcname, dstname)
   645             # XXX What about devices, sockets etc.?
   646         except (IOError, os.error), why:
   647             errors.append((srcname, dstname, str(why)))
   648         # catch the Error from the recursive copytree so that we can
   649         # continue with other files
   650         except shutil.Error, err:
   651             errors.extend(err.args[0])
   652     try:
   653         shutil.copystat(src, dst)
   654     except WindowsError:
   655         # can't copy file access times on Windows
   656         pass
   657     except OSError, why:
   658         errors.extend((src, dst, str(why)))
   659     if errors:
   660         raise shutil.Error, errors
   661 
   662 
   663 def DistDir(appname, version):
   664     "make a distribution directory with all the sources in it"
   665     import shutil
   666 
   667     # Our temporary folder where to put our files
   668     TMPFOLDER=appname+'-'+version
   669 
   670     # Remove an old package directory
   671     if os.path.exists(TMPFOLDER): shutil.rmtree(TMPFOLDER)
   672 
   673     global g_dist_exts, g_excludes
   674 
   675     # Remove the Build dir
   676     build_dir = getattr(Utils.g_module, BLDDIR, None)
   677 
   678     # Copy everything into the new folder
   679     copytree('.', TMPFOLDER, excludes=g_excludes, build_dir=build_dir)
   680 
   681     # TODO undocumented hook
   682     dist_hook = getattr(Utils.g_module, 'dist_hook', None)
   683     if dist_hook:
   684         os.chdir(TMPFOLDER)
   685         try:
   686             dist_hook()
   687         finally:
   688             # go back to the root directory
   689             os.chdir('..')
   690     return TMPFOLDER
   691 
   692 Scripting.DistDir = DistDir
   693 
   694 
   695 
   696 
   697 ### Regression testing
   698 class Regression(object):
   699     def __init__(self, testdir):
   700         self.testdir = testdir
   701         env = Params.g_build.env_of_name('default')
   702         self.diff = env['DIFF']
   703 
   704     def run_test(self, verbose, generate, refDirName, testName):
   705         refTestDirName = os.path.join(refDirName, (testName + ".ref"))
   706 
   707         if not os.path.exists(refDirName):
   708             print"No reference trace repository"
   709             return 1
   710 
   711         if generate:
   712             if not os.path.exists(refTestDirName):
   713                 print "creating new " + refTestDirName
   714                 os.mkdir(refTestDirName)
   715 
   716             #os.system("./waf --cwd regression/" + refTestDirName +
   717             #    " --run " + testName + " > /dev/null 2>&1")
   718             Params.g_options.cwd_launch = refTestDirName
   719             run_program(testName)
   720 
   721             print "Remember to commit " + refTestDirName
   722             return 0
   723         else:
   724             if not os.path.exists(refTestDirName):
   725                 print "Cannot locate reference traces"
   726                 return 1
   727 
   728             shutil.rmtree("traces");
   729             os.mkdir("traces")
   730 
   731             #os.system("./waf --cwd regression/traces --run " +
   732             #  testName + " > /dev/null 2>&1")
   733             Params.g_options.cwd_launch = "traces"
   734             run_program(testName, command_template=get_command_template())
   735 
   736             if verbose:
   737                 #diffCmd = "diff traces " + refTestDirName + " | head"
   738                 diffCmd = subprocess.Popen([self.diff, "traces", refTestDirName],
   739                                            stderr=subprocess.PIPE, stdout=subprocess.PIPE)
   740                 headCmd = subprocess.Popen("head", stdin=diffCmd.stdout)
   741                 rc2 = headCmd.wait()
   742                 diffCmd.stdout.close()
   743                 rc1 = diffCmd.wait()
   744                 rc = rc1 or rc2
   745             else:
   746                 diffCmd = self.diff +" traces " + refTestDirName + \
   747                     " > /dev/null 2>&1"
   748                 rc = os.system(diffCmd)
   749             if rc:
   750                 print "----------"
   751                 print "Traces differ in test: test-" + testName
   752                 print "Reference traces in directory: regression/" + refTestDirName
   753                 print "Traces in directory: traces"
   754                 print "Rerun regression test as: " + \
   755                     "\"./waf --regression --regression-tests=test-" + testName + "\""
   756                 print "Then do \"diff -u regression/" + refTestDirName + " regression/traces" \
   757                     "\" for details"
   758                 print "----------"
   759             return rc
   760 
   761 def _find_tests(testdir):
   762     """Return a list of test modules in the test directory
   763 
   764     Arguments:
   765     testdir -- the directory to look in for tests
   766     """
   767     names = os.listdir(testdir)
   768     tests = []
   769     for name in names:
   770         if name[:5] == "test-" and name[-3:] == ".py":
   771             testname = name[:-3]
   772             tests.append(testname)
   773     tests.sort()
   774     return tests
   775 
   776 def run_regression():
   777     """Execute regression tests."""
   778 
   779     testdir = "tests"
   780     if not os.path.exists(testdir):
   781         print "Tests directory does not exist"
   782         sys.exit(3)
   783     
   784     sys.path.append(testdir)
   785     sys.modules['tracediff'] = Regression(testdir)
   786 
   787     if Params.g_options.regression_tests:
   788         tests = Params.g_options.regression_tests.split(',')
   789     else:
   790         tests = _find_tests(testdir)
   791 
   792     print "========== Running Regression Tests =========="
   793 
   794     if os.system("hg version > /dev/null 2>&1") == 0:
   795         print "Synchronizing reference traces using Mercurial."
   796         if not os.path.exists(REGRESSION_TRACES_DIR_NAME):
   797             os.system("hg clone " + REGRESSION_TRACES_REPO + REGRESSION_TRACES_DIR_NAME + " > /dev/null 2>&1")
   798         else:
   799             _dir = os.getcwd()
   800             os.chdir(REGRESSION_TRACES_DIR_NAME)
   801             try:
   802                 result = os.system("hg -q pull %s && hg -q update" % (REGRESSION_TRACES_REPO + REGRESSION_TRACES_DIR_NAME))
   803             finally:
   804                 os.chdir("..")
   805             if result:
   806                 Params.fatal("Synchronizing reference traces using Mercurial failed.")
   807     else:
   808         print "Synchronizing reference traces from web."
   809         urllib.urlretrieve(REGRESSION_TRACES_URL + REGRESSION_TRACES_TAR_NAME, REGRESSION_TRACES_TAR_NAME)
   810         os.system("tar -xjf %s" % (REGRESSION_TRACES_TAR_NAME,))
   811 
   812     print "Done."
   813 
   814     if not os.path.exists(REGRESSION_TRACES_DIR_NAME):
   815         print "Reference traces directory does not exist"
   816         return 3
   817     
   818     bad = []
   819 
   820     for test in tests:
   821         result = _run_regression_test(test)
   822         if result == 0:
   823             if Params.g_options.regression_generate:
   824                 print "GENERATE " + test
   825             else:
   826                 print "PASS " + test
   827         else:
   828             bad.append(test)
   829             print "FAIL " + test
   830 
   831     return len(bad) > 0
   832 
   833 
   834 def _run_regression_test(test):
   835     """Run a single test.
   836 
   837     Arguments:
   838     test -- the name of the test
   839     """
   840     if os.path.exists("traces"):
   841         files = os.listdir("traces")
   842         for file in files:
   843             if file == '.' or file == '..':
   844                 continue
   845             path = "traces" + os.sep + file
   846             os.remove(path)
   847     else:
   848         os.mkdir("traces")
   849     
   850     mod = __import__(test, globals(), locals(), [])
   851     return mod.run(verbose=(Params.g_options.verbose > 0),
   852                    generate=Params.g_options.regression_generate,
   853                    refDirName=REGRESSION_TRACES_DIR_NAME)