From: stevenknight Date: Fri, 16 May 2003 17:40:05 +0000 (+0000) Subject: SConf fixes. (Christoph Wiedemann) X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=4fbb308f0f99eabd8320d70750a3d71d1ba8842c;p=scons.git SConf fixes. (Christoph Wiedemann) git-svn-id: http://scons.tigris.org/svn/scons/trunk@686 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index ec286697..056b31e8 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -3074,18 +3074,28 @@ The following Checks are predefined. goes by and developers contribute new useful tests.) .TP -.RI Configure.CheckCHeader( self ", " header ) +.RI Configure.CheckCHeader( self ", " header ", [" include_quotes ]) Checks if .I header -is usable in the C-language. +is usable in the C-language. The optional argument +.I include_quotes +must be +a two character string, where the first character denotes the opening +quote and the second character denotes the closing quote (both default +to \N'34') Returns 1 on success and 0 on failure. .TP -.RI Configure.CheckCXXHeader( self ", " header ) +.RI Configure.CheckCXXHeader( self ", " header ", [" include_quotes ]) Checks if .I header -is usable in the C++ language. -Returns 1 on success and 0 on failure. +is usable in the C++ language. The optional argument +.I include_quotes +must be +a two character string, where the first character denotes the opening +quote and the second character denotes the closing quote (both default +to \N'34') +Returns 1 on success and 0 on failure. .TP .RI Configure.CheckLib( self ", [" library ", " symbol ", " autoadd ]) diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 3e325948..f980fb7a 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -72,6 +72,7 @@ act_py = test.workpath('act.py') outfile = test.workpath('outfile') outfile2 = test.workpath('outfile2') +pipe_file = test.workpath('pipe.out') scons_env = SCons.Environment.Environment() @@ -520,20 +521,47 @@ class CommandActionTestCase(unittest.TestCase): def test_pipe_execute(self): """Test capturing piped output from an action """ - pipe_file = open( test.workpath('pipe.out'), "w" ) + pipe = open( pipe_file, "w" ) self.env = Environment(ENV = {'ACTPY_PIPE' : '1'}, PIPE_BUILD = 1, - PSTDOUT = pipe_file, PSTDERR = pipe_file) + PSTDOUT = pipe, PSTDERR = pipe) # everything should also work when piping output self.test_execute() self.env['PSTDOUT'].close() - pipe_out = test.read( test.workpath('pipe.out') ) + pipe_out = test.read( pipe_file ) if sys.platform == 'win32': cr = '\r' else: cr = '' - found = re.findall( "act.py: stdout: executed act.py%s\nact.py: stderr: executed act.py%s\n" % (cr, cr), pipe_out ) + act_out = "act.py: stdout: executed act.py" + act_err = "act.py: stderr: executed act.py" + found = re.findall( "%s%s\n%s%s\n" % (act_out, cr, act_err, cr), + pipe_out ) assert len(found) == 8, found + # test redirection operators + def test_redirect(self, redir): + cmd = r'%s %s %s xyzzy %s' % (python, act_py, outfile, redir) + pipe = open( pipe_file, "w" ) + act = SCons.Action.CommandAction(cmd) + r = act([], [], self.env.Copy(PSTDOUT = pipe, PSTDERR = pipe)) + pipe.close() + assert r == 0 + return (test.read(outfile2, 'r'), test.read(pipe_file, 'r')) + + (redirected, pipe_out) = test_redirect(self,'> %s' % outfile2 ) + assert redirected == "%s\n" % act_out + assert pipe_out == "%s\n" % act_err + + (redirected, pipe_out) = test_redirect(self,'2> %s' % outfile2 ) + assert redirected == "%s\n" % act_err + assert pipe_out == "%s\n" % act_out + + (redirected, pipe_out) = test_redirect(self,'> %s 2>&1' % outfile2 ) + assert (redirected == "%s\n%s\n" % (act_out, act_err) or + redirected == "%s\n%s\n" % (act_err, act_out)) + assert pipe_out == "" + + def test_set_handler(self): """Test setting the command handler... """ diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 071a1d29..ff4dfb5c 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1041,7 +1041,7 @@ class Dir(Entry): creating it first if necessary.""" if not self._sconsign: import SCons.Sig - self._sconsign = SCons.Sig.SConsignFile(self) + self._sconsign = SCons.Sig.SConsignFileFactory(self) return self._sconsign def srcnode(self): diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py index 03c86984..3546a174 100644 --- a/src/engine/SCons/Platform/posix.py +++ b/src/engine/SCons/Platform/posix.py @@ -98,33 +98,18 @@ def piped_env_spawn(sh, escape, cmd, args, env, stdout, stderr): # the command name and the command's stdout is written to stdout # the command's stderr is written to stderr s = _get_env_command( sh, escape, cmd, args, env) - # write the command line out - if stdout != None: - stdout.write(string.join(args) + '\n') proc = popen2.Popen3(s, 1) # process stdout if stdout != None: - #for line in proc.fromchild.xreadlines(): - # stdout.write(line) - while 1: - line = proc.fromchild.readline() - if not line: - break - stdout.write(line) + stdout.write(proc.fromchild.read()) # process stderr if stderr != None: - #for line in proc.childerr.xreadlines(): - # stderr.write(line) - while 1: - line = proc.childerr.readline() - if not line: - break - stderr.write(line) + stderr.write(proc.childerr.read()) stat = proc.wait() if stat & 0xff: return stat | 0x80 return stat >> 8 - + def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): # spawn using fork / exec and providing a pipe for the command's # stdout / stderr stream @@ -135,9 +120,6 @@ def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): (rFdOut, wFdOut) = os.pipe() rFdErr = rFdOut wFdErr = wFdOut - # write the command line out - if stdout != None: - stdout.write(string.join(args) + '\n') # do the fork pid = os.fork() if not pid: @@ -163,7 +145,7 @@ def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): pid, stat = os.waitpid(pid, 0) os.close( wFdOut ) if stdout != stderr: - os.close( wFdErr ) + os.close( wFdErr ) childOut = os.fdopen( rFdOut ) if stdout != stderr: childErr = os.fdopen( rFdErr ) @@ -171,22 +153,10 @@ def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): childErr = childOut # process stdout if stdout != None: - #for line in childOut.xreadlines(): - # stdout.write(line) - while 1: - line = childOut.readline() - if not line: - break - stdout.write(line) + stdout.write( childOut.read() ) # process stderr if stderr != None: - #for line in childErr.xreadlines(): - # stderr.write(line) - while 1: - line = childErr.readline() - if not line: - break - stdout.write(line) + stderr.write( childErr.read() ) os.close( rFdOut ) if stdout != stderr: os.close( rFdErr ) @@ -194,8 +164,8 @@ def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): return stat | 0x80 return stat >> 8 - - + + def generate(env): # If the env command exists, then we can use os.system() diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index b36d6117..bce50f49 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -85,45 +85,62 @@ class TempFileMunge: # scons. def piped_spawn(sh, escape, cmd, args, env, stdout, stderr): + # There is no direct way to do that in python. What we do + # here should work for most cases: + # In case stdout (stderr) is not redirected to a file, + # we redirect it into a temporary file tmpFileStdout + # (tmpFileStderr) and copy the contents of this file + # to stdout (stderr) given in the argument if not sh: sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n") return 127 else: - # NOTE: This is just a big, big hack. What we do is simply pipe the - # output to a temporary file and then write it to the streams. - # I DO NOT know the effect of adding these to a command line that - # already has indirection symbols. - tmpFile = os.path.normpath(tempfile.mktemp()) - args.append(">" + str(tmpFile)) - args.append("2>&1") - if stdout != None: - # ToDo: use the printaction instead of that - stdout.write(string.join(args) + "\n") + # one temporary file for stdout and stderr + tmpFileStdout = os.path.normpath(tempfile.mktemp()) + tmpFileStderr = os.path.normpath(tempfile.mktemp()) + + # check if output is redirected + stdoutRedirected = 0 + stderrRedirected = 0 + for arg in args: + # are there more possibilities to redirect stdout ? + if (string.find( arg, ">", 0, 1 ) != -1 or + string.find( arg, "1>", 0, 2 ) != -1): + stdoutRedirected = 1 + # are there more possibilities to redirect stderr ? + if string.find( arg, "2>", 0, 2 ) != -1: + stderrRedirected = 1 + + # redirect output of non-redirected streams to our tempfiles + if stdoutRedirected == 0: + args.append(">" + str(tmpFileStdout)) + if stderrRedirected == 0: + args.append("2>" + str(tmpFileStderr)) + + # actually do the spawn try: - try: - args = [sh, '/C', escape(string.join(args)) ] - ret = os.spawnve(os.P_WAIT, sh, args, env) - except OSError, e: - ret = exitvalmap[e[0]] + args = [sh, '/C', escape(string.join(args)) ] + ret = os.spawnve(os.P_WAIT, sh, args, env) + except OSError, e: + # catch any error + ret = exitvalmap[e[0]] + if stderr != None: stderr.write("scons: %s: %s\n" % (cmd, e[1])) + # copy child output from tempfiles to our streams + # and do clean up stuff + if stdout != None and stdoutRedirected == 0: try: - input = open( tmpFile, "r" ) - while 1: - line = input.readline() - if not line: - break - if stdout != None: - stdout.write(line) - if stderr != None and stderr != stdout: - stderr.write(line) - finally: - input.close() - finally: - try: - os.remove( tmpFile ) - except OSError: - # What went wrong here ?? + stdout.write(open( tmpFileStdout, "r" ).read()) + os.remove( tmpFileStdout ) + except: pass + + if stderr != None and stderrRedirected == 0: + try: + stderr.write(open( tmpFileStderr, "r" ).read()) + os.remove( tmpFileStderr ) + except: + pass return ret def spawn(sh, escape, cmd, args, env): diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 830b94a7..1546fb11 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -32,6 +32,7 @@ import cPickle import os import shutil import sys +import traceback from types import * import SCons.Action @@ -42,6 +43,10 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings +# 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 + _ac_build_counter = 0 _ac_config_counter = 0 _activeSConfObjects = {} @@ -50,12 +55,17 @@ class SConfWarning(SCons.Warnings.Warning): pass SCons.Warnings.enableWarningClass( SConfWarning ) - +# action to create the source def _createSource( target, source, env ): - fd = open(str(target[0]), "w") + fd = open(target[0].get_path(), "w") fd.write(env['SCONF_TEXT']) fd.close() +def _stringSource( target, source, env ): + import string + return (target[0].get_path() + ' <- \n |' + + string.replace( env['SCONF_TEXT'], "\n", "\n |" ) ) + class SConf: """This is simply a class to represent a configure context. After @@ -71,19 +81,22 @@ class SConf: """ def __init__(self, env, custom_tests = {}, conf_dir='#/.sconf_temp', - log_file='#config.log'): + log_file='#/config.log'): """Constructor. Pass additional tests in the custom_tests-dictinary, e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest defines a custom test. Note also the conf_dir and log_file arguments (you may want to build tests in the BuildDir, not in the SourceDir) """ + global SConfFS + if not SConfFS: + SConfFS = SCons.Node.FS.FS(SCons.Node.FS.default_fs.pathTop) if len(_activeSConfObjects.keys()) > 0: raise (SCons.Errors.UserError, "Only one SConf object may be active at one time") self.env = env if log_file != None: - self.logfile = SCons.Node.FS.default_fs.File(log_file) + self.logfile = SConfFS.File(log_file) else: self.logfile = None self.logstream = None @@ -98,7 +111,8 @@ class SConf: } self.AddTests(default_tests) self.AddTests(custom_tests) - self.confdir = SCons.Node.FS.default_fs.Dir(conf_dir) + self.confdir = SConfFS.Dir(conf_dir) + self.calc = None self.cache = {} self._startup() @@ -110,7 +124,7 @@ class SConf: self._shutdown() return self.env - def setCache(self, nodes, already_done = []): + 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 @@ -122,10 +136,12 @@ class SConf: # tests if (n.has_builder() and not n in already_done): - n.add_pre_action(SCons.Action.Action(self._preCache)) - n.add_post_action(SCons.Action.Action(self._postCache)) - already_done.append( n ) - self.setCache(n.children()) + 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()) def BuildNodes(self, nodes): """ @@ -147,24 +163,25 @@ class SConf: sys.stdout = self.logstream oldStderr = sys.stderr sys.stderr = self.logstream - - self.setCache( nodes ) + self._setCache( nodes ) ret = 1 try: - oldPwd = SCons.Node.FS.default_fs.getcwd() - SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top) # ToDo: use user options for calc - calc = SCons.Sig.Calculator(max_drift=0) + self.calc = SCons.Sig.Calculator(max_drift=0) tm = SCons.Taskmaster.Taskmaster( nodes, SConfBuildTask, - calc ) + self.calc ) # 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]) except: - pass + raise for n in nodes: state = n.get_state() @@ -172,7 +189,6 @@ class SConf: state != SCons.Node.up_to_date): # the node could not be built. we return 0 in this case ret = 0 - SCons.Node.FS.default_fs.chdir(oldPwd) finally: if self.logstream != None: # restore stdout / stderr @@ -189,9 +205,10 @@ class SConf: nodesToBeBuilt = [] - #target = self.confdir.File("conftest_" + str(_ac_build_counter)) f = "conftest_" + str(_ac_build_counter) - target = os.path.join(str(self.confdir), f) + pref = self.env.subst( builder.builder.prefix ) + suff = self.env.subst( builder.builder.suffix ) + target = self.confdir.File(pref + f + suff) self.env['SCONF_TEXT'] = text if text != None: source = self.confdir.File(f + extension) @@ -244,11 +261,7 @@ class SConf: compilation was successful, 0 otherwise. The target is saved in self.lastTarget (for further processing). """ - #ok = self.TryCompile( text, extension) - #if( ok ): return self.TryBuild(self.env.Program, text, extension ) - #else: - # return 0 def TryRun(self, text, extension ): """Compiles and runs the program given in text, using extension @@ -259,9 +272,8 @@ class SConf: ok = self.TryLink(text, extension) if( ok ): prog = self.lastTarget - output = SCons.Node.FS.default_fs.File(str(prog)+'.out') - node = self.no_pipe_env.Command(output, prog, "%s >%s" % (str(prog), - str(output))) + output = SConfFS.File(prog.get_path()+'.out') + node = self.env.Command(output, prog, "%s > $TARGET" % (prog.get_path())) ok = self.BuildNodes([node]) if ok: outputStr = output.get_contents() @@ -300,26 +312,26 @@ class SConf: # We record errors in the cache. Only non-exisiting targets may # have recorded errors needs_rebuild = target[0].exists() - buildSig = target[0].builder.action.get_contents(target, source, env) + 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 - if not self.cache.has_key( str(target[0]) ): + if not self.cache.has_key( target[0].get_path() ): # We have no recorded error, so we try to build the target needs_rebuild = 1 else: - lastBuildSig = self.cache[str(target[0])]['builder'] + lastBuildSig = self.cache[target[0].get_path()]['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.' % - str(target[0])) + target[0].get_path()) return 1 else: # Otherwise, we try to record an error - self.cache[str(target[0])] = { + self.cache[target[0].get_path()] = { 'builder' : buildSig } @@ -327,35 +339,36 @@ class SConf: # Action after target is successfully built # # No error during build -> remove the recorded error - del self.cache[str(target[0])] + del self.cache[target[0].get_path()] + + 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")))) + cacheDesc = cPickle.load(open(self.confdir.File(".cache").get_path())) if cacheDesc['scons_version'] != SCons.__version__: raise Exception, "version mismatch" self.cache = cacheDesc['data'] except: self.cache = {} - #SCons.Warnings.warn( SConfWarning, - # "Couldn't load SConf cache (assuming empty)" ) def _dumpCache(self): # 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: - SCons.Warnings.warn( SConfWarning, - "Couldn't dump SConf cache" ) - - def createDir(self, node): - if not node.up().exists(): - self.createDir( node.up() ) - if not node.exists(): - SCons.Node.FS.Mkdir(node, None, self.env) + cPickle.dump(cacheDesc, open(self.confdir.File(".cache").get_path(),"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 = node.get_path() + if not os.path.isdir( dirName ): + os.makedirs( dirName ) node._exists = 1 def _startup(self): @@ -364,23 +377,13 @@ class SConf: """ global _ac_config_counter global _activeSConfObjects - - #def createDir( node, self = self ): - # if not node.up().exists(): - # createDir( node.up() ) - # if not node.exists(): - # SCons.Node.FS.Mkdir(node, None, self.env) - # node._exists = 1 - self.createDir(self.confdir) - # we don't want scons to build targets confdir automatically - # cause we are doing it 'by hand' + global SConfFS + + self.lastEnvFs = self.env.fs + self.env.fs = SConfFS + self._createDir(self.confdir) self.confdir.up().add_ignore( [self.confdir] ) - self.confdir.set_state( SCons.Node.up_to_date ) - - self.no_pipe_env = self.env.Copy() - # piped spawn will print its own actions (CHANGE THIS!) - SCons.Action.print_actions = 0 if self.logfile != None: # truncate logfile, if SConf.Configure is called for the first time # in a build @@ -388,7 +391,7 @@ class SConf: log_mode = "w" else: log_mode = "a" - self.logstream = open(str(self.logfile), log_mode) + self.logstream = open(self.logfile.get_path(), 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 @@ -396,10 +399,17 @@ class SConf: self.env['PIPE_BUILD'] = 1 self.env['PSTDOUT'] = self.logstream self.env['PSTDERR'] = self.logstream - else: + + tb = traceback.extract_stack()[-3] + + self.logstream.write( '\nfile %s,line %d:\n\tConfigure( confdir = %s )\n\n' % + (tb[0], tb[1], self.confdir.get_path()) ) + else: self.logstream = None # we use a special builder to create source files from TEXT - action = SCons.Action.Action(_createSource,varlist=['SCONF_TEXT']) + action = SCons.Action.Action(_createSource, + _stringSource, + varlist=['SCONF_TEXT']) sconfSrcBld = SCons.Builder.Builder(action=action) self.env.Append( BUILDERS={'SConfSourceBuilder':sconfSrcBld} ) self.active = 1 @@ -414,8 +424,6 @@ class SConf: if not self.active: raise SCons.Errors.UserError, "Finish may be called only once!" - # Piped Spawn print its own actions. CHANGE THIS! - SCons.Action.print_actions = 1 if self.logstream != None: self.logstream.close() self.logstream = None @@ -430,7 +438,7 @@ class SConf: self.active = 0 del _activeSConfObjects[self] self._dumpCache() - + self.env.fs = self.lastEnvFs class CheckContext: """Provides a context for configure tests. Defines how a test writes to the @@ -507,27 +515,34 @@ class CheckContext: def __getattr__( self, attr ): if( attr == 'env' ): return self.sconf.env + elif( attr == 'lastTarget' ): + return self.sconf.lastTarget else: raise AttributeError, "CheckContext instance has no attribute '%s'" % attr -def CheckCHeader(test, header): +def _header_prog( header, include_quotes ): + return "#include %s%s%s\n\n" % (include_quotes[0], + header, + include_quotes[1]) + +def CheckCHeader(test, header, include_quotes='""'): """ A test for a c header file. """ # ToDo: Support also system header files (i.e. #include ) - test.Message("Checking for C header %s... " % header) - ret = test.TryCompile("#include \"%s\"\n\n" % header, ".c") + test.Message("Checking for C header %s ... " % header) + ret = test.TryCompile(_header_prog(header, include_quotes), ".c") test.Result( ret ) return ret -def CheckCXXHeader(test, header): +def CheckCXXHeader(test, header, include_quotes='""'): """ A test for a c++ header file. """ # ToDo: Support also system header files (i.e. #include ) - test.Message("Checking for C header %s... " % header) - ret = test.TryCompile("#include \"%s\"\n\n" % header, ".cpp") + test.Message("Checking for C++ header %s ... " % header) + ret = test.TryCompile(_header_prog(header, include_quotes), ".cpp") test.Result( ret ) return ret @@ -538,7 +553,7 @@ def CheckLib(test, library=None, symbol="main", autoadd=1): compiles without flags. """ # ToDo: accept path for the library - test.Message("Checking for %s in library %s... " % (symbol, library)) + test.Message("Checking for %s in library %s ... " % (symbol, library)) oldLIBS = test.env.get( 'LIBS', [] ) # NOTE: we allow this at in the case that we don't know what the @@ -590,6 +605,7 @@ def CheckLibWithHeader(test, library, header, language, call="main();", autoadd= #include "%s" int main() { %s + return 0; } """ % (header, call) diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 0b737c05..cd4a31f9 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -23,17 +23,16 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import os import re +import string import StringIO import sys +from types import * import unittest import TestCmd -import SCons.Environment -import SCons.SConf - -scons_env = SCons.Environment.Environment() sys.stdout = StringIO.StringIO() if sys.platform == 'win32': @@ -41,84 +40,125 @@ if sys.platform == 'win32': else: existing_lib = "m" -def clearFileCache(dir): - # mostly from FS.Dir.__clearRepositoryCache, but set also state - # of nodes to None - for node in dir.entries.values(): - if node != dir.dir: - if node != dir and isinstance(node, SCons.Node.FS.Dir): - clearFileCache(node) - else: - node._srcreps = None - del node._srcreps - node._rfile = None - del node._rfile - node._rexists = None - del node._rexists - node._exists = None - del node._exists - node._srcnode = None - del node._srcnode - node.set_state(None) - class SConfTestCase(unittest.TestCase): def setUp(self): - self.test = TestCmd.TestCmd(workdir = '') + # we always want to start with a clean directory + self.test = TestCmd.TestCmd(workdir = '') def tearDown(self): self.test.cleanup() def _resetSConfState(self): - - clearFileCache( SCons.Node.FS.default_fs.Dir(self.test.workpath()) ) - SCons.SConf._ac_config_counter = 0 - SCons.SConf._ac_build_counter = 0 - + # Ok, this is tricky, and i do not know, if everything is sane. + # We try to reset scons' state (including all global variables) + import SCons.Sig + SCons.Sig.write() # simulate normal scons-finish + for n in sys.modules.keys(): + if string.split(n, '.')[0] == 'SCons': + m = sys.modules[n] + if type(m) is ModuleType: + # if this is really a scons module, clear its namespace + del sys.modules[n] + m.__dict__.clear() + # we only use SCons.Environment and SCons.SConf for these tests. + import SCons.Environment + import SCons.SConf + self.Environment = SCons.Environment + self.SConf = SCons.SConf + # and we need a new environment, cause references may point to + # old modules (well, at least this is safe ...) + self.scons_env = self.Environment.Environment() + self.scons_env['ENV']['PATH'] = os.environ['PATH'] + + # we want to do some autodetection here + # this stuff works with + # - cygwin on win32 (using cmd.exe, not bash) + # - posix + # - msvc on win32 (hopefully) + if self.scons_env.subst('$CXX') == 'c++': + # better use g++ (which is normally no symbolic link + # --> the c++ call fails on cygwin + self.scons_env['CXX'] = 'g++' + if self.scons_env.subst('$LINK') == 'c++': + self.scons_env['LINK'] = 'g++' + if (not self.scons_env.Detect( self.scons_env.subst('$CXX') ) or + not self.scons_env.Detect( self.scons_env.subst('$CC') ) or + not self.scons_env.Detect( self.scons_env.subst('$LINK') )): + raise Exception, "This test needs an installed compiler!" + if self.scons_env['LINK'] == 'g++': + global existing_lib + existing_lib = 'm' def _baseTryXXX(self, TryFunc): - def checks(sconf, TryFunc): + # TryCompile and TryLink are much the same, so we can test them + # in one method, we pass the function as a string ('TryCompile', + # 'TryLink'), so we are aware of reloading modules. + + def checks(self, sconf, TryFuncString): + TryFunc = self.SConf.SConf.__dict__[TryFuncString] res1 = TryFunc( sconf, "int main() { return 0; }", ".c" ) - res2 = TryFunc( sconf, "not a c program", ".c" ) + res2 = TryFunc( sconf, + '#include "no_std_header.h"\nint main() {return 0; }', + '.c' ) return (res1,res2) - + + # 1. test initial behaviour (check ok / failed) self._resetSConfState() - sconf = SCons.SConf.SConf(scons_env, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: - res = checks( sconf, TryFunc ) + res = checks( self, sconf, TryFunc ) assert res[0] and not res[1] finally: sconf.Finish() - - # test the caching mechanism + + # 2.1 test the error caching mechanism (no dependencies have changed) self._resetSConfState() - - sconf = SCons.SConf.SConf(scons_env, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: - res = checks( sconf, TryFunc ) + res = checks( self, sconf, TryFunc ) assert res[0] and not res[1] finally: 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 ) firstOcc = expr.match( log ) assert firstOcc != None secondOcc = expr.match( log, firstOcc.end(0) ) - assert secondOcc == None + assert secondOcc == None + + # 2.2 test the error caching mechanism (dependencies have changed) + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + test_h = self.test.write( self.test.workpath('config.tests', 'no_std_header.h'), + "/* we are changing a dependency now */" ); + try: + res = checks( self, sconf, TryFunc ) + log = self.test.read( self.test.workpath('config.log') ) + assert res[0] and res[1] + finally: + sconf.Finish() def test_TryCompile(self): - self._baseTryXXX( SCons.SConf.SConf.TryCompile ) + """Test SConf.TryCompile + """ + self._baseTryXXX( "TryCompile" ) #self.SConf.SConf.TryCompile ) def test_TryLink(self): - self._baseTryXXX( SCons.SConf.SConf.TryLink ) + """Test SConf.TryLink + """ + self._baseTryXXX( "TryLink" ) #self.SConf.SConf.TryLink ) def test_TryRun(self): + """Test SConf.TryRun + """ def checks(sconf): prog = """ #include @@ -132,9 +172,9 @@ int main() { return (res1, res2) self._resetSConfState() - sconf = SCons.SConf.SConf(scons_env, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: res = checks(sconf) assert res[0][0] and res[0][1] == "Hello" @@ -144,17 +184,15 @@ int main() { # test the caching mechanism self._resetSConfState() - - sconf = SCons.SConf.SConf(scons_env, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: res = checks(sconf) assert res[0][0] and res[0][1] == "Hello" assert not res[1][0] and res[1][1] == "" finally: 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 ) @@ -165,13 +203,15 @@ int main() { def test_TryAction(self): + """Test SConf.TryAction + """ def actionOK(target, source, env): open(str(target[0]), "w").write( "RUN OK" ) return None def actionFAIL(target, source, env): return 1 self._resetSConfState() - sconf = SCons.SConf.SConf(scons_env, + sconf = self.SConf.SConf(self.scons_env, conf_dir=self.test.workpath('config.tests'), log_file=self.test.workpath('config.log')) try: @@ -185,13 +225,15 @@ int main() { def test_StandardTests(self): + """Test standard checks + """ def CHeaderChecks( sconf ): - res1 = sconf.CheckCHeader( "stdio.h" ) + res1 = sconf.CheckCHeader( "stdio.h", include_quotes="<>" ) res2 = sconf.CheckCHeader( "HopefullyNotCHeader.noh" ) return (res1,res2) def CXXHeaderChecks(sconf): - res1 = sconf.CheckCXXHeader( "vector" ) + res1 = sconf.CheckCXXHeader( "vector", include_quotes="<>" ) res2 = sconf.CheckCXXHeader( "HopefullyNotCXXHeader.noh" ) return (res1,res2) @@ -236,9 +278,9 @@ int main() { return ((res1, libs1), (res2, libs2)) self._resetSConfState() - sconf = SCons.SConf.SConf(scons_env, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: (res1, res2) = CHeaderChecks(sconf) assert res1 and not res2 @@ -260,7 +302,8 @@ int main() { sconf.Finish() def test_CustomChecks(self): - + """Test Custom Checks + """ def CheckCustom(test): test.Message( "Checking UserTest ... " ) prog = """ @@ -278,10 +321,10 @@ int main() { self._resetSConfState() - sconf = SCons.SConf.SConf(scons_env, - custom_tests={'CheckCustom': CheckCustom}, - conf_dir=self.test.workpath('config.tests'), - log_file=self.test.workpath('config.log')) + sconf = self.SConf.SConf(self.scons_env, + custom_tests={'CheckCustom': CheckCustom}, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) try: ret = sconf.CheckCustom() assert ret @@ -291,6 +334,7 @@ int main() { if __name__ == "__main__": suite = unittest.makeSuite(SConfTestCase, 'test_') - if not unittest.TextTestRunner().run(suite).wasSuccessful (): + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): sys.exit(1) - + diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index cf7a86f0..1832e895 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -47,7 +47,7 @@ except ImportError: default_max_drift = 2*24*60*60 #XXX Get rid of the global array so this becomes re-entrant. -sig_files = [] +sig_files = {} # 1 means use build signature for derived source files # 0 means use content signature for derived source files @@ -55,9 +55,18 @@ build_signature = 1 def write(): global sig_files - for sig_file in sig_files: + for sig_file in sig_files.values(): sig_file.write() + +def SConsignFileFactory( dir, module=None): + try: + return sig_files[dir.path] + except KeyError: + sig_files[dir.path] = SConsignFile(dir, module) + return sig_files[dir.path] + + class SConsignEntry: """Objects of this type are pickled to the .sconsign file, so it @@ -110,9 +119,6 @@ class SConsignFile: SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, "Ignoring corrupt .sconsign file: %s"%self.sconsign) - global sig_files - sig_files.append(self) - def get(self, filename): """ Get the .sconsign entry for a file @@ -279,7 +285,7 @@ class Calculator: bsig = cache.get_bsig() if bsig is not None: return bsig - + sigs = map(lambda n, c=self: n.calc_signature(c), children) if node.has_builder(): sigs.append(self.module.signature(node.get_executor())) diff --git a/test/Configure.py b/test/Configure.py index b78cdcb1..97333ab4 100644 --- a/test/Configure.py +++ b/test/Configure.py @@ -36,23 +36,48 @@ if sys.platform == 'win32': else: lib = 'm' -test = TestSCons.TestSCons() +# to use cygwin compilers on cmd.exe -> uncomment following line +#lib = 'm' + +oldPwd = os.getcwd() python = TestSCons.python +test = None + +def reset(dot = 1): + global test, oldPwd + os.chdir( oldPwd ) + TestSCons.scons = None + test = TestSCons.TestSCons() + if dot == 1: + test.match_func = TestCmd.match_re_dotall + + +def checkFiles(test, files): + for f in files: + test.fail_test( not os.path.isfile( test.workpath(f) ) ) + def checkLog( test, logfile, numUpToDate, numCache ): - test.fail_test(not os.path.exists(test.workpath('config.log'))) + test.fail_test(not os.path.exists(test.workpath(logfile))) log = test.read(test.workpath(logfile)) - 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 ) - + 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(logfile), "\n", log + raise try: # 1.1 if checks are ok, the cache mechanism should work + reset(dot=0) + test.write( 'SConstruct', """ env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] conf = Configure(env) r1 = conf.CheckLibWithHeader( '%s', 'math.h', 'c' ) r2 = conf.CheckLibWithHeader( None, 'math.h', 'c' ) @@ -63,16 +88,16 @@ r6 = conf.CheckCXXHeader( 'vector' ) env = conf.Finish() if not (r1 and r2 and r3 and r4 and r5 and r6): Exit(1) -""" % (lib, lib)) +""" % (lib,lib)) required_stdout = test.wrap_stdout(build_str='scons: "." is up to date.\n', read_str= """Checking for main(); in library %s (header math.h) ... ok Checking for main(); in library None (header math.h) ... ok -Checking for main in library %s... ok -Checking for main in library None... ok -Checking for C header math.h... ok -Checking for C header vector... ok +Checking for main in library %s ... ok +Checking for main in library None ... ok +Checking for C header math.h ... ok +Checking for C++ header vector ... ok """ % (lib, lib)) test.run(stdout = required_stdout) @@ -83,9 +108,13 @@ Checking for C header vector... ok # 1.2 if checks are not ok, the cache mechanism should work as well # (via explicit cache) + reset() + test.write( 'SConstruct', """ env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] conf = Configure(env) r1 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error r2 = conf.CheckLib( 'no_c_library_SAFFDG' ) # leads to link error @@ -97,8 +126,8 @@ if not (not r1 and not r2): required_stdout = test.wrap_stdout(build_str='scons: "." is up to date.\n', read_str= - """Checking for C header no_std_c_header.h... failed -Checking for main in library no_c_library_SAFFDG... failed + """Checking for C header no_std_c_header.h ... failed +Checking for main in library no_c_library_SAFFDG ... failed """) test.run(stdout = required_stdout) @@ -109,8 +138,13 @@ Checking for main in library no_c_library_SAFFDG... failed # 2.1 test that normal builds work together with Sconf + reset() + + test.write( 'SConstruct', """ env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] conf = Configure(env) r1 = conf.CheckCHeader( 'math.h' ) r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error @@ -132,8 +166,8 @@ int main() { test.match_func = TestCmd.match_re_dotall required_stdout = test.wrap_stdout(build_str='.*', read_str= - """Checking for C header math.h... ok -Checking for C header no_std_c_header.h... failed + """Checking for C header math.h ... ok +Checking for C header no_std_c_header.h ... failed """) test.run( stdout = required_stdout ) checkLog( test, 'config.log', 0, 0 ) @@ -143,8 +177,13 @@ Checking for C header no_std_c_header.h... failed # 2.2 test that BuildDir builds work together with Sconf + reset() + + test.write( 'SConstruct', """ env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] BuildDir( 'build', '.' ) conf = Configure(env, conf_dir='build/config.tests', log_file='build/config.log') r1 = conf.CheckCHeader( 'math.h' ) @@ -167,8 +206,8 @@ int main() { """) required_stdout = test.wrap_stdout(build_str='.*', read_str= - """Checking for C header math.h... ok -Checking for C header no_std_c_header.h... failed + """Checking for C header math.h ... ok +Checking for C header no_std_c_header.h ... failed """) test.run( stdout = required_stdout ) checkLog( test, 'build/config.log', 0, 0 ) @@ -176,26 +215,64 @@ Checking for C header no_std_c_header.h... failed test.run( stdout = required_stdout ) checkLog( test, 'build/config.log', 3, 1 ) + # 2.3 test that Configure calls in SConscript files work + # even if BuildDir is set + reset() + + + test.subdir( 'sub' ) + test.write( 'SConstruct', """ +BuildDir( 'build', '.' ) +SConscript( 'build/SConscript' ) +""") + test.write( 'SConscript', """ +SConscript( 'sub/SConscript' ) +""") + test.write( 'sub/SConscript', """ +env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] +conf = Configure( env ) +conf.CheckCHeader( 'math.h' ) +env = conf.Finish() +env.Program( 'TestProgram', 'TestProgram.c' ) +""") + test.write( 'sub/TestProgram.h', """ +/* Just a test header */ +""") + test.write( 'sub/TestProgram.c', """ +#include "TestProgram.h" +#include + +int main() { + printf( "Hello\\n" ); +} +""") + test.run() + checkFiles( test, [".sconf_temp/.cache", "config.log"] ) # 3.1 test custom tests + reset() + + compileOK = '#include \\nint main() {printf("Hello");return 0;}' compileFAIL = "syntax error" linkOK = compileOK linkFAIL = "void myFunc(); int main() { myFunc(); }" runOK = compileOK runFAIL = "int main() { return 1; }" - test.write( 'pyAct.py', 'import sys\nopen(sys.argv[1], "w").write(sys.argv[2] + "\\n"),\nsys.exit(int(sys.argv[2]))\n' ) - test.write( 'SConstruct', """ + test.write('pyAct.py', 'import sys\nprint sys.argv[1]\nsys.exit(int(sys.argv[1]))\n') + test.write('SConstruct', """ def CheckCustom(test): - test.Message( 'Executing MyTest...' ) + test.Message( 'Executing MyTest ... ' ) retCompileOK = test.TryCompile( '%s', '.c' ) retCompileFAIL = test.TryCompile( '%s', '.c' ) retLinkOK = test.TryLink( '%s', '.c' ) retLinkFAIL = test.TryLink( '%s', '.c' ) (retRunOK, outputRunOK) = test.TryRun( '%s', '.c' ) (retRunFAIL, outputRunFAIL) = test.TryRun( '%s', '.c' ) - (retActOK, outputActOK) = test.TryAction( '%s pyAct.py $TARGET 0' ) - (retActFAIL, outputActFAIL) = test.TryAction( '%s pyAct.py $TARGET 1' ) + (retActOK, outputActOK) = test.TryAction( '%s pyAct.py 0 > $TARGET' ) + (retActFAIL, outputActFAIL) = test.TryAction( '%s pyAct.py 1 > $TARGET' ) resOK = retCompileOK and retLinkOK and retRunOK and outputRunOK=="Hello" resOK = resOK and retActOK and int(outputActOK)==0 resFAIL = retCompileFAIL or retLinkFAIL or retRunFAIL or outputRunFAIL!="" @@ -204,13 +281,15 @@ def CheckCustom(test): return resOK and not resFAIL env = Environment() +import os +env['ENV']['PATH'] = os.environ['PATH'] conf = Configure( env, custom_tests={'CheckCustom' : CheckCustom} ) 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") + read_str="Executing MyTest ... ok\n") test.run(stdout = required_stdout) checkLog( test, 'config.log', 0, 0 )