X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=QMTest%2FTestSCons.py;h=a1ec227804873a99c2828405a783e1308e31ce7d;hb=704f6e2480ef60718f1aa42c266f04afc9c79580;hp=4a4941d77462c71f747ba9d0f3cead832ca6121d;hpb=fbb05f90998cfec5e132a0d9219d74c76646fd95;p=scons.git diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 4a4941d7..a1ec2278 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -13,13 +13,13 @@ attributes defined in this subclass. """ # __COPYRIGHT__ +from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os -import os.path import re -import string +import shutil import sys import time @@ -30,10 +30,18 @@ except AttributeError: def zip(*lists): result = [] for i in xrange(len(lists[0])): - result.append(tuple(map(lambda l, i=i: l[i], lists))) + result.append(tuple([l[i] for l in lists])) return result __builtin__.zip = zip +try: + x = True +except NameError: + True = not 0 + False = not 1 +else: + del x + from TestCommon import * from TestCommon import __all__ @@ -42,9 +50,9 @@ from TestCommon import __all__ # here provides some independent verification that what we packaged # conforms to what we expect. -default_version = '1.2.0' +default_version = '1.3.0' -copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009' +copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010' # In the checked-in source, the value of SConsVersion in the following # line must remain "__ VERSION __" (without the spaces) so the built @@ -115,7 +123,7 @@ def gccFortranLibs(): stderr = p.stderr for l in stderr.readlines(): - list = string.split(l) + list = l.split() if len(list) > 3 and list[:2] == ['gcc', 'version']: if list[2][:3] in ('4.1','4.2','4.3'): libs = ['gfortranbegin'] @@ -140,7 +148,7 @@ if sys.platform == 'win32': fortran_lib = gccFortranLibs() elif sys.platform == 'cygwin': fortran_lib = gccFortranLibs() -elif string.find(sys.platform, 'irix') != -1: +elif sys.platform.find('irix') != -1: fortran_lib = ['ftn'] else: fortran_lib = gccFortranLibs() @@ -153,7 +161,7 @@ file_expr = r"""File "[^"]*", line \d+, in .+ # re.escape escapes too much. def re_escape(str): for c in ['.', '[', ']', '(', ')', '*', '+', '?']: # Not an exhaustive list. - str = string.replace(str, c, '\\' + c) + str = str.replace(c, '\\' + c) return str @@ -162,12 +170,12 @@ try: sys.version_info except AttributeError: # Pre-1.6 Python has no sys.version_info - version_string = string.split(sys.version)[0] - version_ints = map(int, string.split(version_string, '.')) + version_string = sys.version.split()[0] + version_ints = list(map(int, version_string.split('.'))) sys.version_info = tuple(version_ints + ['final', 0]) def python_version_string(): - return string.split(sys.version)[0] + return sys.version.split()[0] def python_minor_version_string(): return sys.version[:3] @@ -176,11 +184,11 @@ def unsupported_python_version(version=sys.version_info): return version < (1, 5, 2) def deprecated_python_version(version=sys.version_info): - return version < (2, 2, 0) + return version < (2, 4, 0) if deprecated_python_version(): msg = r""" -scons: warning: Support for pre-2.2 Python (%s) is deprecated. +scons: warning: Support for pre-2.4 Python (%s) is deprecated. If this will cause hardship, contact dev@scons.tigris.org. """ @@ -226,35 +234,41 @@ class TestSCons(TestCommon): pass else: os.chdir(script_dir) - if not kw.has_key('program'): + if 'program' not in kw: kw['program'] = os.environ.get('SCONS') if not kw['program']: if os.path.exists('scons'): kw['program'] = 'scons' else: kw['program'] = 'scons.py' - if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'): + elif not os.path.isabs(kw['program']): + kw['program'] = os.path.join(self.orig_cwd, kw['program']) + if 'interpreter' not in kw and not os.environ.get('SCONS_EXEC'): kw['interpreter'] = [python, '-tt'] - if not kw.has_key('match'): + if 'match' not in kw: kw['match'] = match_exact - if not kw.has_key('workdir'): + if 'workdir' not in kw: kw['workdir'] = '' # Term causing test failures due to bogus readline init # control character output on FC8 # TERM can cause test failures due to control chars in prompts etc. os.environ['TERM'] = 'dumb' + + self.ignore_python_version=kw.get('ignore_python_version',1) + if kw.get('ignore_python_version',-1) != -1: + del kw['ignore_python_version'] - if deprecated_python_version(): + if self.ignore_python_version and deprecated_python_version(): sconsflags = os.environ.get('SCONSFLAGS') if sconsflags: sconsflags = [sconsflags] else: sconsflags = [] sconsflags = sconsflags + ['--warn=no-python-version'] - os.environ['SCONSFLAGS'] = string.join(sconsflags) + os.environ['SCONSFLAGS'] = ' '.join(sconsflags) - apply(TestCommon.__init__, [self], kw) + TestCommon.__init__(self, **kw) import SCons.Node.FS if SCons.Node.FS.default_fs is None: @@ -270,7 +284,7 @@ class TestSCons(TestCommon): if not ENV is None: kw['ENV'] = ENV try: - return apply(SCons.Environment.Environment, args, kw) + return SCons.Environment.Environment(*args, **kw) except (SCons.Errors.UserError, SCons.Errors.InternalError): return None @@ -293,7 +307,7 @@ class TestSCons(TestCommon): return None result = env.WhereIs(prog) if norm and os.sep != '/': - result = string.replace(result, os.sep, '/') + result = result.replace(os.sep, '/') return result def detect_tool(self, tool, prog=None, ENV=None): @@ -341,17 +355,47 @@ class TestSCons(TestCommon): build_str + \ term + def run(self, *args, **kw): + """ + Add the --warn=no-python-version option to SCONSFLAGS every + command so test scripts don't have to filter out Python version + deprecation warnings. + Same for --warn=no-visual-c-missing. + """ + save_sconsflags = os.environ.get('SCONSFLAGS') + if save_sconsflags: + sconsflags = [save_sconsflags] + else: + sconsflags = [] + if self.ignore_python_version and deprecated_python_version(): + sconsflags = sconsflags + ['--warn=no-python-version'] + # Provide a way to suppress or provide alternate flags for + # TestSCons purposes by setting TESTSCONS_SCONSFLAGS. + # (The intended use case is to set it to null when running + # timing tests of earlier versions of SCons which don't + # support the --warn=no-visual-c-missing warning.) + sconsflags = sconsflags + [os.environ.get('TESTSCONS_SCONSFLAGS', + '--warn=no-visual-c-missing')] + os.environ['SCONSFLAGS'] = ' '.join(sconsflags) + try: + result = TestCommon.run(self, *args, **kw) + finally: + sconsflags = save_sconsflags + return result + def up_to_date(self, options = None, arguments = None, read_str = "", **kw): s = "" - for arg in string.split(arguments): + for arg in arguments.split(): s = s + "scons: `%s' is up to date.\n" % arg if options: arguments = options + " " + arguments kw['arguments'] = arguments stdout = self.wrap_stdout(read_str = read_str, build_str = s) - kw['stdout'] = re.escape(stdout) + # Append '.*' so that timing output that comes after the + # up-to-date output is okay. + kw['stdout'] = re.escape(stdout) + '.*' kw['match'] = self.match_re_dotall - apply(self.run, [], kw) + self.run(**kw) def not_up_to_date(self, options = None, arguments = None, **kw): """Asserts that none of the targets listed in arguments is @@ -359,16 +403,34 @@ class TestSCons(TestCommon): This function is most useful in conjunction with the -n option. """ s = "" - for arg in string.split(arguments): + for arg in arguments.split(): s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg) if options: arguments = options + " " + arguments s = '('+s+'[^\n]*\n)*' kw['arguments'] = arguments stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE')) - kw['stdout'] = string.replace(stdout, 'ARGUMENTSGOHERE', s) + kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s) kw['match'] = self.match_re_dotall - apply(self.run, [], kw) + self.run(**kw) + + def option_not_yet_implemented(self, option, arguments=None, **kw): + """ + Verifies expected behavior for options that are not yet implemented: + a warning message, and exit status 1. + """ + msg = "Warning: the %s option is not yet implemented\n" % option + kw['stderr'] = msg + if arguments: + # If it's a long option and the argument string begins with '=', + # it's of the form --foo=bar and needs no separating space. + if option[:2] == '--' and arguments[0] == '=': + kw['arguments'] = option + arguments + else: + kw['arguments'] = option + ' ' + arguments + # TODO(1.5) + #return self.run(**kw) + return self.run(**kw) def diff_substr(self, expect, actual, prelen=20, postlen=40): i = 0 @@ -399,9 +461,9 @@ class TestSCons(TestCommon): places, abstracting out the version difference. """ exec 'import traceback; x = traceback.format_stack()[-1]' - x = string.lstrip(x) - x = string.replace(x, '', file) - x = string.replace(x, 'line 1,', 'line %s,' % line) + x = x.lstrip() + x = x.replace('', file) + x = x.replace('line 1,', 'line %s,' % line) return x def normalize_pdf(self, s): @@ -424,12 +486,12 @@ class TestSCons(TestCommon): end_marker = 'endstream\nendobj' encoded = [] - b = string.find(s, begin_marker, 0) + b = s.find(begin_marker, 0) while b != -1: b = b + len(begin_marker) - e = string.find(s, end_marker, b) + e = s.find(end_marker, b) encoded.append((b, e)) - b = string.find(s, begin_marker, e + len(end_marker)) + b = s.find(begin_marker, e + len(end_marker)) x = 0 r = [] @@ -445,7 +507,7 @@ class TestSCons(TestCommon): r.append(d) x = e r.append(s[x:]) - s = string.join(r, '') + s = ''.join(r) return s @@ -453,9 +515,7 @@ class TestSCons(TestCommon): import glob result = [] for p in patterns: - paths = glob.glob(p) - paths.sort() - result.extend(paths) + result.extend(sorted(glob.glob(p))) return result @@ -491,7 +551,7 @@ class TestSCons(TestCommon): ] java_path = self.paths(patterns) + [env['ENV']['PATH']] - env['ENV']['PATH'] = string.join(java_path, os.pathsep) + env['ENV']['PATH'] = os.pathsep.join(java_path) return env['ENV'] def java_where_includes(self,version=None): @@ -523,10 +583,18 @@ class TestSCons(TestCommon): def java_where_java_home(self,version=None): - import os.path - jar=self.java_where_jar(version) - home=os.path.normpath('%s/..'%jar) - return home + if sys.platform[:6] == 'darwin': + if version is None: + home = '/System/Library/Frameworks/JavaVM.framework/Home' + else: + home = '/System/Library/Frameworks/JavaVM.framework/Versions/%s/Home' % version + else: + jar = self.java_where_jar(version) + home = os.path.normpath('%s/..'%jar) + if os.path.isdir(home): + return home + print("Could not determine JAVA_HOME: %s is not a directory" % home) + self.fail_test() def java_where_jar(self, version=None): ENV = self.java_ENV(version) @@ -564,7 +632,7 @@ class TestSCons(TestCommon): stderr=None, status=None) if version: - if string.find(self.stderr(), 'javac %s' % version) == -1: + if self.stderr().find('javac %s' % version) == -1: fmt = "Could not find javac for Java version %s, skipping test(s).\n" self.skip_test(fmt % version) else: @@ -603,9 +671,9 @@ class TestSCons(TestCommon): self.write([dir, 'bin', 'mymoc.py'], """\ import getopt import sys -import string import re -cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', []) +# -w and -z are fake options used in test/QT/QTFLAGS.py +cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:wz', []) output = None impl = 0 opt_string = '' @@ -613,13 +681,14 @@ for opt, arg in cmd_opts: if opt == '-o': output = open(arg, 'wb') elif opt == '-i': impl = 1 else: opt_string = opt_string + ' ' + opt +output.write("/* mymoc.py%s */\\n" % opt_string) for a in args: contents = open(a, 'rb').read() - a = string.replace(a, '\\\\', '\\\\\\\\') + a = a.replace('\\\\', '\\\\\\\\') subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }' if impl: contents = re.sub( r'#include.*', '', contents ) - output.write(string.replace(contents, 'Q_OBJECT', subst)) + output.write(contents.replace('Q_OBJECT', subst)) output.close() sys.exit(0) """) @@ -628,11 +697,11 @@ sys.exit(0) import os.path import re import sys -import string output_arg = 0 impl_arg = 0 impl = None source = None +opt_string = '' for arg in sys.argv[1:]: if output_arg: output = open(arg, 'wb') @@ -644,11 +713,14 @@ for arg in sys.argv[1:]: output_arg = 1 elif arg == "-impl": impl_arg = 1 + elif arg[0:1] == "-": + opt_string = opt_string + ' ' + arg else: if source: sys.exit(1) source = open(arg, 'rb') sourceFile = arg +output.write("/* myuic.py%s */\\n" % opt_string) if impl: output.write( '#include "' + impl + '"\\n' ) includes = re.findall('(.*?)', source.read()) @@ -671,7 +743,7 @@ void my_qt_symbol(const char *arg); #include "../include/my_qobject.h" #include void my_qt_symbol(const char *arg) { - printf( arg ); + fputs( arg, stdout ); } """) @@ -696,8 +768,8 @@ else: self.QT_LIB_DIR = self.workpath(dir, 'lib') def Qt_create_SConstruct(self, place): - if type(place) is type([]): - place = apply(test.workpath, place) + if isinstance(place, list): + place = test.workpath(*place) self.write(place, """\ if ARGUMENTS.get('noqtdir', 0): QTDIR=None else: QTDIR=r'%s' @@ -769,7 +841,7 @@ SConscript( sconscript ) lastEnd = 0 logfile = self.read(self.workpath(logfile)) if (doCheckLog and - string.find( logfile, "scons: warning: The stored build " + logfile.find( "scons: warning: The stored build " "information has an unexpected class." ) >= 0): self.fail_test() sconf_dir = sconf_dir @@ -860,98 +932,40 @@ SConscript( sconscript ) # see also sys.prefix documentation return python_minor_version_string() - def get_platform_python(self): + def get_platform_python_info(self): """ Returns a path to a Python executable suitable for testing on - this platform. - - Mac OS X has no static libpython for SWIG to link against, - so we have to link against Apple's framwork version. However, - testing must use the executable version that corresponds to the - framework we link against, or else we get interpreter errors. - """ - if sys.platform[:6] == 'darwin': - return sys.prefix + '/bin/python' - else: - global python - return python - - def get_quoted_platform_python(self): + this platform and its associated include path, library path, + and library name. """ - Returns a quoted path to a Python executable suitable for testing on - this platform. + python = self.where_is('python') + if not python: + self.skip_test('Can not find installed "python", skipping test.\n') - Mac OS X has no static libpython for SWIG to link against, - so we have to link against Apple's framwork version. However, - testing must use the executable version that corresponds to the - framework we link against, or else we get interpreter errors. - """ - if sys.platform[:6] == 'darwin': - return '"' + self.get_platform_python() + '"' - else: - global _python_ - return _python_ - -# def get_platform_sys_prefix(self): -# """ -# Returns a "sys.prefix" value suitable for linking on this platform. -# -# Mac OS X has a built-in Python but no static libpython, -# so we must link to it using Apple's 'framework' scheme. -# """ -# if sys.platform[:6] == 'darwin': -# fmt = '/System/Library/Frameworks/Python.framework/Versions/%s/' -# return fmt % self.get_python_version() -# else: -# return sys.prefix - - def get_python_frameworks_flags(self): - """ - Returns a FRAMEWORKS value for linking with Python. - - Mac OS X has a built-in Python but no static libpython, - so we must link to it using Apple's 'framework' scheme. - """ - if sys.platform[:6] == 'darwin': - return 'Python' - else: - return '' + self.run(program = python, stdin = """\ +import os, sys +try: + py_ver = 'python%d.%d' % sys.version_info[:2] +except AttributeError: + py_ver = 'python' + sys.version[:3] +print os.path.join(sys.prefix, 'include', py_ver) +print os.path.join(sys.prefix, 'lib', py_ver, 'config') +print py_ver +""") - def get_python_inc(self): - """ - Returns a path to the Python include directory. + return [python] + self.stdout().strip().split('\n') - Mac OS X has a built-in Python but no static libpython, - so we must link to it using Apple's 'framework' scheme. + def start(self, *args, **kw): """ - if sys.platform[:6] == 'darwin': - return sys.prefix + '/Headers' - try: - import distutils.sysconfig - except ImportError: - return os.path.join(sys.prefix, 'include', - 'python' + self.get_python_version()) - else: - return distutils.sysconfig.get_python_inc() + Starts SCons in the test environment. - def get_python_library_path(self): + This method exists to tell Test{Cmd,Common} that we're going to + use standard input without forcing every .start() call in the + individual tests to do so explicitly. """ - Returns the full path of the Python static library (libpython*.a) - """ - if sys.platform[:6] == 'darwin': - # Use the framework version (or try to) since that matches - # the executable and headers we return elsewhere. - python_lib = os.path.join(sys.prefix, 'Python') - if os.path.exists(python_lib): - return python_lib - python_version = self.get_python_version() - python_lib = os.path.join(sys.prefix, 'lib', - 'python%s' % python_version, 'config', - 'libpython%s.a' % python_version) - if os.path.exists(python_lib): - return python_lib - # We can't find it, so maybe it's in the standard path - return '' + if 'stdin' not in kw: + kw['stdin'] = True + return TestCommon.start(self, *args, **kw) def wait_for(self, fname, timeout=10.0, popen=None): """ @@ -981,6 +995,297 @@ SConscript( sconscript ) else: alt_cpp_suffix = '.C' return alt_cpp_suffix + + +class Stat: + def __init__(self, name, units, expression, convert=None): + if convert is None: + convert = lambda x: x + self.name = name + self.units = units + self.expression = re.compile(expression) + self.convert = convert + +StatList = [ + Stat('memory-initial', 'kbytes', + r'Memory before reading SConscript files:\s+(\d+)', + convert=lambda s: int(s) / 1024), + Stat('memory-prebuild', 'kbytes', + r'Memory before building targets:\s+(\d+)', + convert=lambda s: int(s) / 1024), + Stat('memory-final', 'kbytes', + r'Memory after building targets:\s+(\d+)', + convert=lambda s: int(s) / 1024), + + Stat('time-sconscript', 'seconds', + r'Total SConscript file execution time:\s+([\d.]+) seconds'), + Stat('time-scons', 'seconds', + r'Total SCons execution time:\s+([\d.]+) seconds'), + Stat('time-commands', 'seconds', + r'Total command execution time:\s+([\d.]+) seconds'), + Stat('time-total', 'seconds', + r'Total build time:\s+([\d.]+) seconds'), +] + + +class TimeSCons(TestSCons): + """Class for timing SCons.""" + def __init__(self, *args, **kw): + """ + In addition to normal TestSCons.TestSCons intialization, + this enables verbose mode (which causes the command lines to + be displayed in the output) and copies the contents of the + directory containing the executing script to the temporary + working directory. + """ + self.variables = kw.get('variables') + if self.variables is not None: + for variable, value in self.variables.items(): + value = os.environ.get(variable, value) + try: + value = int(value) + except ValueError: + try: + value = float(value) + except ValueError: + pass + self.variables[variable] = value + del kw['variables'] + + self.calibrate = os.environ.get('TIMESCONS_CALIBRATE', '0') != '0' + + if 'verbose' not in kw and not self.calibrate: + kw['verbose'] = True + + # TODO(1.5) + #TestSCons.__init__(self, *args, **kw) + TestSCons.__init__(self, *args, **kw) + + # TODO(sgk): better way to get the script dir than sys.argv[0] + test_dir = os.path.dirname(sys.argv[0]) + test_name = os.path.basename(test_dir) + + if not os.path.isabs(test_dir): + test_dir = os.path.join(self.orig_cwd, test_dir) + self.copy_timing_configuration(test_dir, self.workpath()) + + def main(self, *args, **kw): + """ + The main entry point for standard execution of timings. + + This method run SCons three times: + + Once with the --help option, to have it exit after just reading + the configuration. + + Once as a full build of all targets. + + Once again as a (presumably) null or up-to-date build of + all targets. + + The elapsed time to execute each build is printed after + it has finished. + """ + if 'options' not in kw and self.variables: + options = [] + for variable, value in self.variables.items(): + options.append('%s=%s' % (variable, value)) + kw['options'] = ' '.join(options) + if self.calibrate: + # TODO(1.5) + #self.calibration(*args, **kw) + self.calibration(*args, **kw) + else: + self.uptime() + # TODO(1.5) + #self.startup(*args, **kw) + #self.full(*args, **kw) + #self.null(*args, **kw) + self.startup(*args, **kw) + self.full(*args, **kw) + self.null(*args, **kw) + + def trace(self, graph, name, value, units, sort=None): + fmt = "TRACE: graph=%s name=%s value=%s units=%s" + line = fmt % (graph, name, value, units) + if sort is not None: + line = line + (' sort=%s' % sort) + line = line + '\n' + sys.stdout.write(line) + sys.stdout.flush() + + def report_traces(self, trace, stats): + self.trace('TimeSCons-elapsed', + trace, + self.elapsed_time(), + "seconds", + sort=0) + for name, args in stats.items(): + # TODO(1.5) + #self.trace(name, trace, *args) + self.trace(name, trace, **args) + + def uptime(self): + try: + fp = open('/proc/loadavg') + except EnvironmentError: + pass + else: + avg1, avg5, avg15 = fp.readline().split(" ")[:3] + fp.close() + self.trace('load-average', 'average1', avg1, 'processes') + self.trace('load-average', 'average5', avg5, 'processes') + self.trace('load-average', 'average15', avg15, 'processes') + + def collect_stats(self, input): + result = {} + for stat in StatList: + m = stat.expression.search(input) + if m: + value = stat.convert(m.group(1)) + # The dict keys match the keyword= arguments + # of the trace() method above so they can be + # applied directly to that call. + result[stat.name] = {'value':value, 'units':stat.units} + return result + + def startup(self, *args, **kw): + """ + Runs scons with the --help option. + + This serves as a way to isolate just the amount of startup time + spent reading up the configuration, since --help exits before any + "real work" is done. + """ + kw['options'] = kw.get('options', '') + ' --help' + # Ignore the exit status. If the --help run dies, we just + # won't report any statistics for it, but we can still execute + # the full and null builds. + kw['status'] = None + # TODO(1.5) + #self.run(*args, **kw) + self.run(*args, **kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + # Delete the time-commands, since no commands are ever + # executed on the help run and it is (or should be) always 0.0. + del stats['time-commands'] + self.report_traces('startup', stats) + + def full(self, *args, **kw): + """ + Runs a full build of SCons. + """ + # TODO(1.5) + #self.run(*args, **kw) + self.run(*args, **kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + self.report_traces('full', stats) + # TODO(1.5) + #self.trace('full-memory', 'initial', **stats['memory-initial']) + #self.trace('full-memory', 'prebuild', **stats['memory-prebuild']) + #self.trace('full-memory', 'final', **stats['memory-final']) + self.trace('full-memory', 'initial', **stats['memory-initial']) + self.trace('full-memory', 'prebuild', **stats['memory-prebuild']) + self.trace('full-memory', 'final', **stats['memory-final']) + + def calibration(self, *args, **kw): + """ + Runs a full build of SCons, but only reports calibration + information (the variable(s) that were set for this configuration, + and the elapsed time to run. + """ + # TODO(1.5) + #self.run(*args, **kw) + self.run(*args, **kw) + if self.variables: + for variable, value in self.variables.items(): + sys.stdout.write('VARIABLE: %s=%s\n' % (variable, value)) + sys.stdout.write('ELAPSED: %s\n' % self.elapsed_time()) + + def null(self, *args, **kw): + """ + Runs an up-to-date null build of SCons. + """ + # TODO(sgk): allow the caller to specify the target (argument) + # that must be up-to-date. + # TODO(1.5) + #self.up_to_date(arguments='.', **kw) + kw = kw.copy() + kw['arguments'] = '.' + self.up_to_date(**kw) + sys.stdout.write(self.stdout()) + stats = self.collect_stats(self.stdout()) + # time-commands should always be 0.0 on a null build, because + # no commands should be executed. Remove it from the stats + # so we don't trace it, but only if it *is* 0 so that we'll + # get some indication if a supposedly-null build actually does + # build something. + if float(stats['time-commands']['value']) == 0.0: + del stats['time-commands'] + self.report_traces('null', stats) + # TODO(1.5) + #self.trace('null-memory', 'initial', **stats['memory-initial']) + #self.trace('null-memory', 'prebuild', **stats['memory-prebuild']) + #self.trace('null-memory', 'final', **stats['memory-final']) + self.trace('null-memory', 'initial', **stats['memory-initial']) + self.trace('null-memory', 'prebuild', **stats['memory-prebuild']) + self.trace('null-memory', 'final', **stats['memory-final']) + + def elapsed_time(self): + """ + Returns the elapsed time of the most recent command execution. + """ + return self.endTime - self.startTime + + def run(self, *args, **kw): + """ + Runs a single build command, capturing output in the specified file. + + Because this class is about timing SCons, we record the start + and end times of the elapsed execution, and also add the + --debug=memory and --debug=time options to have SCons report + its own memory and timing statistics. + """ + kw['options'] = kw.get('options', '') + ' --debug=memory --debug=time' + self.startTime = time.time() + try: + # TODO(1.5) + #result = TestSCons.run(self, *args, **kw) + result = TestSCons.run(self, *args, **kw) + finally: + self.endTime = time.time() + return result + + def copy_timing_configuration(self, source_dir, dest_dir): + """ + Copies the timing configuration from the specified source_dir (the + directory in which the controlling script lives) to the specified + dest_dir (a temporary working directory). + + This ignores all files and directories that begin with the string + 'TimeSCons-', and all '.svn' subdirectories. + """ + for root, dirs, files in os.walk(source_dir): + if '.svn' in dirs: + dirs.remove('.svn') + # TODO(1.5) + #dirs = [ d for d in dirs if not d.startswith('TimeSCons-') ] + #files = [ f for f in files if not f.startswith('TimeSCons-') ] + not_timescons_entries = lambda s: not s.startswith('TimeSCons-') + dirs = list(filter(not_timescons_entries, dirs)) + files = list(filter(not_timescons_entries, files)) + for dirname in dirs: + source = os.path.join(root, dirname) + destination = source.replace(source_dir, dest_dir) + os.mkdir(destination) + if sys.platform != 'win32': + shutil.copystat(source, destination) + for filename in files: + source = os.path.join(root, filename) + destination = source.replace(source_dir, dest_dir) + shutil.copy2(source, destination) # In some environments, $AR will generate a warning message to stderr @@ -993,3 +1298,9 @@ SConscript( sconscript ) # test/AR.py for sample usage). noisy_ar=r'(ar: creating( archive)? \S+\n?)*' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: