Commits

Anonymous committed d27a548

Lots of Configure() enhancements. (Christoph Wiedemann)

Comments (0)

Files changed (16)

 regardless of whether a target
 file was rebuilt or retrieved from the cache.
 
+.TP
+.RI --config= mode
+This specifies how the
+.B Configure
+call should use or generate the
+results of configuration tests.
+The option should be specified from
+among the following choices:
+
+.TP
+--config=auto
+scons will use its normal dependency mechanisms
+to decide if a test must be rebuilt or not.
+This saves time by not running the same configuration tests
+every time you invoke scons,
+but will overlook changes in system header files
+or external commands (such as compilers)
+if you don't specify those dependecies explicitly.
+This is the default behavior.
+
+.TP
+--config=force
+If this option is specified,
+all configuration tests will be re-run
+regardless of whether the
+cached results are out of date.
+This can be used to explicitly
+force the configuration tests to be updated
+in response to an otherwise unconfigured change
+in a system header file or compiler.
+
+.TP
+--config=cache
+If this option is specified,
+no configuration tests will be rerun
+and all results will be taken from cache.
+Note that scons will still consider it an error
+if --config=cache is specified
+and a necessary test does not
+yet have any results in the cache.
+
 .TP 
 .RI "-C" " directory" ",  --directory=" directory
 Change to the specified 
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ])
-.TP
-.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ])
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.TP
+.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ])
 Creates a Configure object for integrated
 functionality similar to GNU autoconf.
 See the section "Configure Contexts,"
 .B scons
 does not maintain an explicit cache of the tested values,
 but uses its normal dependency tracking to keep the checked values
-up to date. 
+up to date. However, users may override this behaviour with the 
+.B --config
+command line option.
+
 The following methods can be used to perform checks:
 
 .TP
-.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ])
-.TP
-.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ])
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.TP
+.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ])
 This creates a configure context, which can be used to perform checks.
 .I env
 specifies the environment for building the tests.
 .B BuildDir
 method,
 you may want to specify a subdirectory under your build directory.
+.I config_h
+specifies a C header file where the results of tests 
+will be written, e.g. #define HAVE_STDIO_H, #define HAVE_LIBM, etc. 
+The default is to not write a
+.B config.h
+file.
+You can specify the same
+.B config.h
+file in multiple calls to Configure,
+in which case
+.B scons
+will concatenate all results in the specified file.
+Note that SCons
+uses its normal dependency checking
+to decide if it's necessary to rebuild
+the specified
+.I config_h
+file.
+This means that the file is not necessarily re-built each
+time scons is run,
+but is only rebuilt if its contents will have changed
+and some target that depends on the
+.I config_h
+file is being built.
 
 .EE
 A created
     so these can be set individually, instead of being hard-wired
     relative to $QTDIR.
 
+  - The %TEMP% and %TMP% external environment variables are now propagated
+    automatically to the command execution environment on Windows systems.
+
+  - A new --config= command-line option allows explicit control of
+    of when the Configure() tests are run:  --config=force forces all
+    checks to be run, --config=cache uses all previously cached values,
+    --config=auto (the default) runs tests only when dependency analysis
+    determines it's necessary.
+
+  - The Configure() subsystem can now write a config.h file with values
+    like HAVE_STDIO_H, HAVE_LIBM, etc.
+
+  - The Configure() subsystem now executes its checks silently when the
+    -Q option is specified.
+
+  - The Configure() subsystem now reports if a test result is being
+    taken from cache, and prints the standard output and error output
+    of tests even when cached.
+
+  - Configure() test results are now reported as "yes" or "no" instead of
+    "ok" or "failed."
+
+  - Fixed traceback printing when calling the env.Configure() method
+    instead of the Configure() global function.
+
+  - The Configure() subsystem now caches build failures in a .sconsign
+    file in the subdirectory, not a .cache file.  This may cause
+    tests to be re-executed the first time after you install 0.97.
+
+  - Additional significant internal cleanups in the Configure() subsystem
+    and its tests.
+
 
 
 RELEASE 0.96.1 - XXX
     - The deprecated "validater" keyword to the Options.Add() method
       has been removed.
 
+    - The %TEMP% and %TMP% external environment variables are now
+      propagated automatically to the command execution environment on
+      Windows systems.
+
+    - The Configure() subsystem now reports tests results as "yes" and
+      "no" instead of "ok" and "failed."  This might interfere with any
+      scripts that automatically parse the Configure() output from SCons.
+
+    - The Configure() subsystem now stores its cached results in a
+      different file.  This may cause configuration tests to be re-run
+      the first time after you install 0.97.
+
   Please note the following important changes since release 0.95:
 
     - All Builder calls (both built-in like Program(), Library(),

src/engine/SCons/Conftest.py

 #                       The file must not exist or be empty when starting.
 #                       Empty or None to skip this (some tests will not work!).
 #
+# context.config_h      (may be missing). If present, must be a string, which
+#                       will be filled with the contents of a config_h file.
+#
 # context.vardict       Dictionary holding variables used for the tests and
 #                       stores results from the tests, used for the build
 #                       commands.
 #                       be a number and "SYSTEMNAME" a string.
 #
 
+import re
 import string
 from types import IntType
 
 #
+# PUBLIC VARIABLES
+#
+
+LogInputFiles = 1    # Set that to log the input files in case of a failed test
+LogErrorMessages = 1 # Set that to log Conftest-generated error messages
+
+#
 # PUBLIC FUNCTIONS
 #
 
 
     if not text:
         text = """
-                int main() {
-                        return 0;
-                    }\n\n"""
+int main() {
+    return 0;
+}
+"""
 
     context.Display("Checking if building a %s file works... " % lang)
     ret = context.BuildProg(text, suffix)
         includetext = ''
     if not header:
         header = """
-                #ifdef __cplusplus
-                extern "C"
-                #endif
-                char %s();""" % function_name
+#ifdef __cplusplus
+extern "C"
+#endif
+char %s();""" % function_name
 
     lang, suffix, msg = _lang2suffix(language)
     if msg:
         return msg
 
     text = """
-            %(include)s
-            #include <assert.h>
-            %(hdr)s
+%(include)s
+#include <assert.h>
+%(hdr)s
 
-            int main() {
-                    #if defined (__stub_%(name)s) || defined (__stub___%(name)s)
-                    fail fail fail
-                    #else
-                    %(name)s();
-                    #endif
+int main() {
+#if defined (__stub_%(name)s) || defined (__stub___%(name)s)
+  fail fail fail
+#else
+  %(name)s();
+#endif
 
-                    return 0;
-                }\n\n""" % { 'name': function_name,
-                             'include': includetext,
-                             'hdr': header }
+  return 0;
+}
+""" % { 'name': function_name,
+        'include': includetext,
+        'hdr': header }
 
     context.Display("Checking for %s function %s()... " % (lang, function_name))
     ret = context.BuildProg(text, suffix)
     # - Using "sizeof(TYPE)" is valid when TYPE is actually a variable.
     # - Using the previous two together works reliably.
     text = """
-            %(include)s
-            %(header)s
+%(include)s
+%(header)s
 
-            int main() {
-                    if ((%(name)s *) 0)
-                            return 0;
-                    if (sizeof (%(name)s))
-                            return 0;
-            }\n\n""" % { 'include': includetext,
-                         'header': header,
-                         'name': type_name }
+int main() {
+  if ((%(name)s *) 0)
+    return 0;
+  if (sizeof (%(name)s))
+    return 0;
+}
+""" % { 'include': includetext,
+        'header': header,
+        'name': type_name }
 
     context.Display("Checking for %s type %s... " % (lang, type_name))
     ret = context.BuildProg(text, suffix)
         header = ""
 
     text = """
-            %s
-            %s """ % (includetext, header)
+%s
+%s""" % (includetext, header)
 
     # Add a function declaration if needed.
     if func_name and func_name != "main" and not header:
         text = text + """
-                #ifdef __cplusplus
-                extern "C"
-                #endif
-                char %s();""" % func_name
+#ifdef __cplusplus
+extern "C"
+#endif
+char %s();
+""" % func_name
 
     # The actual test code.
     if not call:
         call = "%s();" % func_name
     text = text + """
-            int
-            main() {
-            %s
-            return 0;
-            }
-            \n\n""" % call
+int
+main() {
+  %s
+return 0;
+}
+""" % call
 
     i = string.find(call, "\n")
     if i > 0:
         if oldLIBS != -1 and (ret or not autoadd):
             context.SetLIBS(oldLIBS)
             
-        if ret == "":
+        if not ret:
             return ret
 
     return ret
 def _Have(context, key, have):
     """
     Store result of a test in context.havedict and context.headerfilename.
-    "key" is a "HAVE_abc" name.  It is turned into all CAPITALS and ":./" are
-    replaced by an underscore.
+    "key" is a "HAVE_abc" name.  It is turned into all CAPITALS and non-
+    alphanumerics are replaced by an underscore.
     The value of "have" can be:
     1      - Feature is defined, add "#define key".
     0      - Feature is not defined, add "/* #undef key */".
              when desired and escape special characters!
     """
     key_up = string.upper(key)
-    key_up = string.replace(key_up, ':', '_')
-    key_up = string.replace(key_up, '.', '_')
-    key_up = string.replace(key_up, '/', '_')
-    key_up = string.replace(key_up, ' ', '_')
+    key_up = re.sub('[^A-Z0-9_]', '_', key_up)
     context.havedict[key_up] = have
+    if have == 1:
+        line = "#define %s\n" % key_up
+    elif have == 0:
+        line = "/* #undef %s */\n" % key_up
+    elif type(have) == IntType:
+        line = "#define %s %d\n" % (key_up, have)
+    else:
+        line = "#define %s %s\n" % (key_up,
+                                    re.sub('[^A-Za-z0-9_]', '_', str(have)))
+    
     if context.headerfilename:
         f = open(context.headerfilename, "a")
-        if have == 1:
-            f.write("#define %s\n" % key_up)
-        elif have == 0:
-            f.write("/* #undef %s */\n" % key_up)
-        elif type(have) == IntType:
-            f.write("#define %s %d\n" % (key_up, have))
-        else:
-            f.write("#define %s %s\n" % (key_up, str(have)))
+        f.write(line)
         f.close()
+    elif hasattr(context,'config_h'):
+        context.config_h = context.config_h + line
 
 
 def _LogFailed(context, text, msg):
     Write to the log about a failed program.
     Add line numbers, so that error messages can be understood.
     """
-    context.Log("Failed program was:\n")
-    lines = string.split(text, '\n')
-    if len(lines) and lines[-1] == '':
-        lines = lines[:-1]              # remove trailing empty line
-    n = 1
-    for line in lines:
-        context.Log("%d: %s\n" % (n, line))
-        n = n + 1
-    context.Log("Error message: %s\n" % msg)
+    if LogInputFiles:
+        context.Log("Failed program was:\n")
+        lines = string.split(text, '\n')
+        if len(lines) and lines[-1] == '':
+            lines = lines[:-1]              # remove trailing empty line
+        n = 1
+        for line in lines:
+            context.Log("%d: %s\n" % (n, line))
+            n = n + 1
+    if LogErrorMessages:
+        context.Log("Error message: %s\n" % msg)
 
 
 def _lang2suffix(lang):

src/engine/SCons/Environment.py

         if args:
             nargs = nargs + self.subst_list(args)[0]
         nkw = self.subst_kw(kw)
+        nkw['called_from_env_method'] = 1
         try:
             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
         except KeyError:

src/engine/SCons/Errors.py

         self.status = status
         apply(Exception.__init__, (self,) + args)
 
-class ConfigureDryRunError(UserError):
-    """Raised when a file needs to be updated during a Configure process,
-    but the user requested a dry-run"""
-    def __init__(self,file):
-        UserError.__init__(self,"Cannot update configure test (%s) within a dry-run." % str(file))

src/engine/SCons/ErrorsTests.py

         except SCons.Errors.ExplicitExit, e:
             assert e.node == "node"
 
-    def test_ConfigureDryRunError(self):
-        """Test the ConfigureDryRunError."""
-        try:
-            raise SCons.Errors.ConfigureDryRunError, "FileName"
-        except SCons.Errors.UserError, e:
-            assert e.args == ("Cannot update configure test (FileName) within a dry-run.",)
-
-
 if __name__ == "__main__":
     suite = unittest.makeSuite(ErrorsTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():

src/engine/SCons/Platform/win32.py

     # default.  We're doing this for SYSTEMROOT, though, because it's
     # needed for anything that uses sockets, and seldom changes.  Weigh
     # the impact carefully before adding other variables to this list.
-    import_env = [ 'SYSTEMROOT' ]
+    import_env = [ 'SYSTEMROOT', 'TEMP', 'TMP' ]
     for var in import_env:
         v = os.environ.get(var)
         if v:

src/engine/SCons/SConf.py

 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import cPickle
 import os
+import re
 import string
+import StringIO
 import sys
 import traceback
 import types
 import SCons.Warnings
 import SCons.Conftest
 
-# First i thought of using a different filesystem as the default_fs,
-# but it showed up that there are too many side effects in doing that.
-SConfFS=SCons.Node.FS.default_fs
+# Turn off the Conftest error logging
+SCons.Conftest.LogInputFiles = 0
+SCons.Conftest.LogErrorMessages = 0
 
 # to be set, if we are in dry-run mode
 dryrun = 0
 
-_ac_build_counter = 0
-_ac_config_counter = 0
-_activeSConfObjects = {}
+AUTO=0  # use SCons dependency scanning for up-to-date checks
+FORCE=1 # force all tests to be rebuilt
+CACHE=2 # force all tests to be taken from cache (raise an error, if necessary)
+cache_mode = AUTO
 
+def SetCacheMode(mode):
+    """Set the Configure cache mode. mode must be one of "auto", "force",
+    or "cache"."""
+    global cache_mode
+    if mode == "auto":
+        cache_mode = AUTO
+    elif mode == "force":
+        cache_mode = FORCE
+    elif mode == "cache":
+        cache_mode = CACHE
+    else:
+        raise ValueError, "SCons.SConf.SetCacheMode: Unknown mode " + mode
+
+progress_display = SCons.Util.display # will be overwritten by SCons.Script
+def SetProgressDisplay(display):
+    """Set the progress display to use (called from SCons.Script)"""
+    global progress_display
+    progress_display = display
+
+SConfFS=SCons.Node.FS.default_fs
+
+_ac_build_counter = 0 # incremented, whenever TryBuild is called
+_ac_config_logs = {}  # all config.log files created in this build
+_ac_config_hs   = {}  # all config.h files created in this build
+sconf_global = None   # current sconf object
+
+def _createConfigH(target, source, env):
+    t = open(str(target[0]), "w")
+    defname = re.sub('[^A-Za-z0-9_]', '_', string.upper(str(target[0])))
+    t.write("""#ifndef %(DEFNAME)s_SEEN
+#define %(DEFNAME)s_SEEN
+
+""" % {'DEFNAME' : defname})
+    t.write(source[0].get_contents())
+    t.write("""
+#endif /* %(DEFNAME)s_SEEN */
+""" % {'DEFNAME' : defname})
+    t.close()
+
+def _stringConfigH(target, source, env):
+    return "scons: Configure: creating " + str(target[0])
+
+def CreateConfigHBuilder(env):
+    """Called just before the building targets phase begins."""
+    if len(_ac_config_hs) == 0:
+        return
+    action = SCons.Action.Action(_createConfigH,
+                                 _stringConfigH)
+    sconfigHBld = SCons.Builder.Builder(action=action)
+    env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} )
+    for k in _ac_config_hs.keys():
+        env.SConfigHBuilder(k, env.Value(_ac_config_hs[k]))
+    
 class SConfWarning(SCons.Warnings.Warning):
     pass
-SCons.Warnings.enableWarningClass( SConfWarning )
+SCons.Warnings.enableWarningClass(SConfWarning)
 
-# action to create the source
+# some error definitions
+class SConfError(SCons.Errors.UserError):
+    def __init__(self,msg):
+        SCons.Errors.UserError.__init__(self,msg)
+
+class ConfigureDryRunError(SConfError):
+    """Raised when a file or directory needs to be updated during a Configure
+    process, but the user requested a dry-run"""
+    def __init__(self,target):
+        if not isinstance(target, SCons.Node.FS.File):
+            msg = 'Cannot create configure directory "%s" within a dry-run.' % str(target)
+        else:
+            msg = 'Cannot update configure test "%s" within a dry-run.' % str(target)
+        SConfError.__init__(self,msg)
+
+class ConfigureCacheError(SConfError):
+    """Raised when a use explicitely requested the cache feature, but the test
+    is run the first time."""
+    def __init__(self,target):
+        SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target))
+
+# define actions for building text files
 def _createSource( target, source, env ):
     fd = open(str(target[0]), "w")
-    fd.write(env['SCONF_TEXT'])
+    fd.write(source[0].get_contents())
     fd.close()
-
 def _stringSource( target, source, env ):
     import string
-    return (str(target[0]) + ' <- \n  |' +
-            string.replace( env['SCONF_TEXT'], "\n", "\n  |" ) )
+    return (str(target[0]) + ' <-\n  |' +
+            string.replace( source[0].get_contents(),
+                            '\n', "\n  |" ) )
 
+# python 2.2 introduces types.BooleanType
 BooleanTypes = [types.IntType]
 if hasattr(types, 'BooleanType'): BooleanTypes.append(types.BooleanType)
 
+class SConfBuildInfo(SCons.Node.FS.BuildInfo):
+    """
+    Special build info for targets of configure tests. Additional members
+    are result (did the builder succeed last time?) and string, which
+    contains messages of the original build phase.
+    """
+    result = None # -> 0/None -> no error, != 0 error
+    string = None # the stdout / stderr output when building the target
+    
+    def __init__(self, result, string, sig):
+        self.result = result
+        self.string = string
+        self.bsig = sig
+
+
+class Streamer:
+    """
+    'Sniffer' for a file-like writable object. Similar to the unix tool tee.
+    """
+    def __init__(self, orig):
+        self.orig = orig
+        self.s = StringIO.StringIO()
+
+    def write(self, str):
+        if self.orig:
+            self.orig.write(str)
+        self.s.write(str)
+
+    def writelines(self, lines):
+        for l in lines:
+            self.write(l + '\n')
+
+    def getvalue(self):
+        """
+        Return everything written to orig since the Streamer was created.
+        """
+        return self.s.getvalue()
+        
+
+class SConfBuildTask(SCons.Taskmaster.Task):
+    """
+    This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
+    correctly and knows about the current cache_mode.
+    """
+    def display(self, message):
+        if sconf_global.logstream:
+            sconf_global.logstream.write("scons: Configure: " + message + "\n")
+
+    def display_cached_string(self, bi):
+        """
+        Logs the original builder messages, given the SConfBuildInfo instance
+        bi.
+        """
+        if not isinstance(bi, SConfBuildInfo):
+            SCons.Warnings.warn(SConfWarning,
+              "The stored build information has an unexpected class.")
+        else:
+            self.display("The original builder output was:\n" +
+                         string.replace("  |" + str(bi.string),
+                                        "\n", "\n  |"))
+
+    def failed(self):
+        # check, if the reason was a ConfigureDryRunError or a
+        # ConfigureCacheError and if yes, reraise the exception
+        exc_type = self.exc_info()[0]
+        if issubclass(exc_type, SConfError):
+            raise
+        elif issubclass(exc_type, SCons.Errors.BuildError):
+            # we ignore Build Errors (occurs, when a test doesn't pass)
+            pass
+        else:
+            self.display('Caught exception while building "%s":\n' %
+                         self.targets[0])
+            try:
+                excepthook = sys.excepthook
+            except AttributeError:
+                # Earlier versions of Python don't have sys.excepthook...
+                def excepthook(type, value, tb):
+                    import traceback
+                    traceback.print_tb(tb)
+                    print type, value
+            apply(excepthook, self.exc_info())
+        return SCons.Taskmaster.Task.failed(self)
+
+    def collect_node_states(self):
+        # returns (is_up_to_date, cached_error, cachable)
+        # where is_up_to_date is 1, if the node(s) are up_to_date
+        #       cached_error  is 1, if the node(s) are up_to_date, but the
+        #                           build will fail
+        #       cachable      is 0, if some nodes are not in our cache
+        is_up_to_date = 1
+        cached_error = 0
+        cachable = 1
+        for t in self.targets:
+            bi = t.get_stored_info()
+            c_bi = isinstance(bi, SConfBuildInfo)
+            if c_bi:
+                if cache_mode == CACHE:
+                    t.state = SCons.Node.up_to_date
+                else:
+                    bsig = t.calc_signature(sconf_global.calc)
+                    is_up_to_date = (is_up_to_date and
+                                     bsig == bi.bsig)
+                cached_error = cached_error or bi.result
+            else:
+                # the node hasn't been built in a SConf context or doesn't
+                # exist
+                cachable = 0
+                is_up_to_date = 0
+        return (is_up_to_date, cached_error, cachable)
+
+    def execute(self):
+        sconf = sconf_global
+
+        is_up_to_date, cached_error, cachable = self.collect_node_states()
+
+        if cache_mode == CACHE and not cachable:
+            raise ConfigureCacheError(self.targets[0])
+        elif cache_mode == FORCE:
+            is_up_to_date = 0
+
+        if cached_error and is_up_to_date:
+            self.display("Building \"%s\" failed in a previous run and all "
+                         "its sources are up to date." % str(self.targets[0]))
+            self.display_cached_string(self.targets[0].get_stored_info())
+            raise SCons.Errors.BuildError # will be 'caught' in self.failed
+        elif is_up_to_date:            
+            self.display("\"%s\" is up to date." % str(self.targets[0]))
+            self.display_cached_string(self.targets[0].get_stored_info())
+        elif dryrun:
+            raise ConfigureDryRunError(self.targets[0])
+        else:
+            # note stdout and stderr are the same here
+            s = sys.stdout = sys.stderr = Streamer(sys.stdout)
+            try:
+                env = self.targets[0].get_build_env()
+                env['PSTDOUT'] = env['PSTDERR'] = s
+                try:
+                    sconf.cached = 0
+                    self.targets[0].build()
+                finally:
+                    sys.stdout = sys.stderr = env['PSTDOUT'] = \
+                                 env['PSTDERR'] = sconf.logstream
+            except KeyboardInterrupt:
+                raise
+            except SystemExit:
+                exc_value = sys.exc_info()[1]
+                raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
+            except:
+                for t in self.targets:
+                    sig = t.calc_signature(sconf.calc)
+                    string = s.getvalue()
+                    t.dir.sconsign().set_entry(t.name,
+                                               SConfBuildInfo(1,string,sig))
+                raise
+            else:
+                for t in self.targets:
+                    sig = t.calc_signature(sconf.calc)
+                    string = s.getvalue()
+                    t.dir.sconsign().set_entry(t.name,
+                                               SConfBuildInfo(0,string,sig))
+
 class SConf:
     """This is simply a class to represent a configure context. After
     creating a SConf object, you can call any tests. After finished with your
     """
 
     def __init__(self, env, custom_tests = {}, conf_dir='#/.sconf_temp',
-                 log_file='#/config.log'): 
+                 log_file='#/config.log', config_h = None,
+                 called_from_env_method = 0): 
         """Constructor. Pass additional tests in the custom_tests-dictinary,
         e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest
         defines a custom test.
         global SConfFS
         if not SConfFS:
             SConfFS = SCons.Node.FS.FS(SCons.Node.FS.default_fs.pathTop)
-        if len(_activeSConfObjects.keys()) > 0:
+        if not sconf_global is None:
             raise (SCons.Errors.UserError,
                    "Only one SConf object may be active at one time")
         self.env = env
             self.logfile = None
         self.logstream = None
         self.lastTarget = None
+        self.called_from_env_method = called_from_env_method
+        self.cached = 0 # will be set, if all test results are cached
 
         # add default tests
         default_tests = {
         self.AddTests(custom_tests)
         self.confdir = SConfFS.Dir(conf_dir)
         self.calc = None
-        self.cache = {}
+        if not config_h is None:
+            config_h = SConfFS.File(config_h)
+        self.config_h = config_h
         self._startup()
 
     def Finish(self):
         """Call this method after finished with your tests:
         env = sconf.Finish()"""
-        global _lastSConfObj
-        _lastSConfObj = None
         self._shutdown()
         return self.env
 
-    def _setCache(self, nodes, already_done = []):
-        # Set up actions used for caching errors
-        # Caching positive tests should not be necessary, cause
-        # the build system knows, if test objects/programs/outputs
-        # are up to date.
-        for n in nodes:
-            # The 'n in already_done' expression is not really efficient.
-            # We may do something more sophisticated in the future :-),
-            # but there should not be that many dependencies in configure
-            # tests
-            if (n.has_builder() and
-                not n in already_done):
-                n.add_pre_action(SCons.Action.Action(self._preCache,
-                                                     self._stringCache))
-                n.add_post_action(SCons.Action.Action(self._postCache,
-                                                      self._stringCache))
-                already_done.append( n )
-            self._setCache(n.children())
-
-            # Calling children() has set up the implicit cache (and
-            # other state), but we're not really building things yet,
-            # so generated files won't have been generated.  Clear the
-            # state so we will, in fact, build everything that's necessary
-            # when we do the build.
-            #
-            # XXX - it would be good to find a better way to do this,
-            # maybe by doing something with the actions in the actual
-            # Taskmaster...?
-            n.clear()
-
     def BuildNodes(self, nodes):
         """
         Tries to build the given nodes immediately. Returns 1 on success,
         0 on error.
         """
-
-        global SCons
-        import SCons.Script    # really ugly, but we need BuildTask :-(
-        # Is it better to provide a seperate Task for SConf builds ?
-        class SConfBuildTask(SCons.Script.BuildTask):
-            """Errors in SConf builds are not fatal, so we override
-            the do_failed method"""
-            def do_failed(self, status=2):
-                pass
-
-        class SConfDryRunTask(SConfBuildTask):
-            """Raise ConfiugreDryRunErrors whenever a target is to
-            be built. Pass these Errors to the main script."""
-            def execute(self):
-                target = self.targets[0]
-                if (target.get_state() != SCons.Node.up_to_date and
-                    target.has_builder() and
-                    not hasattr(target.builder, 'status')):
-
-                    raise SCons.Errors.ConfigureDryRunError(target)
-                
-            def failed(self):
-                exc_type, exc_value = self.exc_info()[:2]
-                if exc_type == SCons.Errors.ConfigureDryRunError:
-                    raise exc_type, exc_value
-                # Should be SConfBuildTask.failed(), really,
-                # but that causes name errors in Python 1.5.2.
-                SCons.Script.BuildTask.failed(self)
-
         if self.logstream != None:
             # override stdout / stderr to write in log file
             oldStdout = sys.stdout
         old_os_dir = os.getcwd()
         SConfFS.chdir(SConfFS.Top, change_os_dir=1)
 
-        self._setCache( nodes ) 
         ret = 1
 
         try:
             # ToDo: use user options for calc
             self.calc = SCons.Sig.Calculator(max_drift=0)
-            if dryrun:
-                buildTask = SConfDryRunTask
-            else:
-                buildTask = SConfBuildTask
-            tm = SCons.Taskmaster.Taskmaster( nodes, buildTask )
+            tm = SCons.Taskmaster.Taskmaster(nodes, SConfBuildTask)
             # we don't want to build tests in parallel
             jobs = SCons.Job.Jobs(1, tm )
-            try:
-                jobs.run()
-            except SCons.Errors.BuildError, e:
-                sys.stderr.write("scons: *** [%s] %s\n" % (e.node, e.errstr))
-                if e.errstr == 'Exception':
-                    traceback.print_exception(e.args[0], e.args[1], e.args[2])
-
+            jobs.run()
             for n in nodes:
                 state = n.get_state()
                 if (state != SCons.Node.executed and
             # Slide our wrapper into the construction environment as
             # the SPAWN function.
             self.env['SPAWN'] = self.pspawn_wrapper
-            self.env['SCONF_TEXT'] = text
+            sourcetext = self.env.Value(text)
 
             if text != None:
-                source = self.confdir.File(f + extension)
-                sourceNode = self.env.SConfSourceBuilder(target=source,
-                                                         source=None)
-                nodesToBeBuilt.extend(sourceNode)
+                textFile = self.confdir.File(f + extension)
+                textFileNode = self.env.SConfSourceBuilder(target=textFile,
+                                                           source=sourcetext)
+                nodesToBeBuilt.extend(textFileNode)
+                source = textFileNode
             else:
                 source = None
 
             result = self.BuildNodes(nodesToBeBuilt)
 
         finally:
-            # Clean up the environment, restoring the SPAWN value.
+            # Restor the SPAWN value to the environment.
             self.env['SPAWN'] = save_spawn
-            del self.env['SCONF_TEXT']
 
         _ac_build_counter = _ac_build_counter + 1
         if result:
                        "Test called after sconf.Finish()")
             context = CheckContext(self.sconf)
             ret = apply(self.test, (context,) +  args, kw)
+            if not self.sconf.config_h is None:
+                self.sconf.config_h_text = self.sconf.config_h_text + context.config_h
             context.Result("error: no result")
             return ret
 
         for name in tests.keys():
             self.AddTest(name, tests[name])
 
-    def _preCache(self, target, source, env):
-        # Action before target is actually built
-        #
-        # We record errors in the cache. Only non-exisiting targets may
-        # have recorded errors
-        needs_rebuild = target[0].exists()
-        buildSig = target[0].calc_signature(self.calc)
-        for node in source:
-            if node.get_state() != SCons.Node.up_to_date:
-                # if any of the sources has changed, we cannot use our cache
-                needs_rebuild = 1
-        tname = str(target[0])
-        if not self.cache.has_key( tname ):
-            # We have no recorded error, so we try to build the target
-            needs_rebuild = 1
-        else:
-            lastBuildSig = self.cache[tname]['builder']
-            if lastBuildSig != buildSig:
-                needs_rebuild = 1
-        if not needs_rebuild:
-            # When we are here, we can savely pass the recorded error
-            print ('(cached): Building "%s" failed in a previous run.' %
-                   target[0])
-            return 1
-        else:
-            # Otherwise, we try to record an error
-            self.cache[tname] = {
-               'builder' :  buildSig
-            }
-
-    def _postCache(self, target, source, env):
-        # Action after target is successfully built
-        #
-        # No error during build -> remove the recorded error
-        del self.cache[str(target[0])]
-
-    def _stringCache(self, target, source, env):
-        return None
-
-    def _loadCache(self):
-        # try to load build-error cache
-        try:
-            cacheDesc = cPickle.load(open(str(self.confdir.File(".cache"))))
-            if cacheDesc['scons_version'] != SCons.__version__:
-                raise Exception, "version mismatch"
-            self.cache = cacheDesc['data']
-        except KeyboardInterrupt:
-            raise
-        except:
-            self.cache = {}
-
-    def _dumpCache(self):
-        if dryrun:
-            return
-        # try to dump build-error cache
-        try:
-            cacheDesc = {'scons_version' : SCons.__version__,
-                         'data'          : self.cache }
-            cPickle.dump(cacheDesc, open(str(self.confdir.File(".cache")),"w"))
-        except Exception, e:
-            # this is most likely not only an IO error, but an error
-            # inside SConf ...
-            SCons.Warnings.warn( SConfWarning, "Couldn't dump SConf cache" )
-
     def _createDir( self, node ):
         dirName = str(node)
         if dryrun:
             if not os.path.isdir( dirName ):
-                raise SCons.Errors.ConfigureDryRunError(dirName)
+                raise ConfigureDryRunError(dirName)
         else:
             if not os.path.isdir( dirName ):
                 os.makedirs( dirName )
         """Private method. Set up logstream, and set the environment
         variables necessary for a piped build
         """
-        global _ac_config_counter
-        global _activeSConfObjects
+        global _ac_config_logs
+        global sconf_global
         global SConfFS
         
         self.lastEnvFs = self.env.fs
         if self.logfile != None and not dryrun:
             # truncate logfile, if SConf.Configure is called for the first time
             # in a build
-            if _ac_config_counter == 0:
+            if _ac_config_logs.has_key(self.logfile):
+                log_mode = "a"
+            else:
+                _ac_config_logs[self.logfile] = None
                 log_mode = "w"
-            else:
-                log_mode = "a"
             self.logstream = open(str(self.logfile), log_mode)
             # logfile may stay in a build directory, so we tell
             # the build system not to override it with a eventually
             # existing file with the same name in the source directory
             self.logfile.dir.add_ignore( [self.logfile] )
 
-            tb = traceback.extract_stack()[-3]
-            
-            self.logstream.write( '\nfile %s,line %d:\n\tConfigure( confdir = %s )\n\n' %
-                                  (tb[0], tb[1], str(self.confdir)) )
+            tb = traceback.extract_stack()[-3-self.called_from_env_method]
+            old_fs_dir = SConfFS.getcwd()
+            SConfFS.chdir(SConfFS.Top, change_os_dir=0)
+            self.logstream.write('file %s,line %d:\n\tConfigure(confdir = %s)\n' %
+                                 (tb[0], tb[1], str(self.confdir)) )
+            SConfFS.chdir(old_fs_dir)
         else: 
             self.logstream = None
         # we use a special builder to create source files from TEXT
         action = SCons.Action.Action(_createSource,
-                                     _stringSource,
-                                     varlist=['SCONF_TEXT'])
+                                     _stringSource)
         sconfSrcBld = SCons.Builder.Builder(action=action)
         self.env.Append( BUILDERS={'SConfSourceBuilder':sconfSrcBld} )
+        self.config_h_text = _ac_config_hs.get(self.config_h, "")
         self.active = 1
         # only one SConf instance should be active at a time ...
-        _activeSConfObjects[self] = None
-        _ac_config_counter = _ac_config_counter + 1
-        self._loadCache()
+        sconf_global = self
 
     def _shutdown(self):
         """Private method. Reset to non-piped spawn"""
-        global _activeSConfObjets
+        global sconf_global, _ac_config_hs
 
         if not self.active:
             raise SCons.Errors.UserError, "Finish may be called only once!"
-        if self.logstream != None:
+        if self.logstream != None and not dryrun:
+            self.logstream.write("\n")
             self.logstream.close()
             self.logstream = None
         # remove the SConfSourceBuilder from the environment
         del blds['SConfSourceBuilder']
         self.env.Replace( BUILDERS=blds )
         self.active = 0
-        del _activeSConfObjects[self]
-        self._dumpCache()
+        sconf_global = None
+        if not self.config_h is None:
+            _ac_config_hs[self.config_h] = self.config_h_text
         self.env.fs = self.lastEnvFs
 
 class CheckContext:
     def __init__(self, sconf):
         """Constructor. Pass the corresponding SConf instance."""
         self.sconf = sconf
-        self.cached = 0
         self.did_show_result = 0
 
         # for Conftest.py:
         self.vardict = {}
         self.havedict = {}
-        self.headerfilename = None      # XXX may cause trouble!
+        self.headerfilename = None
+        self.config_h = "" # config_h text will be stored here
+        # we don't regenerate the config.h file after each test. That means,
+        # that tests won't be able to include the config.h file, and so
+        # they can't do an #ifdef HAVE_XXX_H. This shouldn't be a major
+        # issue, though. If it turns out, that we need to include config.h
+        # in tests, we must ensure, that the dependencies are worked out
+        # correctly. Note that we can't use Conftest.py's support for config.h,
+        # cause we will need to specify a builder for the config.h file ...
 
     def Message(self, text):
         """Inform about what we are doing right now, e.g.
         'Checking for SOMETHING ... '
         """
-        # write to config.log
-        if self.sconf.logstream != None:
-            self.sconf.logstream.write(text + '\n')
-        sys.stdout.write(text)
+        self.Display(text)
+        self.sconf.cached = 1
         self.did_show_result = 0
 
     def Result(self, res):
         """
         if type(res) in BooleanTypes:
             if res:
-                text = "ok"
+                text = "yes"
             else:
-                text = "failed"
+                text = "no"
         elif type(res) == types.StringType:
             text = res
         else:
             raise TypeError, "Expected string, int or bool, got " + str(type(res))
 
         if self.did_show_result == 0:
-            if self.cached:
-                text = text + " (cached)"
-
             # Didn't show result yet, do it now.
-            if self.sconf.logstream != None:
-                self.sconf.logstream.write("Result: " + text + "\n\n")
-            sys.stdout.write(text + "\n")
+            self.Display(text + "\n")
             self.did_show_result = 1
 
-
     def TryBuild(self, *args, **kw):
         return apply(self.sconf.TryBuild, args, kw)
 
     #### Stuff used by Conftest.py (look there for explanations).
 
     def BuildProg(self, text, ext):
+        self.sconf.cached = 1
         # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
-        res = self.TryBuild(self.env.Program, text, ext)
-        if type(res) in BooleanTypes:
-            if res:
-                ret = ""
-            else:
-                ret = "failed to build test program"
-        elif type(res) == types.StringType:
-            ret = res
-        else:
-            raise TypeError, "Expected string or int"
-        return ret
+        return not self.TryBuild(self.env.Program, text, ext)
 
     def CompileProg(self, text, ext):
+        self.sconf.cached = 1
         # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
-        res = self.TryBuild(self.env.Object, text, ext)
-        if type(res) in BooleanTypes:
-            if res:
-                ret = ""
-            else:
-                ret = "failed to compile test program"
-        elif type(res) == types.StringType:
-            ret = res
-        else:
-            raise TypeError, "Expected string or int"
-        return ret
+        return not self.TryBuild(self.env.Object, text, ext)
 
     def AppendLIBS(self, lib_name_list):
         oldLIBS = self.env.get( 'LIBS', [] )
         return oldLIBS
 
     def Display(self, msg):
-        sys.stdout.write(msg)
-        self.Log(msg)
+        if self.sconf.cached:
+            # We assume that Display is called twice for each test here
+            # once for the Checking for ... message and once for the result.
+            # The self.sconf.cached flag can only be set between those calls
+            msg = "(cached) " + msg
+            self.sconf.cached = 0
+        progress_display(msg, append_newline=0)
+        self.Log("scons: Configure: " + msg + "\n")
 
     def Log(self, msg):
         if self.sconf.logstream != None:
 def CheckFunc(context, function_name, language = None):
     res = SCons.Conftest.CheckFunc(context, function_name, language = language)
     context.did_show_result = 1
-    if not res:
-        return 1        # Ok
-    return 0            # Failed
-
+    return not res
 
 def CheckType(context, type_name, includes = "", language = None):
     res = SCons.Conftest.CheckType(context, type_name,
                                         header = includes, language = language)
     context.did_show_result = 1
-    if not res:
-        return 1        # Ok
-    return 0            # Failed
+    return not res
 
+def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
+    # used by CheckHeader and CheckLibWithHeader to produce C - #include
+    # statements from the specified header (list)
+    if not SCons.Util.is_List(headers):
+        headers = [headers]
+    l = []
+    if leaveLast:
+        lastHeader = headers[-1]
+        headers = headers[:-1]
+    else:
+        lastHeader = None
+    for s in headers:
+        l.append("#include %s%s%s\n"
+                 % (include_quotes[0], s, include_quotes[1]))
+    return string.join(l, ''), lastHeader
 
 def CheckHeader(context, header, include_quotes = '<>', language = None):
     """
     A test for a C or C++ header file.
     """
-    if not SCons.Util.is_List(header):
-        header = [header]
-    l = []
-    for s in header[:-1]:
-        l.append("#include %s%s%s\n" % (include_quotes[0], s, include_quotes[1]))
-    res = SCons.Conftest.CheckHeader(context, header[-1], string.join(l, ''),
+    prog_prefix, hdr_to_check = \
+                 createIncludesFromHeaders(header, 1, include_quotes)
+    res = SCons.Conftest.CheckHeader(context, hdr_to_check, prog_prefix,
                                      language = language,
                                      include_quotes = include_quotes)
     context.did_show_result = 1
-    if not res:
-        return 1        # Ok
-    return 0            # Failed
-
+    return not res
 
 # Bram: Make this function obsolete?  CheckHeader() is more generic.
 
 
 
 def CheckLib(context, library = None, symbol = "main", autoadd = 1,
-                                               header = None, language = None):
+             header = None, language = None):
     """
     A test for a library. See also CheckLibWithHeader.
     Note that library may also be None to test whether the given symbol
     res = SCons.Conftest.CheckLib(context, library, symbol, header = header,
                                         language = language, autoadd = autoadd)
     context.did_show_result = 1
-    if not res:
-        return 1        # Ok
-    return 0            # Failed
-
+    return not res
 
 # XXX
 # Bram: Can only include one header and can't use #ifdef HAVE_HEADER_H.
 
 def CheckLibWithHeader(context, libs, header, language,
-                                                call = "main();", autoadd = 1):
+                       call = "main();", autoadd = 1):
     # ToDo: accept path for library. Support system header files.
     """
     Another (more sophisticated) test for a library.
     As in CheckLib, we support library=None, to test if the call compiles
     without extra link flags.
     """
-
-    if not SCons.Util.is_List(header):
-        header = [header]
-    l = []
-    for s in header:
-        l.append('#include "%s"\n' % (s))
-
-
+    prog_prefix, dummy = \
+                 createIncludesFromHeaders(header, 0)
     if libs == []:
         libs = [None]
 
     if not SCons.Util.is_List(libs):
         libs = [libs]
 
-    res = SCons.Conftest.CheckLib(context, libs, "main", string.join(l, ''),
+    res = SCons.Conftest.CheckLib(context, libs, "main", prog_prefix,
             call = call, language = language, autoadd = autoadd)
     context.did_show_result = 1
-    if not res:
-        return 1        # Ok
-    return 0            # Failed
+    return not res
 
     

src/engine/SCons/SConfTests.py

             sconf.Finish()
         # we should have exactly one one error cached 
         log = self.test.read( self.test.workpath('config.log') )
-        expr = re.compile( ".*(\(cached\))", re.DOTALL ) 
+        expr = re.compile( ".*failed in a previous run and all", re.DOTALL ) 
         firstOcc = expr.match( log )
         assert firstOcc != None 
         secondOcc = expr.match( log, firstOcc.end(0) )
                         return
                     def built(self):
                         pass
+                    def get_stored_info(self):
+                        pass
+                    def calc_signature(self, calc):
+                        pass
                 return [MyNode('n1'), MyNode('n2')]
         try:
             self.scons_env.Append(BUILDERS = {'SConfActionBuilder' : MyBuilder()})
             assert not res[1][0] and res[1][1] == ""
         finally:
             sconf.Finish()
-        # we should have exactly one one error cached 
+        # we should have exactly one error cached 
         log = self.test.read( self.test.workpath('config.log') )
-        expr = re.compile( ".*(\(cached\))", re.DOTALL )
+        expr = re.compile( ".*failed in a previous run and all", re.DOTALL )
         firstOcc = expr.match( log )
         assert firstOcc != None 
         secondOcc = expr.match( log, firstOcc.end(0) )
                 assert got == expect, "before and after LIBS were not the same"
             finally:
                 sconf.env = env
-
         finally:
             sconf.Finish()
 

src/engine/SCons/Script/__init__.py

 try:
     __builtin__.zip
 except AttributeError:
-    def zip(l1, l2):
+    def zip(*lists):
         result = []
-        for i in xrange(len(l1)):
-           result.append((l1[i], l2[i]))
+        for i in xrange(len(lists[0])):
+            result.append(tuple(map(lambda l, i=i: l[i], lists)))
         return result
     __builtin__.zip = zip
 
                         action="store_true", dest='cache_show', default=0,
                         help="Print build actions for files from CacheDir.")
 
+        config_options = ["auto", "force" ,"cache"]
+
+        def opt_config(option, opt, value, parser, c_options=config_options):
+            if value in c_options:
+                parser.values.config = value
+            else:
+                raise OptionValueError("Warning:  %s is not a valid config type" % value)
+        self.add_option('--config', action="callback", type="string",
+                        callback=opt_config, nargs=1, dest="config",
+                        metavar="MODE", default="auto",
+                        help="Controls Configure subsystem: "
+                             "%s." % string.join(config_options, ", "))
+
         def opt_not_yet(option, opt, value, parser):
             sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
             sys.exit(0)
         CleanTask.execute = CleanTask.show
     if options.question:
         SCons.SConf.dryrun = 1
+    SCons.SConf.SetCacheMode(options.config)
+    SCons.SConf.SetProgressDisplay(progress_display)
 
     if options.no_progress or options.silent:
         progress_display.set_mode(0)
         sys.exit(exit_status)
     global sconscript_time
     sconscript_time = time.time() - start_time
+    SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
     progress_display("scons: done reading SConscript files.")
 
     # Tell the Node.FS subsystem that we're all done reading the
         _scons_internal_error()
     except SCons.Errors.UserError, e:
         _scons_user_error(e)
-    except SCons.Errors.ConfigureDryRunError, e:
-        _scons_configure_dryrun_error(e)
     except:
         # An exception here is likely a builtin Python exception Python
         # code in an SConscript file.  Show them precisely what the

src/engine/SCons/Util.py

     def __init__(self):
         self.__call__ = self.print_it
 
-    def print_it(self, text):
-        sys.stdout.write(text + '\n')
+    def print_it(self, text, append_newline=1):
+        if append_newline: text = text + '\n'
+        sys.stdout.write(text)
 
-    def dont_print(self, text):
+    def dont_print(self, text, append_newline=1):
         pass
 
     def set_mode(self, mode):

src/engine/SCons/UtilTests.py

         display("line2")
         display.set_mode(1)
         display("line3")
-
-        assert sys.stdout.buffer == "line1\nline3\n"
+        display("line4\n", append_newline=0)
+        display.set_mode(0)
+        display("dont print1")
+        display("dont print2\n", append_newline=0)
+        display.set_mode(1)
+        assert sys.stdout.buffer == "line1\nline3\nline4\n"
         sys.stdout = old_stdout
 
     def test_fs_delete(self):

test/Configure.py

 
 import os
 import re
+import shutil
+import string
 import sys
-import shutil
+
+import __builtin__
+try:
+    __builtin__.zip
+except AttributeError:
+    def zip(*lists):
+        result = []
+        for i in xrange(len(lists[0])):
+            result.append(tuple(map(lambda l, i=i: l[i], lists)))
+        return result
+    __builtin__.zip = zip
 
 import TestCmd
 import TestSCons
 work_dir = None
 python = TestSCons.python
 test = TestSCons.TestSCons()
+_obj = TestSCons._obj
+_exe = TestSCons._exe
 
-
-def reset(match = 1):
+RE = 0
+RE_DOTALL = 1
+EXACT = 2
+def reset(match):
     global test, work_dir, work_cnt
     work_cnt = work_cnt + 1
     work_dir='test%d' % work_cnt
     test.subdir(work_dir)
-    if match == 0:
+    if match == RE:
         test.match_func = TestCmd.match_re
-    elif match == 1:
+    elif match == RE_DOTALL:
         test.match_func = TestCmd.match_re_dotall
-    elif match == 2:
+    elif match == EXACT:
         test.match_func = TestCmd.match_exact
 
 def checkFiles(test, files):
     for f in files:
         test.fail_test( not os.path.isfile( test.workpath(work_dir,f) ) )
 
+def checklib(lang, name, up_to_date):
+    if lang == 'C':
+        return (".c", _obj, _exe)
+    elif lang == 'C++':
+        return (".cc", _obj, _exe)
 
-def checkLog( test, logfile, numUpToDate, numCache ):
-    test.fail_test(not os.path.exists(test.workpath(work_dir, logfile)))
-    log = test.read(test.workpath(work_dir, logfile))
+NCR = 0 # non-cached rebuild
+CR  = 1 # cached rebuild (up to date)
+NCF = 2 # non-cached build failure
+CF  = 3 # cached build failure
+
+def checkLogAndStdout(checks, results, cached,
+                      test, logfile, sconf_dir, sconstruct,
+                      doCheckLog=1, doCheckStdout=1):
+    class NoMatch:
+        def __init__(self, p):
+            self.pos = p
+            
+    def matchPart(log, logfile, lastEnd):
+        m = re.match(log, logfile[lastEnd:])
+        if not m:
+            raise NoMatch, lastEnd
+        return m.end() + lastEnd
     try:
-        test.fail_test( len( re.findall( "is up to date", log ) ) != numUpToDate )
-        test.fail_test( len( re.findall( "\(cached\): Building \S+ failed in a previous run.", log ) ) != numCache )
-    except:
-        print "contents of log ", test.workpath(work_dir, logfile), "\n", log
-        raise
+        #print len(os.linesep)
+        ls = os.linesep
+        nols = "("
+        for i in range(len(ls)):
+            nols = nols + "("
+            for j in range(i):
+                nols = nols + ls[j]
+            nols = nols + "[^" + ls[i] + "])"
+            if i < len(ls)-1:
+                nols = nols + "|"
+        nols = nols + ")"
+        lastEnd = 0
+        logfile = test.read(test.workpath(work_dir, logfile))
+        if (doCheckLog and
+            string.find( logfile, "scons: warning: The stored build "
+                         "information has an unexpected class." ) >= 0):
+            test.fail_test()
+        sconf_dir = sconf_dir
+        sconstruct = sconstruct
 
+        log = re.escape("file " + sconstruct + ",line ") + r"\d+:" + ls
+        if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
+        log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
+        if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
+        rdstr = ""
+        cnt = 0
+        for check,result,cache_desc in zip(checks, results, cached):
+            log   = re.escape("scons: Configure: " + check) + ls
+            if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
+            log = ""
+            result_cached = 1
+            for bld_desc in cache_desc: # each TryXXX
+                for ext, flag in bld_desc: # each file in TryBuild
+                    file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
+                    if flag == NCR:
+                        # rebuild will pass
+                        if ext in ['.c', '.cpp']:
+                            log=log + re.escape(file + " <-") + ls
+                            log=log + r"(  \|" + nols + "*" + ls + ")+?"
+                        else:
+                            log=log + "(" + nols + "*" + ls +")*?"
+                        result_cached = 0
+                    if flag == CR:
+                        # up to date
+                        log=log + \
+                             re.escape("scons: Configure: \"%s\" is up to date." 
+                                       % file) + ls
+                        log=log+re.escape("scons: Configure: The original builder "
+                                          "output was:") + ls
+                        log=log+r"(  \|.*"+ls+")+"
+                    if flag == NCF:
+                        # non-cached rebuild failure
+                        log=log + "(" + nols + "*" + ls + ")*?"
+                        result_cached = 0
+                    if flag == CF:
+                        # cached rebuild failure
+                        log=log + \
+                             re.escape("scons: Configure: Building \"%s\" failed "
+                                       "in a previous run and all its sources are"
+                                       " up to date." % file) + ls
+                        log=log+re.escape("scons: Configure: The original builder "
+                                          "output was:") + ls
+                        log=log+r"(  \|.*"+ls+")+"
+                cnt = cnt + 1
+            if result_cached:
+                result = "(cached) " + result
+            rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
+            log=log + re.escape("scons: Configure: " + result) + ls + ls
+            if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
+            log = ""
+        if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
+        if doCheckLog and lastEnd != len(logfile):
+            raise NoMatch, lastEnd
+        
+    except NoMatch, m:
+        print "Cannot match log file against log regexp."
+        print "log file: "
+        print "------------------------------------------------------"
+        print logfile[m.pos:]
+        print "------------------------------------------------------"
+        print "log regexp: "
+        print "------------------------------------------------------"
+        print log
+        print "------------------------------------------------------"
+        test.fail_test()
 
+    if doCheckStdout:
+        exp_stdout = test.wrap_stdout(".*", rdstr)
+        if not test.match_re_dotall(test.stdout(), exp_stdout):
+            print "Unexpected stdout: "
+            print "-----------------------------------------------------"
+            print repr(test.stdout())
+            print "-----------------------------------------------------"
+            print repr(exp_stdout)
+            print "-----------------------------------------------------"
+            test.fail_test()
+        
 try:
-
     # 1.1 if checks are ok, the cache mechanism should work
 
-    reset(match=2)
+    reset(RE)
 
     test.write([work_dir,  'SConstruct'], """
 env = Environment()
      Exit(1)
 """ % (lib,lib))
 
-    required_stdout = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
-                                       read_str=
-    """Checking for main() in C library %s... yes
-Checking for main() in C library None... yes
-Checking for main() in C library %s... yes
-Checking for main() in C library None... yes
-Checking for C header file math.h... yes
-Checking for C++ header file vector... yes
-""" % (lib, lib))
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for main() in C library %s... " % lib,
+                       "Checking for main() in C library None... ",
+                       "Checking for main() in C library %s... " % lib,
+                       "Checking for main() in C library None... ",
+                       "Checking for C header file math.h... ",
+                       "Checking for C++ header file vector... "],
+                      ["yes"]*6,
+                      [[((".c", NCR), (_obj, NCR), (_exe, NCR))]]*4 +
+                        [[((".c", NCR), (_obj, NCR))]] +
+                        [[((".cpp", NCR), (_obj, NCR))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")    
+    
 
-
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog(test,'config.log', 0, 0 )
-
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog(test,'config.log',12, 0 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for main() in C library %s... " % lib,
+                       "Checking for main() in C library None... ",
+                       "Checking for main() in C library %s... " % lib,
+                       "Checking for main() in C library None... ",
+                       "Checking for C header file math.h... ",
+                       "Checking for C++ header file vector... "],
+                      ["yes"]*6,
+                      [[((".c", CR), (_obj, CR), (_exe, CR))]]*4 +
+                       [[((".c", CR), (_obj, CR))]] +
+                       [[((".cpp", CR), (_obj, CR))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")
 
     # 1.2 if checks are not ok, the cache mechanism should work as well
     #     (via explicit cache)
-    reset(match=2)              # match exactly, "()" is a regexp thing
+    reset(EXACT)              # match exactly, "()" is a regexp thing
 
     test.write([work_dir,  'SConstruct'], """
 env = Environment()
      Exit(1)
 """)
 
-    required_stdout = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
-                                       read_str=
-    """Checking for C header file no_std_c_header.h... no
-Checking for main() in C library no_c_library_SAFFDG... no
-""")
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file no_std_c_header.h... ",
+                       "Checking for main() in C library no_c_library_SAFFDG... "],
+                      ["no"]*2,
+                      [[((".c", NCR), (_obj, NCF))],
+                       [((".c", NCR), (_obj, NCR), (_exe, NCF))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")
 
-
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog(test, 'config.log', 0, 0 )
-
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog(test, 'config.log', 2, 2 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file no_std_c_header.h... ",
+                       "Checking for main() in C library no_c_library_SAFFDG... "],
+                      ["no"]*2,
+                      [[((".c", CR), (_obj, CF))],
+                       [((".c", CR), (_obj, CR), (_exe, CF))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")
 
 
     # 2.1 test that normal builds work together with Sconf
-    reset()
+    reset(RE_DOTALL)
 
 
     test.write([work_dir,  'SConstruct'], """
   printf( "Hello\\n" );
 }
 """)
-    required_stdout = test.wrap_stdout(build_str='.*',
-                                       read_str=
-    """Checking for C header file math.h... yes
-Checking for C header file no_std_c_header.h... no
-""")
-    test.run(chdir=work_dir,  stdout = required_stdout )
-    checkLog( test, 'config.log', 0, 0 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file math.h... ",
+                       "Checking for C header file no_std_c_header.h... "],
+                      ["yes", "no"],
+                      [[((".c", NCR), (_obj, NCR))],
+                       [((".c", NCR), (_obj, NCF))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")
 
-    test.run(chdir=work_dir,  stdout = required_stdout )
-    checkLog( test, 'config.log', 3, 1 )
-
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file math.h... ",
+                       "Checking for C header file no_std_c_header.h... "],
+                      ["yes", "no"],
+                      [[((".c", CR), (_obj, CR))],
+                       [((".c", CR), (_obj, CF))]],
+                      test, "config.log", ".sconf_temp", "SConstruct")
 
     # 2.2 test that BuildDir builds work together with Sconf
-    reset()
+    reset(RE_DOTALL)
 
 
     test.write([work_dir,  'SConstruct'], """
   printf( "Hello\\n" );
 }
 """)
-    required_stdout = test.wrap_stdout(build_str='.*',
-                                       read_str=
-    """Checking for C header file math.h... yes
-Checking for C header file no_std_c_header.h... no
-""")
-    test.run(chdir=work_dir,  stdout = required_stdout )
-    checkLog( test, 'build/config.log', 0, 0 )
 
-    test.run(chdir=work_dir,  stdout = required_stdout )
-    checkLog( test, 'build/config.log', 3, 1 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file math.h... ",
+                       "Checking for C header file no_std_c_header.h... "],
+                      ["yes", "no"],
+                      [[((".c", NCR), (_obj, NCR))],
+                       [((".c", NCR), (_obj, NCF))]],
+                      test,
+                      os.path.join("build", "config.log"),
+                      os.path.join("build", "config.tests"),
+                      "SConstruct")
+    
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Checking for C header file math.h... ",
+                       "Checking for C header file no_std_c_header.h... "],
+                      ["yes", "no"],
+                      [[((".c", CR), (_obj, CR))],
+                       [((".c", CR), (_obj, CF))]],
+                      test,
+                      os.path.join("build", "config.log"),
+                      os.path.join("build", "config.tests"),
+                      "SConstruct")
 
     # 2.3 test that Configure calls in SConscript files work
     #     even if BuildDir is set
-    reset()
+    reset(RE_DOTALL)
 
     test.subdir( [work_dir, 'sub'], [work_dir, 'sub', 'local'] )
     test.write([work_dir,  'SConstruct'], """
   printf( "Hello\\n" );
 }
 """)
-    required_stdout = test.wrap_stdout(build_str='.*',
-                                       read_str=
-    """Checking for C header file math.h... yes
-Checking for C header file no_std_c_header.h... no
-Executing Custom Test ... ok
-""")
+
     # first with SConscriptChdir(0)
-    test.run(chdir=work_dir, stdout = required_stdout, arguments='chdir=no')
-    checkFiles( test, [".sconf_temp/.cache", "config.log"] )
-    checkLog( test, 'config.log', 0, 0 )
+    test.run(chdir=work_dir, arguments='chdir=no')
+    checkLogAndStdout( ["Checking for C header file math.h... ",
+                        "Checking for C header file no_std_c_header.h... ",
+                        "Executing Custom Test ... "],
+                        ["yes", "no", "yes"],
+                        [[((".c", NCR), (_obj, NCR))],
+                         [((".c", NCR), (_obj, NCF))],
+                         [((".c", NCR), (_obj, NCR))]],
+                        test, "config.log",
+                        ".sconf_temp",
+                        os.path.join("build", "sub", "SConscript"))
 
-    test.run(chdir=work_dir, stdout = required_stdout, arguments='chdir=no')
-    checkFiles( test, [".sconf_temp/.cache", "config.log"] )
-    checkLog( test, 'config.log', 5, 1 )
+    test.run(chdir=work_dir, arguments='chdir=no')
+    checkLogAndStdout( ["Checking for C header file math.h... ",
+                        "Checking for C header file no_std_c_header.h... ",
+                        "Executing Custom Test ... "],
+                        ["yes", "no", "yes"],
+                        [[((".c", CR), (_obj, CR))],
+                         [((".c", CR), (_obj, CF))],
+                         [((".c", CR), (_obj, CR))]],
+                        test, "config.log",
+                        ".sconf_temp",
+                        os.path.join("build", "sub", "SConscript"))
 
     shutil.rmtree(test.workpath(work_dir, ".sconf_temp"))
 
     # now with SConscriptChdir(1)
-    test.run(chdir=work_dir, stdout = required_stdout, arguments='chdir=yes')
-    checkFiles( test, [".sconf_temp/.cache", "config.log"] )
-    checkLog( test, 'config.log', 0, 0 )
+    test.run(chdir=work_dir, arguments='chdir=yes')
+    checkLogAndStdout( ["Checking for C header file math.h... ",
+                        "Checking for C header file no_std_c_header.h... ",
+                        "Executing Custom Test ... "],
+                        ["yes", "no", "yes"],
+                        [[((".c", NCR), (_obj, NCR))],
+                         [((".c", NCR), (_obj, NCF))],
+                         [((".c", NCR), (_obj, NCR))]],
+                        test, "config.log",
+                        ".sconf_temp",
+                        os.path.join("build", "sub", "SConscript"))
 
-    test.run(chdir=work_dir, stdout = required_stdout, arguments='chdir=yes')
-    checkFiles( test, [".sconf_temp/.cache", "config.log"] )
-    checkLog( test, 'config.log', 5, 1 )
+    test.run(chdir=work_dir, arguments='chdir=yes')
+    checkLogAndStdout( ["Checking for C header file math.h... ",
+                        "Checking for C header file no_std_c_header.h... ",
+                        "Executing Custom Test ... "],
+                        ["yes", "no", "yes"],
+                        [[((".c", CR), (_obj, CR))],
+                         [((".c", CR), (_obj, CF))],
+                         [((".c", CR), (_obj, CR))]],
+                        test, "config.log",
+                        ".sconf_temp",
+                        os.path.join("build", "sub", "SConscript"))
 
     # 3.1 test custom tests
-    reset()
+    reset(RE_DOTALL)
 
     compileOK = '#include <stdio.h>\\nint main() {printf("Hello");return 0;}'
     compileFAIL = "syntax error"
 env = conf.Finish()
 """ % (compileOK, compileFAIL, linkOK, linkFAIL, runOK, runFAIL,
        python, python ) )
-    required_stdout = test.wrap_stdout(build_str='.*',
-                                       read_str="Executing MyTest ... ok\n")
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog( test, 'config.log', 0, 0 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Executing MyTest ... "],
+                      ["yes"],
+                      [[(('.c', NCR), (_obj, NCR)),
+                        (('.c', NCR), (_obj, NCF)),
+                        (('.c', NCR), (_obj, NCR), (_exe, NCR)),
+                        (('.c', NCR), (_obj, NCR), (_exe, NCF)),
+                        (('.c', NCR), (_obj, NCR), (_exe, NCR), (_exe + '.out', NCR)),
+                        (('.c', NCR), (_obj, NCR), (_exe, NCR), (_exe + '.out', NCF)),
+                        (('', NCR),),
+                        (('', NCF),)]],
+                       test, "config.log", ".sconf_temp", "SConstruct")
 
-    test.run(chdir=work_dir, stdout = required_stdout)
-    checkLog( test, 'config.log', 12, 4 )
+    test.run(chdir=work_dir)
+    checkLogAndStdout(["Executing MyTest ... "],
+                      ["yes"],
+                      [[(('.c', CR), (_obj, CR)),
+                        (('.c', CR), (_obj, CF)),
+                        (('.c', CR), (_obj, CR), (_exe, CR)),
+                        (('.c', CR), (_obj, CR), (_exe, CF)),
+                        (('.c', CR), (_obj, CR), (_exe, CR), (_exe + '.out', CR)),
+                        (('.c', CR), (_obj, CR), (_exe, CR), (_exe + '.out', CF)),
+                        (('', CR),),
+                        (('', CF),)]],
+                       test, "config.log", ".sconf_temp", "SConstruct")
 
     # 4.1 test that calling normal builders from an actual configuring
     # environment works
-    reset()
+    reset(RE_DOTALL)
 
     test.write([work_dir, 'cmd.py'], r"""
 import sys
 
     # 4.2 test that calling Configure from a builder results in a
     # readable Error
-    reset(match=2)
+    reset(EXACT)
 
     test.write([work_dir, 'SConstruct'], """
 def ConfigureAction(target, source, env):
 """)
     test.run(chdir=work_dir)
 
+    # 5.1 test the ConfigureDryRunError
+    
+    reset(EXACT) # exact match
+    test.write([work_dir,  'SConstruct'], """
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckLib('%s') # will pass
+r2 = conf.CheckLib('hopefullynolib') # will fail
+env = conf.Finish()
+if not (r1 and not r2):
+     Exit(1)
+""" % (lib))
+
+    test.run(chdir=work_dir, arguments='-n', status=2, stderr="""
+scons: *** Cannot create configure directory ".sconf_temp" within a dry-run.
+File "SConstruct", line 5, in ?
+""")
+    test.must_not_exist([work_dir, 'config.log'])
+    test.subdir([work_dir, '.sconf_temp'])
+    
+    test.run(chdir=work_dir, arguments='-n', status=2, stderr="""
+scons: *** Cannot update configure test "%s" within a dry-run.
+File "SConstruct", line 6, in ?
+""" % os.path.join(".sconf_temp", "conftest_0.c"))
+
+    test.run(chdir=work_dir)
+    checkLogAndStdout( ["Checking for main() in C library %s... " % lib,
+                        "Checking for main() in C library hopefullynolib... "],
+                        ["yes", "no"],
+                        [[((".c", NCR), (_obj, NCR))],
+                         [((".c", NCR), (_obj, NCF))]],
+                        test, "config.log", ".sconf_temp", "SConstruct")
+    oldLog = test.read(test.workpath(work_dir, 'config.log'))
+
+    test.run(chdir=work_dir, arguments='-n')
+    checkLogAndStdout( ["Checking for main() in C library %s... " % lib,
+                        "Checking for main() in C library hopefullynolib... "],
+                        ["yes", "no"],
+                        [[((".c", CR), (_obj, CR))],
+                         [((".c", CR), (_obj, CF))]],
+                        test, "config.log", ".sconf_temp", "SConstruct",
+                        doCheckLog=0)
+    newLog = test.read(test.workpath(work_dir, 'config.log'))
+    if newLog != oldLog:
+        print "Unexpected update of log file within a dry run"
+        test.fail_test()
+
+    # 5.2 test the --config=<auto|force|cache> option
+    reset(EXACT) # exact match
+
+    test.write([work_dir,  'SConstruct'], """
+env = Environment(CPPPATH='#/include')
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader('non_system_header1.h')
+r2 = conf.CheckCHeader('non_system_header2.h')
+env = conf.Finish()
+""")
+    test.subdir([work_dir, 'include'])
+    test.write([work_dir, 'include', 'non_system_header1.h'], """
+/* A header */
+""")
+
+    test.run(chdir=work_dir, arguments='--config=cache', status=2, stderr="""
+scons: *** "%s" is not yet built and cache is forced.
+File "SConstruct", line 6, in ?
+""" % os.path.join(".sconf_temp", "conftest_0.c"))
+
+    test.run(chdir=work_dir, arguments='--config=auto')
+    checkLogAndStdout( ["Checking for C header file non_system_header1.h... ",
+                        "Checking for C header file non_system_header2.h... "],
+                        ["yes", "no"],
+                        [[((".c", NCR), (_obj, NCR))],
+                         [((".c", NCR), (_obj, NCF))]],
+                        test, "config.log", ".sconf_temp", "SConstruct")
+    test.run(chdir=work_dir, arguments='--config=auto')
+    checkLogAndStdout( ["Checking for C header file non_system_header1.h... ",
+                        "Checking for C header file non_system_header2.h... "],
+                        ["yes", "no"],
+                        [[((".c", CR), (_obj, CR))],
+                         [((".c", CR), (_obj, CF))]],