summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-04-23 03:54:48 (GMT)
committerSteven Knight <knight@baldmt.com>2003-04-23 03:54:48 (GMT)
commit6a62c2995bb3d60005b839a5c5a1285ecd5aa859 (patch)
treef3d9a137283b24eb6202f5ee13bb79f0075ef1d5
parent78d494e47d685a52b47ba9e071025be152086c74 (diff)
downloadSCons-6a62c2995bb3d60005b839a5c5a1285ecd5aa859.zip
SCons-6a62c2995bb3d60005b839a5c5a1285ecd5aa859.tar.gz
SCons-6a62c2995bb3d60005b839a5c5a1285ecd5aa859.tar.bz2
Add SConf infrastructure (Autoconf functionality). (Chrisoph Wiedemann)
-rw-r--r--doc/man/scons.1277
-rw-r--r--src/CHANGES.txt12
-rw-r--r--src/engine/MANIFEST.in1
-rw-r--r--src/engine/SCons/Action.py30
-rw-r--r--src/engine/SCons/ActionTests.py60
-rw-r--r--src/engine/SCons/Platform/posix.py121
-rw-r--r--src/engine/SCons/Platform/win32.py50
-rw-r--r--src/engine/SCons/SConf.py609
-rw-r--r--src/engine/SCons/SConfTests.py296
-rw-r--r--src/engine/SCons/Script/SConscript.py2
-rw-r--r--test/Configure.py228
11 files changed, 1662 insertions, 24 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index 18c5d80..91afc37 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -2868,6 +2868,283 @@ method:
env2 = env.Copy(CC="cl.exe")
.EE
+.SS Configure contexts
+
+.B scons
+supports
+.I configure contexts,
+an integrated mechanism similar to the
+various AC_CHECK macros in GNU autoconf
+for testing for the existence of C header
+files, libraries, etc.
+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.
+The following methods can be used to perform checks:
+
+.TP
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ])
+This creates a configure context, which can be used to perform checks.
+.I env
+specifies the environment for building the tests.
+This environment may be modified when performing checks.
+.I custom_tests
+is a dictionary containing custom tests.
+See also the section about custom tests below.
+By default, no custom tests are added to the configure context.
+.I conf_dir
+specifies a directory where the test cases are built.
+Note that this directory is not used for building
+normal targets.
+The default value is the directory
+#/.sconf_temp.
+.I log_file
+specifies a file which collects the output from commands
+that are executed to check for the existence of header files, libraries, etc.
+The default is the file #/config.log.
+If you are using the
+.B BuildDir
+method,
+you may want to specify a subdirectory under your build directory.
+
+.EE
+A created
+.B Configure
+instance has the following associated methods:
+
+.TP
+.RI Configure.Finish( self )
+This method should be called after configuration is done.
+It returns the environment as modified
+by the configuration checks performed.
+After this method is called, no further checks can be performed
+with this configuration context.
+However, you can create a new
+.RI Configure
+context to perform additional checks.
+Only one context should be active at a time.
+
+The following Checks are predefined.
+(This list will likely grow larger as time
+goes by and developers contribute new useful tests.)
+
+.TP
+.RI Configure.CheckCHeader( self ", " header )
+Checks if
+.I header
+is usable in the C-language.
+Returns 1 on success and 0 on failure.
+
+.TP
+.RI Configure.CheckCXXHeader( self ", " header )
+Checks if
+.I header
+is usable in the C++ language.
+Returns 1 on success and 0 on failure.
+
+.TP
+.RI Configure.CheckLib( self ", [" library ", " symbol ", " autoadd ])
+Checks if
+.I library
+provides
+.IR symbol .
+If the value of
+.I autoadd
+is 1 and the library provides the specified
+.IR symbol ,
+appends the library to the LIBS construction environment variable.
+.I library
+may also be None (the default),
+in which case
+.I symbol
+is checked with the current LIBS variable.
+The default
+.I symbol
+is "main",
+which just check if
+you can link against the specified
+.IR library .
+The default value for
+.I autoadd
+is 1.
+It is assumed, that the C-language is used.
+This method returns 1 on success and 0 on error.
+
+.TP
+.RI Configure.CheckLibWithHeader( self ", " library ", " header ", " language ", [" call ", " autoadd ])
+
+In contrast to the
+.RI Configure.CheckLib
+call, this call provides a more sophisticated way to check against libraries.
+Again,
+.I library
+specifies the library to check.
+.I header
+specifies a header to check for.
+.I language
+may be one of 'C','c','CXX','cxx','C++' and 'c++'.
+.I call
+can be any valid expression (with a trailing ';'). The default is 'main();'.
+.I autoadd
+specifies whether to add the library to the environment (only if the check
+succeeds). This method returns 1 on success and 0 on error.
+
+.EE
+Example of a typical Configure usage:
+
+.ES
+env = Environment()
+conf = Configure( env )
+if not conf.CheckCHeader( 'math.h' ):
+ print 'We really need math.h!'
+ Exit(1)
+if conf.CheckLibWithHeader( 'qt', 'qapp.h', 'c++', 'QApplication qapp(0,0);' ):
+ # do stuff for qt - usage, e.g.
+ conf.env.Append( CPPFLAGS = '-DWITH_QT' )
+env = conf.Finish()
+.EE
+
+.EE
+You can define your own custom checks.
+in addition to the predefined checks.
+These are passed in a dictionary to the Configure function.
+This dictionary maps the names of the checks
+to user defined Python callables
+(either Python functions or class instances implementing the
+.I __call__
+method).
+The first argument of the call is always a
+.I CheckContext
+instance followed by the arguments,
+which must be supplied by the user of the check.
+These CheckContext instances define the following methods:
+
+.TP
+.RI CheckContext.Message( self ", " text )
+
+Usually called before the check is started.
+.I text
+will be displayed to the user, e.g. 'Checking for library X...'
+
+.TP
+.RI CheckContext.Result( self, ", " res )
+
+Usually called after the check is done.
+.I res
+can be either an integer or a string. In the former case, 'ok' (res != 0)
+or 'failed' (res == 0) is displayed to the user, in the latter case the
+given string is displayed.
+
+.TP
+.RI CheckContext.TryCompile( self ", " text ", " extension )
+Checks if a file with the specified
+.I extension
+(e.g. '.c') containing
+.I text
+can be compiled using the environment's
+.B Object
+builder. Returns 1 on success and 0 on failure.
+
+.TP
+.RI CheckContext.TryLink( self ", " text ", " extension )
+Checks, if a file with the specified
+.I extension
+(e.g. '.c') containing
+.I text
+can be compiled using the environment's
+.B Program
+builder. Returns 1 on success and 0 on failure.
+
+.TP
+.RI CheckContext.TryRun( self ", " text ", " extension )
+Checks, if a file with the specified
+.I extension
+(e.g. '.c') containing
+.I text
+can be compiled using the environment's
+.B Program
+builder. On success, the program is run. If the program
+executes successfully
+(that is, its return status is 0),
+a tuple
+.I (1, outputStr)
+is returned, where
+.I outputStr
+is the standard output of the
+program.
+If the program fails execution
+(its return status is non-zero),
+then (0, '') is returned.
+
+.TP
+.RI CheckContext.TryAction( self ", " action ", [" text ", " extension ])
+Checks if the specified
+.I action
+with an optional source file (contents
+.I text
+, extension
+.I extension
+= ''
+) can be executed.
+.I action
+may be anything which can be converted to a
+.B scons
+.RI Action.
+On success,
+.I (1, outputStr)
+is returned, where
+.I outputStr
+is the content of the target file.
+On failure
+.I (0, '')
+is returned.
+
+.TP
+.RI CheckContext.TryBuild( self ", " builder ", [" text ", " extension ])
+Low level implementation for testing specific builds;
+the methods above are based on this metho.
+Given the Builder instance
+.I builder
+and the optional
+.I text
+of a source file with optional
+.IR extension ,
+this method returns 1 on success and 0 on failure. In addition,
+.I self.lastTarget
+is set to the build target node, if the build was successful.
+
+.EE
+Example for implementing and using custom tests:
+
+.ES
+def CheckQt(context, qtdir):
+ context.Message( 'Checking for qt ...' )
+ lastLIBS = context.env['LIBS']
+ lastLIBPATH = context.env['LIBPATH']
+ lastCPPPATH= context.env['CPPPATH']
+ context.env.Append(LIBS = 'qt', LIBPATH = qtdir + '/lib', CPPPATH = qtdir + '/include' )
+ ret = context.TryLink("""
+#include <qapp.h>
+int main(int argc, char **argv) {
+ QApplication qapp(argc, argv);
+ return 0;
+}
+"""
+ if not ret:
+ context.env.Replace(LIBS = lastLIBS, LIBPATH=lastLIBPATH, CPPPATH=lastCPPPATH)
+ context.Result( ret )
+ return ret
+
+env = Environment()
+conf = Configure( env, custom_tests = 'CheckQt' : CheckQt )
+if not conf.CheckQt('/usr/lib/qt'):
+ print 'We really need qt!'
+ Exit(1)
+env = conf.Finish()
+.EE
+
.SS Construction Variable Options
Often when building software, various options need to be specified at build
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index f6f2746..10cf7b3 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -75,10 +75,22 @@ RELEASE 0.14 - XXX
- Support Import('*') to import everything that's been Export()ed.
+ From David Snopek:
+
+ - Contribute the "Autoscons" code for Autoconf-like checking for
+ the existence of libraries, header files and the like.
+
From Greg Spencer:
- Support the C preprocessor #import statement.
+ From Christoph Wiedemann:
+
+ - Integrate David Snopek's "Autoscons" code as the new SConf
+ configuration subsystem, including caching of values between
+ runs (using normal SCons dependency mechanisms), tests, and
+ documentation.
+
RELEASE 0.13 - Mon, 31 Mar 2003 20:22:00 -0600
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in
index 02646ac..251b48a 100644
--- a/src/engine/MANIFEST.in
+++ b/src/engine/MANIFEST.in
@@ -24,6 +24,7 @@ SCons/Scanner/__init__.py
SCons/Scanner/C.py
SCons/Scanner/Fortran.py
SCons/Scanner/Prog.py
+SCons/SConf.py
SCons/Script/SConscript.py
SCons/Script/__init__.py
SCons/Sig/__init__.py
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 45f4c98..9ad925a 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -210,10 +210,28 @@ class CommandAction(ActionBase):
else:
raise SCons.Errors.UserError('Missing SHELL construction variable.')
- if env.has_key('SPAWN'):
- spawn = env['SPAWN']
+ # for SConf support (by now): check, if we want to pipe the command
+ # output to somewhere else
+ if env.has_key('PIPE_BUILD'):
+ pipe_build = 1
+ if env.has_key('PSPAWN'):
+ pspawn = env['PSPAWN']
+ else:
+ raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
+ if env.has_key('PSTDOUT'):
+ pstdout = env['PSTDOUT']
+ else:
+ raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
+ if env.has_key('PSTDERR'):
+ pstderr = env['PSTDERR']
+ else:
+ raise SCons.Errors.UserError('Missing PSTDOUT construction variable.')
else:
- raise SCons.Errors.UserError('Missing SPAWN construction variable.')
+ pipe_build = 0
+ if env.has_key('SPAWN'):
+ spawn = env['SPAWN']
+ else:
+ raise SCons.Errors.UserError('Missing SPAWN construction variable.')
cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm,
target, source)
@@ -234,7 +252,11 @@ class CommandAction(ActionBase):
# interpreter we are using
map(lambda x, e=escape: x.escape(e), cmd_line)
cmd_line = map(str, cmd_line)
- ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
+ if pipe_build:
+ ret = pspawn( shell, escape, cmd_line[0], cmd_line,
+ ENV, pstdout, pstderr )
+ else:
+ ret = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
if ret:
return ret
return 0
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index eedc865..fb77fe6 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -31,6 +31,7 @@ def Func():
pass
import os
+import re
import StringIO
import sys
import types
@@ -61,6 +62,9 @@ try:
except:
pass
f.close()
+if os.environ.has_key( 'ACTPY_PIPE' ):
+ sys.stdout.write( 'act.py: stdout: executed act.py\\n' )
+ sys.stderr.write( 'act.py: stderr: executed act.py\\n' )
sys.exit(0)
""")
@@ -80,6 +84,7 @@ class Environment:
self.d = {}
self.d['SHELL'] = scons_env['SHELL']
self.d['SPAWN'] = scons_env['SPAWN']
+ self.d['PSPAWN'] = scons_env['PSPAWN']
self.d['ESCAPE'] = scons_env['ESCAPE']
for k, v in kw.items():
self.d[k] = v
@@ -94,6 +99,8 @@ class Environment:
return self.d.get(s, s)
def __getitem__(self, item):
return self.d[item]
+ def __setitem__(self, item, value):
+ self.d[item] = value
def has_key(self, item):
return self.d.has_key(item)
def get(self, key, value):
@@ -102,6 +109,12 @@ class Environment:
return self.d.items()
def Dictionary(self):
return self.d
+ def Copy(self, **kw):
+ res = Environment()
+ res.d = SCons.Environment.our_deepcopy(self.d)
+ for k, v in kw.items():
+ res.d[k] = v
+ return res
def sig_dict(self):
d = {}
for k,v in self.items(): d[k] = v
@@ -348,10 +361,15 @@ class CommandActionTestCase(unittest.TestCase):
"""Test execution of command Actions
"""
+ try:
+ env = self.env
+ except AttributeError:
+ env = Environment()
+
cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
act = SCons.Action.CommandAction(cmd1)
- r = act([], [], Environment())
+ r = act([], [], env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'xyzzy'\n", c
@@ -359,7 +377,7 @@ class CommandActionTestCase(unittest.TestCase):
cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
act = SCons.Action.CommandAction(cmd2)
- r = act('foo', [], Environment())
+ r = act('foo', [], env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'foo'\n", c
@@ -367,7 +385,7 @@ class CommandActionTestCase(unittest.TestCase):
cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
act = SCons.Action.CommandAction(cmd3)
- r = act(['aaa', 'bbb'], [], Environment())
+ r = act(['aaa', 'bbb'], [], env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'aaa' 'bbb'\n", c
@@ -375,7 +393,7 @@ class CommandActionTestCase(unittest.TestCase):
cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
act = SCons.Action.CommandAction(cmd4)
- r = act([], ['one', 'two'], Environment())
+ r = act([], ['one', 'two'], env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'one' 'two'\n", c
@@ -385,7 +403,7 @@ class CommandActionTestCase(unittest.TestCase):
act = SCons.Action.CommandAction(cmd4)
r = act([],
source = ['three', 'four', 'five'],
- env = Environment())
+ env = env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'three' 'four'\n", c
@@ -395,7 +413,7 @@ class CommandActionTestCase(unittest.TestCase):
act = SCons.Action.CommandAction(cmd5)
r = act(target = 'out5',
source = [],
- env = Environment(ENV = {'XYZZY' : 'xyzzy'}))
+ env = env.Copy(ENV = {'XYZZY' : 'xyzzy'}))
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
@@ -411,7 +429,7 @@ class CommandActionTestCase(unittest.TestCase):
act = SCons.Action.CommandAction(cmd6)
r = act(target = [Obj('111'), Obj('222')],
source = [Obj('333'), Obj('444'), Obj('555')],
- env = Environment())
+ env = env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: '222' '111' '333' '444'\n", c
@@ -423,14 +441,14 @@ class CommandActionTestCase(unittest.TestCase):
act = SCons.Action.CommandAction(cmd7)
- global show_string
+ global show_string
show_string = ""
def my_show(string):
global show_string
show_string = show_string + string + "\n"
act.show = my_show
- r = act([], [], Environment())
+ r = act([], [], env.Copy())
assert r == 0
assert show_string == expect7, show_string
@@ -445,20 +463,38 @@ class CommandActionTestCase(unittest.TestCase):
# Test that a nonexistent command returns 127
act = SCons.Action.CommandAction(python + "_XyZzY_")
- r = act([], [], Environment(out = outfile))
+ r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexistent, "r == %d" % r
# Test that trying to execute a directory returns 126
dir, tail = os.path.split(python)
act = SCons.Action.CommandAction(dir)
- r = act([], [], Environment(out = outfile))
+ r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexecutable, "r == %d" % r
# Test that trying to execute a non-executable file returns 126
act = SCons.Action.CommandAction(outfile)
- r = act([], [], Environment(out = outfile))
+ r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexecutable, "r == %d" % r
+
+ def test_pipe_execute(self):
+ """Test capturing piped output from an action
+ """
+ pipe_file = open( test.workpath('pipe.out'), "w" )
+ self.env = Environment(ENV = {'ACTPY_PIPE' : '1'}, PIPE_BUILD = 1,
+ PSTDOUT = pipe_file, PSTDERR = pipe_file)
+ # everything should also work when piping output
+ self.test_execute()
+ self.env['PSTDOUT'].close()
+ pipe_out = test.read( test.workpath('pipe.out') )
+ 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 )
+ assert len(found) == 8, found
+
def test_set_handler(self):
"""Test setting the command handler...
"""
diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py
index 777b928..fd78de4 100644
--- a/src/engine/SCons/Platform/posix.py
+++ b/src/engine/SCons/Platform/posix.py
@@ -32,11 +32,13 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import SCons.Util
-import string
import os
-import sys
import os.path
+import popen2
+import string
+import sys
+
+import SCons.Util
def escape(arg):
"escape shell special characters"
@@ -49,7 +51,7 @@ def escape(arg):
return '"' + arg + '"'
-def env_spawn(sh, escape, cmd, args, env):
+def _get_env_command(sh, escape, cmd, args, env):
if env:
s = 'env -i '
for key in env.keys():
@@ -58,7 +60,10 @@ def env_spawn(sh, escape, cmd, args, env):
s = s + escape(string.join(args))
else:
s = string.join(args)
+ return s
+def env_spawn(sh, escape, cmd, args, env):
+ s = _get_env_command( sh, escape, cmd, args, env)
stat = os.system(s)
if stat & 0xff:
return stat | 0x80
@@ -82,7 +87,110 @@ def fork_spawn(sh, escape, cmd, args, env):
if stat & 0xff:
return stat | 0x80
return stat >> 8
-
+
+def piped_env_spawn(sh, escape, cmd, args, env, stdout, stderr):
+ # spawn using Popen3 combined with the env command
+ # 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)
+ # 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)
+ 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
+ if stdout != stderr:
+ (rFdOut, wFdOut) = os.pipe()
+ (rFdErr, wFdErr) = os.pipe()
+ else:
+ (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:
+ # Child process
+ os.close( rFdOut )
+ if rFdOut != rFdErr:
+ os.close( rFdErr )
+ os.dup2( wFdOut, 1 ) # is there some symbolic way to do that ?
+ os.dup2( wFdErr, 2 )
+ os.close( wFdOut )
+ if stdout != stderr:
+ os.close( wFdErr )
+ exitval = 127
+ args = [sh, '-c', string.join(args)]
+ try:
+ os.execvpe(sh, args, env)
+ except OSError, e:
+ exitval = exitvalmap[e[0]]
+ stderr.write("scons: %s: %s\n" % (cmd, e[1]))
+ os._exit(exitval)
+ else:
+ # Parent process
+ pid, stat = os.waitpid(pid, 0)
+ os.close( wFdOut )
+ if stdout != stderr:
+ os.close( wFdErr )
+ childOut = os.fdopen( rFdOut )
+ if stdout != stderr:
+ childErr = os.fdopen( rFdErr )
+ else:
+ 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)
+ # 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)
+ os.close( rFdOut )
+ if stdout != stderr:
+ os.close( rFdErr )
+ if stat & 0xff:
+ return stat | 0x80
+ return stat >> 8
+
+
+
def generate(env):
# If the env command exists, then we can use os.system()
@@ -91,8 +199,10 @@ def generate(env):
# threads (i.e. -j) and is more efficient than forking Python.
if env.Detect('env'):
spawn = env_spawn
+ pspawn = piped_env_spawn
else:
spawn = fork_spawn
+ pspawn = piped_fork_spawn
if not env.has_key('ENV'):
env['ENV'] = {}
@@ -109,6 +219,7 @@ def generate(env):
env['SHLIBSUFFIX'] = '.so'
env['LIBPREFIXES'] = '$LIBPREFIX'
env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
+ env['PSPAWN'] = pspawn
env['SPAWN'] = spawn
env['SHELL'] = 'sh'
env['ESCAPE'] = escape
diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py
index 4286e24..d97e61e 100644
--- a/src/engine/SCons/Platform/win32.py
+++ b/src/engine/SCons/Platform/win32.py
@@ -32,11 +32,14 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import SCons.Util
import os
import os.path
+import popen2
import string
import sys
+import tempfile
+
+import SCons.Util
class TempFileMunge:
"""A callable class. You can set an Environment variable to this,
@@ -57,8 +60,6 @@ class TempFileMunge:
(reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048:
return self.cmd
else:
- import tempfile
-
# In Cygwin, we want to use rm to delete the temporary file,
# because del does not exist in the sh shell.
rm = env.Detect('rm') or 'del'
@@ -84,6 +85,48 @@ class TempFileMunge:
# you had better have cmd or command.com in your PATH when you run
# scons.
+def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
+ 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")
+ 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]]
+ stderr.write("scons: %s: %s\n" % (cmd, e[1]))
+ 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 ??
+ pass
+ return ret
+
def spawn(sh, escape, cmd, args, env):
if not sh:
sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
@@ -157,6 +200,7 @@ def generate(env):
env['SHLIBSUFFIX'] = '.dll'
env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX' ]
env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
+ env['PSPAWN'] = piped_spawn
env['SPAWN'] = spawn
env['SHELL'] = cmd_interp
env['TEMPFILE'] = TempFileMunge
diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py
new file mode 100644
index 0000000..64cd5f9
--- /dev/null
+++ b/src/engine/SCons/SConf.py
@@ -0,0 +1,609 @@
+"""SCons.SConf
+
+Autoconf-like configuration support.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import cPickle
+import os
+import shutil
+import sys
+from types import *
+
+import SCons.Action
+import SCons.Builder
+import SCons.Errors
+import SCons.Node.FS
+import SCons.Taskmaster
+import SCons.Util
+import SCons.Warnings
+
+_ac_build_counter = 0
+_ac_config_counter = 0
+_activeSConfObjects = {}
+
+class SConfWarning(SCons.Warnings.Warning):
+ pass
+SCons.Warnings.enableWarningClass( SConfWarning )
+
+
+def _createSource( target, source, env ):
+ fd = open(str(target[0]), "w")
+ fd.write(env['SCONF_TEXT'])
+ fd.close()
+
+
+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
+ tests, be sure to call the Finish() method, which returns the modified
+ environment.
+ Some words about caching: In most cases, it is not necessary to cache
+ Test results explicitely. Instead, we use the scons dependency checking
+ mechanism. For example, if one wants to compile a test program
+ (SConf.TryLink), the compiler is only called, if the program dependencies
+ have changed. However, if the program could not be compiled in a former
+ SConf run, we need to explicitely cache this error.
+ """
+
+ def __init__(self, env, custom_tests = {}, conf_dir='#/.sconf_temp',
+ 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)
+ """
+ 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)
+ else:
+ self.logfile = None
+ self.logstream = None
+ self.lastTarget = None
+
+ # add default tests
+ default_tests = {
+ 'CheckCHeader' : CheckCHeader,
+ 'CheckCXXHeader' : CheckCXXHeader,
+ 'CheckLib' : CheckLib,
+ 'CheckLibWithHeader' : CheckLibWithHeader
+ }
+ self.AddTests(default_tests)
+ self.AddTests(custom_tests)
+ self.confdir = SCons.Node.FS.default_fs.Dir(conf_dir)
+ self.cache = {}
+ 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))
+ n.add_post_action(SCons.Action.Action(self._postCache))
+ already_done.append( n )
+ self.setCache(n.children())
+
+ def BuildNodes(self, nodes):
+ """
+ Tries to build the given nodes immediately. Returns 1 on success,
+ 0 on error.
+ """
+
+ 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
+
+ if self.logstream != None:
+ # override stdout / stderr to write in log file
+ oldStdout = sys.stdout
+ sys.stdout = self.logstream
+ oldStderr = sys.stderr
+ sys.stderr = self.logstream
+
+ 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)
+ tm = SCons.Taskmaster.Taskmaster( nodes,
+ SConfBuildTask,
+ calc )
+ # we don't want to build tests in parallel
+ jobs = SCons.Job.Jobs(1, tm )
+ try:
+ jobs.run()
+ except:
+ pass
+
+
+ for n in nodes:
+ state = n.get_state()
+ if (state != SCons.Node.executed and
+ 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
+ sys.stdout = oldStdout
+ sys.stderr = oldStderr
+ return ret
+
+
+ def TryBuild(self, builder, text = None, extension = ""):
+ """Low level TryBuild implementation. Normally you don't need to
+ call that - you can use TryCompile / TryLink / TryRun instead
+ """
+ global _ac_build_counter
+
+ nodesToBeBuilt = []
+
+ #target = self.confdir.File("conftest_" + str(_ac_build_counter))
+ f = "conftest_" + str(_ac_build_counter)
+ target = os.path.join(str(self.confdir), f)
+ if text != None:
+ source = self.confdir.File(f + extension)
+ sourceNode = self.env.SConfSourceBuilder(target=source,
+ source=None)
+ nodesToBeBuilt.append(sourceNode)
+ else:
+ source = None
+ self.env['SCONF_TEXT'] = text
+
+ node = builder(target = target, source = source)
+ nodesToBeBuilt.append(node)
+ ret = self.BuildNodes(nodesToBeBuilt)
+
+ del self.env['SCONF_TEXT']
+
+ _ac_build_counter = _ac_build_counter + 1
+ if ret:
+ self.lastTarget = node
+ else:
+ self.lastTarget = None
+
+ return ret
+
+ def TryAction(self, action, text = None, extension = ""):
+ """Tries to execute the given action with optional source file
+ contents <text> and optional source file extension <extension>,
+ Returns the status (0 : failed, 1 : ok) and the contents of the
+ output file.
+ """
+ builder = SCons.Builder.Builder(action=action)
+ self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} )
+ ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
+ del self.env['BUILDERS']['SConfActionBuilder']
+ if ok:
+ outputStr = self.lastTarget.get_contents()
+ return (1, outputStr)
+ return (0, "")
+
+ def TryCompile( self, text, extension):
+ """Compiles the program given in text to an env.Object, using extension
+ as file extension (e.g. '.c'). Returns 1, if compilation was
+ successful, 0 otherwise. The target is saved in self.lastTarget (for
+ further processing).
+ """
+ return self.TryBuild(self.env.Object, text, extension)
+
+ def TryLink( self, text, extension ):
+ """Compiles the program given in text to an executable env.Program,
+ using extension as file extension (e.g. '.c'). Returns 1, if
+ 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
+ as file extension (e.g. '.c'). Returns (1, outputStr) on success,
+ (0, '') otherwise. The target (a file containing the program's stdout)
+ is saved in self.lastTarget (for further processing).
+ """
+ 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)))
+ ok = self.BuildNodes([node])
+ if ok:
+ outputStr = output.get_contents()
+ return( 1, outputStr)
+ return (0, "")
+
+ class TestWrapper:
+ """A wrapper around Tests (to ensure sanity)"""
+ def __init__(self, test, sconf):
+ self.test = test
+ self.sconf = sconf
+ def __call__(self, *args, **kw):
+ if not self.sconf.active:
+ raise (SCons.Errors.UserError,
+ "Test called after sconf.Finish()")
+ context = CheckContext(self.sconf)
+ ret = apply(self.test, (context,) + args, kw)
+ context.Result("error: no result")
+ return ret
+
+ def AddTest(self, test_name, test_instance):
+ """Adds test_class to this SConf instance. It can be called with
+ self.test_name(...)"""
+ setattr(self, test_name, SConf.TestWrapper(test_instance, self))
+
+ def AddTests(self, tests):
+ """Adds all the tests given in the tests dictionary to this SConf
+ instance
+ """
+ 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].builder.get_contents(target, source, env)
+ 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]) ):
+ # We have no recorded error, so we try to build the target
+ needs_rebuild = 1
+ else:
+ lastBuildSig = self.cache[str(target[0])]['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]))
+ return 1
+ else:
+ # Otherwise, we try to record an error
+ self.cache[str(target[0])] = {
+ '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 _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:
+ 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)
+ node._exists = 1
+
+ def _startup(self):
+ """Private method. Set up logstream, and set the environment
+ variables necessary for a piped build
+ """
+ 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'
+ 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
+ if _ac_config_counter == 0:
+ log_mode = "w"
+ else:
+ log_mode = "a"
+ self.logstream = open(str(self.logfile), log_mode)
+ # logfile may stay in a build directory, so we tell
+ # the build system not to override it with a eventually
+ # existing file with the same name in the source directory
+ self.logfile.dir.add_ignore( [self.logfile] )
+ self.env['PIPE_BUILD'] = 1
+ self.env['PSTDOUT'] = self.logstream
+ self.env['PSTDERR'] = self.logstream
+ else:
+ self.logstream = None
+ # we use a special builder to create source files from TEXT
+ action = SCons.Action.Action(_createSource,varlist=['SCONF_TEXT'])
+ sconfSrcBld = SCons.Builder.Builder(action=action)
+ self.env.Append( BUILDERS={'SConfSourceBuilder':sconfSrcBld} )
+ 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()
+
+ def _shutdown(self):
+ """Private method. Reset to non-piped spawn"""
+ global _activeSConfObjets
+
+ 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
+ # clean up environment
+ del self.env['PIPE_BUILD']
+ del self.env['PSTDOUT']
+ del self.env['PSTDERR']
+ # remove the SConfSourceBuilder from the environment
+ blds = self.env['BUILDERS']
+ del blds['SConfSourceBuilder']
+ self.env.Replace( BUILDERS=blds )
+ self.active = 0
+ del _activeSConfObjects[self]
+ self._dumpCache()
+
+
+class CheckContext:
+ """Provides a context for configure tests. Defines how a test writes to the
+ screen and log file.
+
+ A typical test is just a callable with an instance of CheckContext as
+ first argument:
+
+ def CheckCustom(context, ...)
+ context.Message('Checking my weird test ... ')
+ ret = myWeirdTestFunction(...)
+ context.Result(ret)
+
+ Often, myWeirdTestFunction will be one of
+ context.TryCompile/context.TryLink/context.TryRun. The results of
+ those are cached, for they are only rebuild, if the dependencies have
+ changed.
+ """
+
+ def __init__(self, sconf):
+ """Constructor. Pass the corresponding SConf instance."""
+ self.sconf = sconf
+ self.cached = 0
+ self.show_result = 0
+
+ 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.show_result = 0
+
+ def Result(self, res ):
+ """Inform about the result of the test. res may be an integer or a
+ string. In case of an integer, the written text will be 'ok' or
+ 'failed'.
+ """
+ if( type(res) == IntType ):
+ if res:
+ text = "ok"
+ else:
+ text = "failed"
+ elif( type(res) == StringType ):
+ text = res
+ else:
+ raise TypeError, "Expected string or int"
+ if( self.cached ):
+ text = text + " (cached)"
+ if self.show_result == 0:
+ if self.sconf.logstream != None:
+ self.sconf.logstream.write("Result: " + text + "\n\n")
+ sys.stdout.write(text + "\n")
+ self.show_result = 1
+
+
+ def TryBuild(self, *args, **kw):
+ return apply(self.sconf.TryBuild, args, kw)
+
+ def TryAction(self, *args, **kw):
+ return apply(self.sconf.TryAction, args, kw)
+
+ def TryCompile(self, *args, **kw):
+ return apply(self.sconf.TryCompile, args, kw)
+
+ def TryLink(self, *args, **kw):
+ return apply(self.sconf.TryLink, args, kw)
+
+ def TryRun(self, *args, **kw):
+ return apply(self.sconf.TryRun, args, kw)
+
+ def __getattr__( self, attr ):
+ if( attr == 'env' ):
+ return self.sconf.env
+ else:
+ raise AttributeError, "CheckContext instance has no attribute '%s'" % attr
+
+def CheckCHeader(test, header):
+ """
+ A test for a c header file.
+ """
+ # ToDo: Support also system header files (i.e. #include <header.h>)
+ test.Message("Checking for C header %s... " % header)
+ ret = test.TryCompile("#include \"%s\"\n\n" % header, ".c")
+ test.Result( ret )
+ return ret
+
+
+def CheckCXXHeader(test, header):
+ """
+ A test for a c++ header file.
+ """
+ # ToDo: Support also system header files (i.e. #include <header.h>)
+ test.Message("Checking for C header %s... " % header)
+ ret = test.TryCompile("#include \"%s\"\n\n" % header, ".cpp")
+ test.Result( ret )
+ return ret
+
+def CheckLib(test, library=None, symbol="main", autoadd=1):
+ """
+ A test for a library. See also CheckLibWithHeader.
+ Note that library may also be None to test whether the given symbol
+ compiles without flags.
+ """
+ # ToDo: accept path for the 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
+ # library is called like when we get --libs from a configure script
+ if library != None:
+ test.env.Append(LIBS = [ library ])
+
+ text = ""
+ if symbol != "main":
+ text = text + """
+#ifdef __cplusplus
+extern "C"
+#endif
+char %s();""" % symbol
+ text = text + """
+int
+main() {
+%s();
+return 0;
+}
+\n\n""" % symbol
+
+ ret = test.TryLink( text, ".c" )
+ if not autoadd or not ret:
+ test.env.Replace(LIBS=oldLIBS)
+
+ test.Result(ret)
+ return ret
+
+def CheckLibWithHeader(test, library, header, language, call="main();", autoadd=1):
+ # ToDo: accept path for library. Support system header files.
+ """
+ Another (more sophisticated) test for a library.
+ Checks, if library and header is available for language (maybe 'C'
+ or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'.
+ As in CheckLib, we support library=None, to test if the call compiles
+ without extra link flags.
+ """
+ test.Message("Checking for %s in library %s (header %s) ... " %
+ (call, library, header))
+ oldLIBS= test.env.get( 'LIBS', [] )
+
+ # NOTE: we allow this at in the case that we don't know what the
+ # library is called like when we get --libs from a configure script
+ if library != None:
+ test.env.Append(LIBS = [ library ])
+
+ text = """\
+#include "%s"
+int main() {
+ %s
+}
+""" % (header, call)
+
+ if language in ["C", "c"]:
+ extension=".c"
+ elif language in ["CXX", "cxx", "C++", "c++"]:
+ extension=".cpp"
+ else:
+ raise SCons.Errors.UserError, "Unknown language!"
+
+ ret = test.TryLink( text, extension)
+ if not autoadd or not ret:
+ test.env.Replace( LIBS = oldLIBS )
+
+ test.Result(ret)
+ return ret
diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py
new file mode 100644
index 0000000..0b737c0
--- /dev/null
+++ b/src/engine/SCons/SConfTests.py
@@ -0,0 +1,296 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import re
+import StringIO
+import sys
+import unittest
+
+import TestCmd
+
+import SCons.Environment
+import SCons.SConf
+
+scons_env = SCons.Environment.Environment()
+sys.stdout = StringIO.StringIO()
+
+if sys.platform == 'win32':
+ existing_lib = "msvcrt"
+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 = '')
+
+ 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
+
+
+ def _baseTryXXX(self, TryFunc):
+ def checks(sconf, TryFunc):
+ res1 = TryFunc( sconf, "int main() { return 0; }", ".c" )
+ res2 = TryFunc( sconf, "not a c program", ".c" )
+ 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'))
+ try:
+ res = checks( sconf, TryFunc )
+ assert res[0] and not res[1]
+ finally:
+ sconf.Finish()
+
+ # 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'))
+ try:
+ res = checks( 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
+
+ def test_TryCompile(self):
+ self._baseTryXXX( SCons.SConf.SConf.TryCompile )
+
+ def test_TryLink(self):
+ self._baseTryXXX( SCons.SConf.SConf.TryLink )
+
+ def test_TryRun(self):
+ def checks(sconf):
+ prog = """
+#include <stdio.h>
+int main() {
+ printf( "Hello" );
+ return 0;
+}
+"""
+ res1 = sconf.TryRun( prog, ".c" )
+ res2 = sconf.TryRun( "not a c program", ".c" )
+ 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'))
+ 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()
+
+ # 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'))
+ 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 )
+ firstOcc = expr.match( log )
+ assert firstOcc != None
+ secondOcc = expr.match( log, firstOcc.end(0) )
+ assert secondOcc == None
+
+
+ def test_TryAction(self):
+ 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,
+ conf_dir=self.test.workpath('config.tests'),
+ log_file=self.test.workpath('config.log'))
+ try:
+ (ret, output) = sconf.TryAction(action=actionOK)
+ assert ret and output == "RUN OK"
+ (ret, output) = sconf.TryAction(action=actionFAIL)
+ assert not ret and output == ""
+ finally:
+ sconf.Finish()
+
+
+
+ def test_StandardTests(self):
+ def CHeaderChecks( sconf ):
+ res1 = sconf.CheckCHeader( "stdio.h" )
+ res2 = sconf.CheckCHeader( "HopefullyNotCHeader.noh" )
+ return (res1,res2)
+
+ def CXXHeaderChecks(sconf):
+ res1 = sconf.CheckCXXHeader( "vector" )
+ res2 = sconf.CheckCXXHeader( "HopefullyNotCXXHeader.noh" )
+ return (res1,res2)
+
+ def LibChecks(sconf):
+ res1 = sconf.CheckLib( existing_lib, "main", autoadd=0 )
+ res2 = sconf.CheckLib( "hopefullynolib", "main", autoadd=0 )
+ return (res1, res2)
+
+ def LibChecksAutoAdd(sconf):
+ def libs(env):
+ if env.has_key( "LIBS" ):
+ return env['LIBS']
+ else:
+ return []
+ env = sconf.env.Copy()
+ res1 = sconf.CheckLib( existing_lib, "main", autoadd=1 )
+ libs1 = (libs(env), libs(sconf.env) )
+ sconf.env = env.Copy()
+ res2 = sconf.CheckLib( existing_lib, "main", autoadd=0 )
+ libs2 = (libs(env), libs(sconf.env) )
+ sconf.env = env.Copy()
+ return ((res1, libs1), (res2, libs2))
+
+ def LibWithHeaderChecks(sconf):
+ res1 = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=0 )
+ res2 = sconf.CheckLibWithHeader( "hopefullynolib", "math.h", "C", autoadd=0 )
+ return (res1, res2)
+
+ def LibWithHeaderChecksAutoAdd(sconf):
+ def libs(env):
+ if env.has_key( "LIBS" ):
+ return env['LIBS']
+ else:
+ return []
+ env = sconf.env.Copy()
+ res1 = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=1 )
+ libs1 = (libs(env), libs(sconf.env) )
+ sconf.env = env.Copy()
+ res2 = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=0 )
+ libs2 = (libs(env), libs(sconf.env) )
+ sconf.env = env.Copy()
+ 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'))
+ try:
+ (res1, res2) = CHeaderChecks(sconf)
+ assert res1 and not res2
+ (res1, res2) = CXXHeaderChecks(sconf)
+ assert res1 and not res2
+ (res1, res2) = LibChecks(sconf)
+ assert res1 and not res2
+ ((res1, libs1), (res2, libs2)) = LibChecksAutoAdd(sconf)
+ assert res1 and res2
+ assert len(libs1[1]) - 1 == len(libs1[0]) and libs1[1][-1] == existing_lib
+ assert len(libs2[1]) == len(libs2[0])
+ (res1, res2) = LibWithHeaderChecks(sconf)
+ assert res1 and not res2
+ ((res1, libs1), (res2, libs2)) = LibWithHeaderChecksAutoAdd(sconf)
+ assert res1 and res2
+ assert len(libs1[1]) - 1 == len(libs1[0]) and libs1[1][-1] == existing_lib
+ assert len(libs2[1]) == len(libs2[0])
+ finally:
+ sconf.Finish()
+
+ def test_CustomChecks(self):
+
+ def CheckCustom(test):
+ test.Message( "Checking UserTest ... " )
+ prog = """
+#include <stdio.h>
+
+int main() {
+ printf( "Hello" );
+ return 0;
+}
+"""
+ (ret, output) = test.TryRun( prog, ".c" )
+ test.Result( ret )
+ assert ret and output == "Hello"
+ return ret
+
+
+ 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'))
+ try:
+ ret = sconf.CheckCustom()
+ assert ret
+ finally:
+ sconf.Finish()
+
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(SConfTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful ():
+ sys.exit(1)
+
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index e77b879..e3da64f 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -38,6 +38,7 @@ import SCons.Errors
import SCons.Node
import SCons.Node.FS
import SCons.Platform
+import SCons.SConf
import SCons.Script
import SCons.Tool
import SCons.Util
@@ -508,6 +509,7 @@ def BuildDefaultGlobals():
globals['Builder'] = SCons.Builder.Builder
globals['CacheDir'] = SCons.Node.FS.default_fs.CacheDir
globals['Clean'] = Clean
+ globals['Configure'] = SCons.SConf.SConf
globals['CScan'] = SCons.Defaults.CScan
globals['Default'] = Default
globals['Dir'] = SCons.Node.FS.default_fs.Dir
diff --git a/test/Configure.py b/test/Configure.py
new file mode 100644
index 0000000..b78cdcb
--- /dev/null
+++ b/test/Configure.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import re
+import sys
+
+import TestCmd
+import TestSCons
+
+if sys.platform == 'win32':
+ lib = 'msvcrt'
+else:
+ lib = 'm'
+
+test = TestSCons.TestSCons()
+python = TestSCons.python
+
+def checkLog( test, logfile, numUpToDate, numCache ):
+ test.fail_test(not os.path.exists(test.workpath('config.log')))
+ 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:
+
+ # 1.1 if checks are ok, the cache mechanism should work
+
+ test.write( 'SConstruct', """
+env = Environment()
+conf = Configure(env)
+r1 = conf.CheckLibWithHeader( '%s', 'math.h', 'c' )
+r2 = conf.CheckLibWithHeader( None, 'math.h', 'c' )
+r3 = conf.CheckLib( '%s', autoadd=0 )
+r4 = conf.CheckLib( None, autoadd=0 )
+r5 = conf.CheckCHeader( 'math.h' )
+r6 = conf.CheckCXXHeader( 'vector' )
+env = conf.Finish()
+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 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
+""" % (lib, lib))
+
+ test.run(stdout = required_stdout)
+ checkLog(test,'config.log', 0, 0 )
+
+ test.run(stdout = required_stdout)
+ checkLog(test,'config.log',12, 0 )
+
+ # 1.2 if checks are not ok, the cache mechanism should work as well
+ # (via explicit cache)
+
+ test.write( 'SConstruct', """
+env = Environment()
+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
+env = conf.Finish()
+if not (not r1 and not r2):
+ print "FAIL: ", r1, r2
+ Exit(1)
+""")
+
+ 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
+""")
+
+ test.run(stdout = required_stdout)
+ checkLog(test, 'config.log', 0, 0 )
+
+ test.run(stdout = required_stdout)
+ checkLog(test, 'config.log', 2, 2 )
+
+
+ # 2.1 test that normal builds work together with Sconf
+ test.write( 'SConstruct', """
+env = Environment()
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+SConscript( 'SConscript' )
+""")
+ test.write( 'SConscript', """
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+ test.write( 'TestProgram.c', """
+#include <stdio.h>
+
+int main() {
+ printf( "Hello\\n" );
+}
+""")
+ 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
+""")
+ test.run( stdout = required_stdout )
+ checkLog( test, 'config.log', 0, 0 )
+
+ test.run( stdout = required_stdout )
+ checkLog( test, 'config.log', 3, 1 )
+
+
+ # 2.2 test that BuildDir builds work together with Sconf
+ test.write( 'SConstruct', """
+env = Environment()
+BuildDir( 'build', '.' )
+conf = Configure(env, conf_dir='build/config.tests', log_file='build/config.log')
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+# print open( 'build/config.log' ).readlines()
+SConscript( 'build/SConscript' )
+""")
+ test.write( 'SConscript', """
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+ test.write( 'TestProgram.c', """
+#include <stdio.h>
+
+int main() {
+ printf( "Hello\\n" );
+}
+""")
+ 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
+""")
+ test.run( stdout = required_stdout )
+ checkLog( test, 'build/config.log', 0, 0 )
+
+ test.run( stdout = required_stdout )
+ checkLog( test, 'build/config.log', 3, 1 )
+
+
+ # 3.1 test custom tests
+ compileOK = '#include <stdio.h>\\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', """
+def CheckCustom(test):
+ 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' )
+ 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!=""
+ resFAIL = resFAIL or retActFAIL or outputActFAIL!=""
+ test.Result( resOK and not resFAIL )
+ return resOK and not resFAIL
+
+env = Environment()
+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")
+ test.run(stdout = required_stdout)
+ checkLog( test, 'config.log', 0, 0 )
+
+ test.run(stdout = required_stdout)
+ checkLog( test, 'config.log', 12, 4 )
+
+ test.pass_test()
+
+finally:
+ pass
+ #os.system( 'find . -type f -exec ls -l {} \;' )
+ #print "-------------config.log------------------"
+ #print test.read( test.workpath('config.log' ))
+ #print "-------------build/config.log------------"
+ #print test.read( test.workpath('build/config.log' ))