Lots of Configure() enhancements. (Christoph Wiedemann)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 5 Sep 2004 20:10:43 +0000 (20:10 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 5 Sep 2004 20:10:43 +0000 (20:10 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1060 fdb21ef1-2011-0410-befe-b5e4ea1792b1

16 files changed:
doc/man/scons.1
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Conftest.py
src/engine/SCons/Environment.py
src/engine/SCons/Errors.py
src/engine/SCons/ErrorsTests.py
src/engine/SCons/Platform/win32.py
src/engine/SCons/SConf.py
src/engine/SCons/SConfTests.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/Configure.py
test/option-n.py
test/option-q.py

index 37013ea66252f92e6dcc87fd9a0d75e2b650f0cf..81fa47fd0c1139386cdf66482efdf354938aa301 100644 (file)
@@ -421,6 +421,47 @@ This will produce consistent output for build logs,
 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 
@@ -2504,9 +2545,9 @@ env.Command('baz.out', 'baz.in',
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI Configure( env ", [" 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 ])
+.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,"
@@ -6432,13 +6473,16 @@ In contrast to autoconf,
 .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 ])
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ])
 .TP
-.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ])
+.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.
@@ -6461,6 +6505,30 @@ If you are using the
 .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
index eec33a5da12862020f16f93017ab6c5338fddd04..33ef27a0a9ea4a432147a5b861ca517388063b95 100644 (file)
@@ -62,6 +62,38 @@ RELEASE 0.97 - XXX
     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
index 802be53eb10f86945d6f8306c65a20d74acea4f4..948085afd29bc8eb54c4a8dc3b0dde0ae9c92423 100644 (file)
@@ -49,6 +49,18 @@ RELEASE 0.97 - 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(),
index 7260a5271776f9c91e2229725953344c8cdd5b0b..f8912437fd7975d8a5e6274ea98d389d70ecbed0 100644 (file)
@@ -78,6 +78,9 @@ Autoconf-like configuration support; low level implementation of tests.
 #                       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.
@@ -91,9 +94,17 @@ Autoconf-like configuration support; low level implementation of tests.
 #                       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
 #
@@ -121,9 +132,10 @@ def CheckBuilder(context, text = None, language = None):
 
     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)
@@ -164,10 +176,10 @@ def CheckFunc(context, function_name, header = None, language = None):
         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:
@@ -175,21 +187,22 @@ def CheckFunc(context, function_name, header = None, language = None):
         return msg
 
     text = """
-            %(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
-
-                    return 0;
-                }\n\n""" % { 'name': function_name,
-                             'include': includetext,
-                             'hdr': header }
+%(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
+
+  return 0;
+}
+""" % { 'name': function_name,
+        'include': includetext,
+        'hdr': header }
 
     context.Display("Checking for %s function %s()... " % (lang, function_name))
     ret = context.BuildProg(text, suffix)
@@ -282,17 +295,18 @@ def CheckType(context, type_name, fallback = None,
     # - Using "sizeof(TYPE)" is valid when TYPE is actually a variable.
     # - Using the previous two together works reliably.
     text = """
-            %(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 }
+%(include)s
+%(header)s
+
+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)
@@ -335,27 +349,28 @@ def CheckLib(context, libs, func_name, header = None,
         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:
@@ -390,7 +405,7 @@ def CheckLib(context, libs, func_name, header = None,
         if oldLIBS != -1 and (ret or not autoadd):
             context.SetLIBS(oldLIBS)
             
-        if ret == "":
+        if not ret:
             return ret
 
     return ret
@@ -418,8 +433,8 @@ def _YesNoResult(context, ret, key, text):
 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 */".
@@ -432,22 +447,24 @@ def _Have(context, key, have):
              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):
@@ -455,15 +472,17 @@ 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):
index 9e58ff6f87cab024edab0ce1f57a40b8eff311af..ba63032c875fa4f288d92d7d18ebef0d81e93420 100644 (file)
@@ -1041,6 +1041,7 @@ class Base:
         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:
index 7eb3098086f0cc74117b7a0381c88896f2a2d274..4dacde6f867ef2dcb0a57c8d70709f4d324122b4 100644 (file)
@@ -53,8 +53,3 @@ class ExplicitExit(Exception):
         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))
index 248b36651bc7abc30eda48d9d5faa62df27e5e27..b0b50d416d91f7484eb08380168a80e84ecc4f48 100644 (file)
@@ -58,14 +58,6 @@ class ErrorsTestCase(unittest.TestCase):
         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():
index b6df577d99160c1d18f29c2b2cd2e686d1becae4..d3c674b098e8604b16f33cf94b0f9c4799364e58 100644 (file)
@@ -300,7 +300,7 @@ def generate(env):
     # 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:
index 910d47f7838dad61201733ff832d1289b2293c3b..4d6c8316b2c75d8aa17167eb6e8ae153aea7ffd8 100644 (file)
@@ -28,9 +28,10 @@ Autoconf-like configuration support.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import cPickle
 import os
+import re
 import string
+import StringIO
 import sys
 import traceback
 import types
@@ -44,35 +45,273 @@ import SCons.Util
 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)
+
+# 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)
 
-# action to create the source
+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
@@ -87,7 +326,8 @@ class SConf:
     """
 
     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.
@@ -100,7 +340,7 @@ class SConf:
         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
@@ -110,6 +350,8 @@ class SConf:
             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 = {
@@ -125,81 +367,22 @@ class SConf:
         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
@@ -212,26 +395,15 @@ class SConf:
         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
@@ -288,13 +460,14 @@ class SConf:
             # 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
 
@@ -305,9 +478,8 @@ class SConf:
             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:
@@ -377,6 +549,8 @@ class SConf:
                        "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
 
@@ -392,75 +566,11 @@ class SConf:
         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 )
@@ -470,8 +580,8 @@ class SConf:
         """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
@@ -482,41 +592,43 @@ class SConf:
         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:
-                log_mode = "w"
-            else:
+            if _ac_config_logs.has_key(self.logfile):
                 log_mode = "a"
+            else:
+                _ac_config_logs[self.logfile] = None
+                log_mode = "w"
             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
@@ -524,8 +636,9 @@ class SConf:
         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:
@@ -549,22 +662,27 @@ 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):
@@ -575,25 +693,19 @@ class CheckContext:
         """
         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)
 
@@ -620,32 +732,14 @@ class CheckContext:
     #### 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', [] )
@@ -658,8 +752,14 @@ class CheckContext:
         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:
@@ -671,37 +771,41 @@ class CheckContext:
 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.
 
@@ -722,7 +826,7 @@ def CheckCXXHeader(context, header, include_quotes = '""'):
 
 
 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
@@ -739,16 +843,13 @@ def CheckLib(context, library = None, symbol = "main", autoadd = 1,
     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.
@@ -757,25 +858,17 @@ def CheckLibWithHeader(context, libs, header, language,
     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
 
     
index f9c144c32dd4c3a5cf8691eac9fb32e5c1ddd3ad..1f6e65cc600bb73a894c42d946f3a2562b8ac346 100644 (file)
@@ -122,7 +122,7 @@ class SConfTestCase(unittest.TestCase):
             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) )
@@ -191,6 +191,10 @@ class SConfTestCase(unittest.TestCase):
                         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()})
@@ -245,9 +249,9 @@ int main() {
             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) )
@@ -392,7 +396,6 @@ int main() {
                 assert got == expect, "before and after LIBS were not the same"
             finally:
                 sconf.env = env
-
         finally:
             sconf.Finish()
 
index 2325d35b35b58b04010f4a53bb336107754f23e1..0ad910216e7e0651ccaf9b2f6b5957b709ec3c04 100644 (file)
@@ -74,10 +74,10 @@ import __builtin__
 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
 
@@ -503,6 +503,19 @@ class OptParser(OptionParser):
                         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)
@@ -792,6 +805,8 @@ def _main(args, parser):
         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)
@@ -904,6 +919,7 @@ def _main(args, parser):
         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
@@ -1103,8 +1119,6 @@ def main():
         _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
index 323f5fffc41a70d9f3f2e77365d05dc7aaae2c92..8707ed4e03497e85dce5d922ec3d673e1b972e1a 100644 (file)
@@ -343,10 +343,11 @@ class DisplayEngine:
     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):
index 52f56a710d9f26df04f467208265b03995db2cef..d1873908c110abafbf2e1f30476ff6363e1b149f 100644 (file)
@@ -1210,8 +1210,12 @@ class UtilTestCase(unittest.TestCase):
         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):
index 7da4e9e0c7cd5b7c919b2fb8621bde168d6e4325..2309904c5d53de9b66c3cbca736d419cfb43cbc4 100644 (file)
@@ -26,8 +26,20 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import re
-import sys
 import shutil
+import string
+import sys
+
+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
@@ -44,18 +56,22 @@ work_cnt = 0
 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):
@@ -63,23 +79,131 @@ def checkFiles(test, files):
     for f in files:
         test.fail_test( not os.path.isfile( test.workpath(work_dir,f) ) )
 
-
-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))
+def checklib(lang, name, up_to_date):
+    if lang == 'C':
+        return (".c", _obj, _exe)
+    elif lang == 'C++':
+        return (".cc", _obj, _exe)
+
+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()
@@ -97,26 +221,36 @@ if not (r1 and r2 and r3 and r4 and r5 and r6):
      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, stdout = required_stdout)
-    checkLog(test,'config.log', 0, 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", 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',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()
@@ -131,22 +265,25 @@ if not (not r1 and not r2):
      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, stdout = required_stdout)
-    checkLog(test, 'config.log', 0, 0 )
+    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', 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'], """
@@ -171,20 +308,24 @@ int main() {
   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,  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", NCR), (_obj, NCR))],
+                       [((".c", NCR), (_obj, NCF))]],
+                      test, "config.log", ".sconf_temp", "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, "config.log", ".sconf_temp", "SConstruct")
 
     # 2.2 test that BuildDir builds work together with Sconf
-    reset()
+    reset(RE_DOTALL)
 
 
     test.write([work_dir,  'SConstruct'], """
@@ -211,20 +352,32 @@ int main() {
   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'], """
@@ -279,34 +432,61 @@ int main() {
   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, stdout = required_stdout, arguments='chdir=no')
-    checkFiles( test, [".sconf_temp/.cache", "config.log"] )
-    checkLog( test, 'config.log', 5, 1 )
+    # first with SConscriptChdir(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, 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, 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", 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, 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"
@@ -341,17 +521,35 @@ conf.CheckCustom()
 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
@@ -373,7 +571,7 @@ env = conf.Finish()
 
     # 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):
@@ -421,12 +619,260 @@ conf.Finish()
 """)
     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))]],
+                        test, "config.log", ".sconf_temp", "SConstruct")
+    
+    test.run(chdir=work_dir, arguments='--config=force')
+    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=cache')
+    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))]],
+                        test, "config.log", ".sconf_temp", "SConstruct")
+
+    test.write([work_dir, 'include', 'non_system_header2.h'], """
+/* Another header */
+""")
+    test.unlink([work_dir, 'include', 'non_system_header1.h'])
+    test.run(chdir=work_dir, arguments='--config=cache')
+    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))]],
+                        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... "],
+                        ["no", "yes"],
+                        [[((".c", CR), (_obj, NCF))],
+                         [((".c", CR), (_obj, NCR))]],
+                        test, "config.log", ".sconf_temp", "SConstruct")
+
+    # 5.3 test -Q option
+    reset(EXACT)
+    test.write([work_dir,  'SConstruct'], """
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader('stdio.h')
+env = conf.Finish()
+""")
+    test.run(chdir=work_dir, arguments='-Q',
+             stdout="scons: `.' is up to date.\n", stderr="")
+
+
+    # 6. check config.h support
+    reset(EXACT)
+    test.write([work_dir, 'SConstruct'], """
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env, config_h = 'config.h')
+r1 = conf.CheckFunc('printf')
+r2 = conf.CheckFunc('noFunctionCall')
+r3 = conf.CheckType('int')
+r4 = conf.CheckType('noType')
+r5 = conf.CheckCHeader('stdio.h', '<>')
+r6 = conf.CheckCHeader('hopefullynoc-header.h')
+r7 = conf.CheckCXXHeader('vector', '<>')
+r8 = conf.CheckCXXHeader('hopefullynocxx-header.h')
+env = conf.Finish()
+conf = Configure(env, config_h = 'config.h')
+r9 = conf.CheckLib('%s', 'sin')
+r10 = conf.CheckLib('hopefullynolib', 'sin')
+r11 = conf.CheckLibWithHeader('%s', 'math.h', 'c')
+r12 = conf.CheckLibWithHeader('%s', 'hopefullynoheader2.h', 'c')
+r13 = conf.CheckLibWithHeader('hopefullynolib2', 'math.h', 'c')
+env = conf.Finish()
+""" % (lib, lib, lib))
+
+    expected_read_str = """\
+Checking for C function printf()... yes
+Checking for C function noFunctionCall()... no
+Checking for C type int... yes
+Checking for C type noType... no
+Checking for C header file stdio.h... yes
+Checking for C header file hopefullynoc-header.h... no
+Checking for C++ header file vector... yes
+Checking for C++ header file hopefullynocxx-header.h... no
+Checking for sin() in C library %(lib)s... yes
+Checking for sin() in C library hopefullynolib... no
+Checking for main() in C library %(lib)s... yes
+Checking for main() in C library %(lib)s... no
+Checking for main() in C library hopefullynolib2... no
+""" % {'lib' : lib}
+
+    expected_build_str = """\
+scons: Configure: creating config.h
+"""
+    
+    expected_stdout = test.wrap_stdout(build_str=expected_build_str,
+                                       read_str=expected_read_str)
+
+    expected_config_h = string.replace("""#ifndef CONFIG_H_SEEN
+#define CONFIG_H_SEEN
+
+#define HAVE_PRINTF
+/* #undef HAVE_NOFUNCTIONCALL */
+#define HAVE_INT
+/* #undef HAVE_NOTYPE */
+#define HAVE_STDIO_H
+/* #undef HAVE_HOPEFULLYNOC_HEADER_H */
+#define HAVE_VECTOR
+/* #undef HAVE_HOPEFULLYNOCXX_HEADER_H */
+#define HAVE_%(LIB)s
+/* #undef HAVE_LIBHOPEFULLYNOLIB */
+#define HAVE_%(LIB)s
+/* #undef HAVE_%(LIB)s */
+/* #undef HAVE_LIBHOPEFULLYNOLIB2 */
+
+#endif /* CONFIG_H_SEEN */
+""" % {'LIB' : "LIB" + string.upper(lib) }, "\n", os.linesep)
+
+    test.run(chdir=work_dir, stdout=expected_stdout)
+    config_h = test.read(test.workpath(work_dir, 'config.h'))
+    if expected_config_h != config_h:
+        print "Unexpected config.h"
+        print "Expected: "
+        print "---------------------------------------------------------"
+        print repr(expected_config_h)
+        print "---------------------------------------------------------"
+        print "Found: "
+        print "---------------------------------------------------------"
+        print repr(config_h)
+        print "---------------------------------------------------------"
+        print "Stdio: "
+        print "---------------------------------------------------------"
+        print test.stdout()
+        print "---------------------------------------------------------"
+        test.fail_test()
+
+    expected_read_str = re.sub(r'\b((yes)|(no))\b',
+                               r'(cached) \1',
+                               expected_read_str)
+    expected_build_str = "scons: `.' is up to date.\n"
+    expected_stdout = test.wrap_stdout(build_str=expected_build_str,
+                                       read_str=expected_read_str)
+    #expected_stdout = string.replace(expected_stdout, "\n", os.linesep)
+    test.run(chdir=work_dir, stdout=expected_stdout)    
+    config_h = test.read(test.workpath(work_dir, 'config.h'))    
+    if expected_config_h != config_h:
+        print "Unexpected config.h"
+        print "Expected: "
+        print "---------------------------------------------------------"
+        print repr(expected_config_h)
+        print "---------------------------------------------------------"
+        print "Found: "
+        print "---------------------------------------------------------"
+        print repr(config_h)
+        print "---------------------------------------------------------"
+        print "Stdio: "
+        print "---------------------------------------------------------"
+        print test.stdout()
+        print "---------------------------------------------------------"
+        test.fail_test()
+    
+
     test.pass_test()
 
 finally:
     pass
     #os.system( 'find . -type f -exec ls -l {} \;' )
     #print "-------------config.log------------------"
-    #print test.read( test.workpath('config.log' ))
+    #print test.read( test.workpath(work_dir, 'config.log'))
     #print "-------------build/config.log------------"
     #print test.read( test.workpath('build/config.log' ))
index 7a324b5da776da0b642b54af481e839ea78dd020..fab117b04ffc43e795dde9284d96dad64f60b84b 100644 (file)
@@ -41,6 +41,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
+import re
 import string
 import sys
 import TestCmd
@@ -188,7 +189,7 @@ else:
 """)
 # test that conf_dir isn't created and an error is raised
 stderr=r"""
-scons: \*\*\* Cannot update configure test \(config\.test\) within a dry-run\.
+scons: \*\*\* Cannot create configure directory "config\.test" within a dry-run\.
 File \S+, line \S+, in \S+
 """
 test.run(arguments="-n",stderr=stderr,status=2,
@@ -200,9 +201,9 @@ test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
 # verify that .cache and config.log are not created.
 # an error should be raised
 stderr=r"""
-scons: \*\*\* Cannot update configure test \(config\.test.conftest_0\.in\) within a dry-run\.
+scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
 File \S+, line \S+, in \S+
-"""
+""" % re.escape(os.path.join("config.test", "conftest_0.in"))
 test.subdir(['configure','config.test'])
 test.run(arguments="-n",stderr=stderr,status=2,
          chdir=test.workpath("configure"))
@@ -217,17 +218,13 @@ test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
 # test that no error is raised, if all targets are up-to-date. In this
 # case .cache and config.log shouldn't be created
 stdout=test.wrap_stdout(build_str="scons: `.' is up to date.\n",
-                        read_str="""\
-Executing Custom Test ... ok
+                        read_str=r"""Executing Custom Test ... \(cached\) yes
 """)
 test.run(status=0,chdir=test.workpath("configure"))
-cache1_mtime = os.path.getmtime(test.workpath("configure","config.test",".cache"))
 log1_mtime = os.path.getmtime(test.workpath("configure","config.log"))
 test.run(stdout=stdout,arguments="-n",status=0,
          chdir=test.workpath("configure"))
-cache2_mtime = os.path.getmtime(test.workpath("configure","config.test",".cache"))
 log2_mtime = os.path.getmtime(test.workpath("configure","config.log"))
-test.fail_test( cache1_mtime != cache2_mtime )
 test.fail_test( log1_mtime != log2_mtime )
 
 test.pass_test()
index 449d5eb2d570d2cbdf1003964f3f6b75843870dd..e8460f09d3cde1416f352938dd3ff5ebed213ccd 100644 (file)
@@ -25,6 +25,7 @@
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os.path
+import re
 import string
 import sys
 
@@ -109,7 +110,7 @@ env.B(target='aaa.out', source='aaa.in')
 """)
 # test that conf_dir isn't created and an error is raised
 stderr=r"""
-scons: \*\*\* Cannot update configure test \(config\.test\) within a dry-run\.
+scons: \*\*\* Cannot create configure directory "config\.test" within a dry-run\.
 File \S+, line \S+, in \S+
 """
 test.run(arguments="-q aaa.out",stderr=stderr,status=2,
@@ -121,9 +122,9 @@ test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
 # verify that .cache and config.log are not created.
 # an error should be raised
 stderr=r"""
-scons: \*\*\* Cannot update configure test \(config\.test.conftest_0\.in\) within a dry-run\.
+scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
 File \S+, line \S+, in \S+
-"""
+""" % re.escape(os.path.join("config.test", "conftest_0.in"))
 test.subdir(['configure','config.test'])
 test.run(arguments="-q aaa.out",stderr=stderr,status=2,
          chdir=test.workpath("configure"))
@@ -138,17 +139,13 @@ test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
 # test that no error is raised, if all targets are up-to-date. In this
 # case .cache and config.log shouldn't be created
 stdout=test.wrap_stdout(build_str='cp aaa.in aaa.out\n',
-                        read_str="""\
-Executing Custom Test ... ok
+                        read_str="""Executing Custom Test ... yes
 """)
 test.run(stdout=stdout,arguments="aaa.out",status=0,chdir=test.workpath("configure"))
-cache1_mtime = os.path.getmtime(test.workpath("configure","config.test",".cache"))
 log1_mtime = os.path.getmtime(test.workpath("configure","config.log"))
 test.run(arguments="-q aaa.out",status=0,
          chdir=test.workpath("configure"))
-cache2_mtime = os.path.getmtime(test.workpath("configure","config.test",".cache"))
 log2_mtime = os.path.getmtime(test.workpath("configure","config.log"))
-test.fail_test( cache1_mtime != cache2_mtime )
 test.fail_test( log1_mtime != log2_mtime )
 
 test.pass_test()