From 8c0944c021fb182621e27270d970287d63943e92 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Sun, 5 Sep 2004 20:10:43 +0000 Subject: Lots of Configure() enhancements. (Christoph Wiedemann) --- doc/man/scons.1 | 78 ++++- src/CHANGES.txt | 32 ++ src/RELEASE.txt | 12 + src/engine/SCons/Conftest.py | 157 +++++---- src/engine/SCons/Environment.py | 1 + src/engine/SCons/Errors.py | 5 - src/engine/SCons/ErrorsTests.py | 8 - src/engine/SCons/Platform/win32.py | 2 +- src/engine/SCons/SConf.py | 601 +++++++++++++++++++-------------- src/engine/SCons/SConfTests.py | 11 +- src/engine/SCons/Script/__init__.py | 24 +- src/engine/SCons/Util.py | 7 +- src/engine/SCons/UtilTests.py | 8 +- test/Configure.py | 642 ++++++++++++++++++++++++++++++------ test/option-n.py | 13 +- test/option-q.py | 13 +- 16 files changed, 1144 insertions(+), 470 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 37013ea..81fa47f 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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 diff --git a/src/CHANGES.txt b/src/CHANGES.txt index eec33a5..33ef27a 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 802be53..948085a 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -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(), diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py index 7260a52..f891243 100644 --- a/src/engine/SCons/Conftest.py +++ b/src/engine/SCons/Conftest.py @@ -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,10 +94,18 @@ 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 - %(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 +%(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): diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 9e58ff6..ba63032 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -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: diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py index 7eb3098..4dacde6 100644 --- a/src/engine/SCons/Errors.py +++ b/src/engine/SCons/Errors.py @@ -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)) diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py index 248b366..b0b50d4 100644 --- a/src/engine/SCons/ErrorsTests.py +++ b/src/engine/SCons/ErrorsTests.py @@ -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(): diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index b6df577..d3c674b 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -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: diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 910d47f..4d6c831 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -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 diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index f9c144c..1f6e65c 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -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() diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 2325d35..0ad9102 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -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 diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 323f5ff..8707ed4 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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): diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 52f56a7..d187390 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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): diff --git a/test/Configure.py b/test/Configure.py index 7da4e9e..2309904 100644 --- a/test/Configure.py +++ b/test/Configure.py @@ -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 \\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= 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' )) diff --git a/test/option-n.py b/test/option-n.py index 7a324b5..fab117b 100644 --- a/test/option-n.py +++ b/test/option-n.py @@ -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() diff --git a/test/option-q.py b/test/option-q.py index 449d5eb..e8460f0 100644 --- a/test/option-q.py +++ b/test/option-q.py @@ -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() -- cgit v0.12