summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/MANIFEST.in1
-rw-r--r--src/engine/SCons/ActionTests.py36
-rw-r--r--src/engine/SCons/Debug.py28
-rw-r--r--src/engine/SCons/Defaults.py39
-rw-r--r--src/engine/SCons/Environment.py128
-rw-r--r--src/engine/SCons/EnvironmentTests.py186
-rw-r--r--src/engine/SCons/Memoize.py7
-rw-r--r--src/engine/SCons/Node/FS.py98
-rw-r--r--src/engine/SCons/Node/FSTests.py73
-rw-r--r--src/engine/SCons/Node/Python.py28
-rw-r--r--src/engine/SCons/Node/PythonTests.py38
-rw-r--r--src/engine/SCons/Node/__init__.py3
-rw-r--r--src/engine/SCons/Options/ListOption.py4
-rw-r--r--src/engine/SCons/Options/ListOptionTests.py3
-rw-r--r--src/engine/SCons/Options/OptionsTests.py13
-rw-r--r--src/engine/SCons/Options/__init__.py6
-rw-r--r--src/engine/SCons/Platform/posix.py6
-rw-r--r--src/engine/SCons/Platform/win32.py33
-rw-r--r--src/engine/SCons/Scanner/CTests.py14
-rw-r--r--src/engine/SCons/Script/Main.py46
-rw-r--r--src/engine/SCons/Script/SConscript.py40
-rw-r--r--src/engine/SCons/Tool/as.py4
-rw-r--r--src/engine/SCons/Tool/fortran.py2
-rw-r--r--src/engine/SCons/Tool/gcc.py2
-rw-r--r--src/engine/SCons/Tool/mingw.py3
-rw-r--r--src/engine/SCons/Tool/mslink.xml14
-rw-r--r--src/engine/SCons/Tool/msvc.py12
-rw-r--r--src/engine/SCons/Tool/msvc.xml47
-rw-r--r--src/engine/SCons/Tool/msvs.py104
-rw-r--r--src/engine/SCons/Tool/msvsTests.py12
-rw-r--r--src/engine/SCons/cpp.py561
-rw-r--r--src/engine/SCons/cppTests.py573
32 files changed, 1876 insertions, 288 deletions
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in
index 9d6c972..c762f1c 100644
--- a/src/engine/MANIFEST.in
+++ b/src/engine/MANIFEST.in
@@ -2,6 +2,7 @@ SCons/__init__.py
SCons/Action.py
SCons/Builder.py
SCons/Conftest.py
+SCons/cpp.py
SCons/dblite.py
SCons/Debug.py
SCons/Defaults.py
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 1085586..57910de 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -182,6 +182,8 @@ if os.name == 'java':
else:
python = sys.executable
+_python_ = '"' + python + '"'
+
class ActionTestCase(unittest.TestCase):
"""Test the Action() factory function"""
@@ -903,7 +905,7 @@ class CommandActionTestCase(unittest.TestCase):
except AttributeError:
env = Environment()
- cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
+ cmd1 = r'%s %s %s xyzzy' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd1)
r = act([], [], env.Copy())
@@ -911,7 +913,7 @@ class CommandActionTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'xyzzy'\n", c
- cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
+ cmd2 = r'%s %s %s $TARGET' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd2)
r = act(DummyNode('foo'), [], env.Copy())
@@ -919,7 +921,7 @@ class CommandActionTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'foo'\n", c
- cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
+ cmd3 = r'%s %s %s ${TARGETS}' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd3)
r = act(map(DummyNode, ['aaa', 'bbb']), [], env.Copy())
@@ -927,7 +929,7 @@ class CommandActionTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'aaa' 'bbb'\n", c
- cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
+ cmd4 = r'%s %s %s $SOURCES' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd4)
r = act([], [DummyNode('one'), DummyNode('two')], env.Copy())
@@ -935,7 +937,7 @@ class CommandActionTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'one' 'two'\n", c
- cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
+ cmd4 = r'%s %s %s ${SOURCES[:2]}' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd4)
sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')]
@@ -945,7 +947,7 @@ class CommandActionTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'three' 'four'\n", c
- cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
+ cmd5 = r'%s %s %s $TARGET XYZZY' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd5)
env5 = Environment()
@@ -978,7 +980,7 @@ class CommandActionTestCase(unittest.TestCase):
def get_subst_proxy(self):
return self
- cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
+ cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile)
act = SCons.Action.CommandAction(cmd6)
r = act(target = [Obj('111'), Obj('222')],
@@ -1016,31 +1018,31 @@ class CommandActionTestCase(unittest.TestCase):
r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexecutable, "r == %d" % r
- act = SCons.Action.CommandAction('%s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 1, r
- act = SCons.Action.CommandAction('@%s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 1, r
- act = SCons.Action.CommandAction('@-%s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 0, r
- act = SCons.Action.CommandAction('-%s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('-%s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 0, r
- act = SCons.Action.CommandAction('@ %s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 1, r
- act = SCons.Action.CommandAction('@- %s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 0, r
- act = SCons.Action.CommandAction('- %s %s 1' % (python, exit_py))
+ act = SCons.Action.CommandAction('- %s %s 1' % (_python_, exit_py))
r = act([], [], env)
assert r == 0, r
@@ -1074,7 +1076,7 @@ class CommandActionTestCase(unittest.TestCase):
# test redirection operators
def test_redirect(self, redir, stdout_msg, stderr_msg):
- cmd = r'%s %s %s xyzzy %s' % (python, act_py, outfile, redir)
+ cmd = r'%s %s %s xyzzy %s' % (_python_, act_py, outfile, redir)
# Write the output and error messages to files because
# Windows can't handle strings that are too big in its
# external environment (os.spawnve() returns EINVAL,
@@ -1571,7 +1573,7 @@ class ListActionTestCase(unittest.TestCase):
a([], [], Environment(s = self))
assert self.inc == 3, self.inc
- cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
+ cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile)
def function2(target, source, env):
open(env['out'], 'a').write("function2\n")
@@ -1644,7 +1646,7 @@ class LazyActionTestCase(unittest.TestCase):
a = SCons.Action.Action('$BAR')
a([], [], env=Environment(BAR = f, s = self))
assert self.test == 1, self.test
- cmd = r'%s %s %s lazy' % (python, act_py, outfile)
+ cmd = r'%s %s %s lazy' % (_python_, act_py, outfile)
a([], [], env=Environment(BAR = cmd, s = self))
c = test.read(outfile, 'r')
assert c == "act.py: 'lazy'\n", c
diff --git a/src/engine/SCons/Debug.py b/src/engine/SCons/Debug.py
index 1cff9c6..47d2134 100644
--- a/src/engine/SCons/Debug.py
+++ b/src/engine/SCons/Debug.py
@@ -119,20 +119,22 @@ else:
caller_dicts = {}
-def caller(back=0):
+def caller(*backlist):
import traceback
- tb = traceback.extract_stack(limit=3+back)
- key = tb[1][:3]
- try:
- entry = caller_dicts[key]
- except KeyError:
- entry = caller_dicts[key] = {}
- key = tb[0][:3]
- try:
- entry[key] = entry[key] + 1
- except KeyError:
- entry[key] = 1
- return '%s:%d(%s)' % func_shorten(key)
+ if not backlist:
+ backlist = [0]
+ result = []
+ for back in backlist:
+ tb = traceback.extract_stack(limit=3+back)
+ key = tb[1][:3]
+ try:
+ entry = caller_dicts[key]
+ except KeyError:
+ entry = caller_dicts[key] = {}
+ key = tb[0][:3]
+ entry[key] = entry.get(key, 0) + 1
+ result.append('%s:%d(%s)' % func_shorten(key))
+ return result
def dump_caller_counts(file=sys.stdout):
keys = caller_dicts.keys()
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index cb628b8..7513c0d 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -173,14 +173,34 @@ Touch = ActionFactory(touch_func,
lambda file: 'Touch("%s")' % file)
# Internal utility functions
-def copyFunc(dest, source, env):
- """Install a source file into a destination by copying it (and its
- permission/mode bits)."""
- shutil.copy2(source, dest)
- st = os.stat(source)
- os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+def installFunc(dest, source, env):
+ """Install a source file or directory into a destination by copying,
+ (including copying permission/mode bits)."""
+
+ if os.path.isdir(source):
+ if os.path.exists(dest):
+ if not os.path.isdir(dest):
+ raise SCons.Errors.UserError, "cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source))
+ else:
+ parent = os.path.split(dest)[0]
+ if not os.path.exists(parent):
+ os.makedirs(parent)
+ shutil.copytree(source, dest)
+ else:
+ shutil.copy2(source, dest)
+ st = os.stat(source)
+ os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+
return 0
+def installStr(dest, source, env):
+ source = str(source)
+ if os.path.isdir(source):
+ type = 'directory'
+ else:
+ type = 'file'
+ return 'Install %s: "%s" as "%s"' % (type, source, dest)
+
def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
"""Creates a new list from 'list' by first interpolating each
element in the list using the 'env' dictionary and then calling f
@@ -334,13 +354,14 @@ ConstructionEnvironment = {
'SCANNERS' : [],
'CONFIGUREDIR' : '#/.sconf_temp',
'CONFIGURELOG' : '#/config.log',
- 'INSTALLSTR' : 'Install file: "$SOURCE" as "$TARGET"',
'CPPSUFFIXES' : SCons.Tool.CSuffixes,
'DSUFFIXES' : SCons.Tool.DSuffixes,
+ 'ENV' : {},
'IDLSUFFIXES' : SCons.Tool.IDLSuffixes,
+ 'INSTALL' : installFunc,
+ 'INSTALLSTR' : installStr,
+ '_installStr' : installStr,
'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes,
- 'ENV' : {},
- 'INSTALL' : copyFunc,
'_concat' : _concat,
'_defines' : _defines,
'_stripixes' : _stripixes,
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index d76f71d..7aa1909 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -84,7 +84,11 @@ def installFunc(target, source, env):
return install(target[0].path, source[0].path, env)
def installString(target, source, env):
- return env.subst_target_source(env['INSTALLSTR'], 0, target, source)
+ s = env.get('INSTALLSTR', '')
+ if callable(s):
+ return s(target[0].path, source[0].path, env)
+ else:
+ return env.subst_target_source(s, 0, target, source)
installAction = SCons.Action.Action(installFunc, installString)
@@ -742,21 +746,31 @@ class Base(SubstitutionEnvironment):
# environment before calling the tools, because they may use
# some of them during initialization.
apply(self.Replace, (), kw)
+ keys = kw.keys()
if options:
+ keys = keys + options.keys()
options.Update(self)
+ save = {}
+ for k in keys:
+ try:
+ save[k] = self._dict[k]
+ except KeyError:
+ # No value may have been set if they tried to pass in a
+ # reserved variable name like TARGETS.
+ pass
+
if tools is None:
tools = self._dict.get('TOOLS', None)
if tools is None:
tools = ['default']
apply_tools(self, tools, toolpath)
- # Now re-apply the passed-in variables and customizable options
+ # Now restore the passed-in variables and customized options
# to the environment, since the values the user set explicitly
# should override any values set by the tools.
- apply(self.Replace, (), kw)
- if options:
- options.Update(self)
+ for key, val in save.items():
+ self._dict[key] = val
#######################################################################
# Utility methods that are primarily for internal use by SCons.
@@ -895,19 +909,19 @@ class Base(SubstitutionEnvironment):
self._dict[key] = val
else:
try:
- # Most straightforward: just try to add them
- # together. This will work in most cases, when the
- # original and new values are of compatible types.
- self._dict[key] = orig + val
- except TypeError:
+ # Check if the original looks like a dictionary.
+ # If it is, we can't just try adding the value because
+ # dictionaries don't have __add__() methods, and
+ # things like UserList will incorrectly coerce the
+ # original dict to a list (which we don't want).
+ update_dict = orig.update
+ except AttributeError:
try:
- # Try to update a dictionary value with another.
- # If orig isn't a dictionary, it won't have an
- # update() method; if val isn't a dictionary,
- # it won't have a keys() method. Either way,
- # it's an AttributeError.
- orig.update(val)
- except AttributeError:
+ # Most straightforward: just try to add them
+ # together. This will work in most cases, when the
+ # original and new values are of compatible types.
+ self._dict[key] = orig + val
+ except (KeyError, TypeError):
try:
# Check if the original is a list.
add_to_orig = orig.append
@@ -925,6 +939,17 @@ class Base(SubstitutionEnvironment):
# value to it (if there's a value to append).
if val:
add_to_orig(val)
+ else:
+ # The original looks like a dictionary, so update it
+ # based on what we think the value looks like.
+ if SCons.Util.is_List(val):
+ for v in val:
+ orig[v] = None
+ else:
+ try:
+ update_dict(val)
+ except (AttributeError, TypeError, ValueError):
+ orig[val] = None
self.scanner_map_delete(kw)
def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
@@ -952,7 +977,7 @@ class Base(SubstitutionEnvironment):
"""
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
- if not self._dict.has_key(key) or not self._dict[key]:
+ if not self._dict.has_key(key) or self._dict[key] in ('', None):
self._dict[key] = val
elif SCons.Util.is_Dict(self._dict[key]) and \
SCons.Util.is_Dict(val):
@@ -1134,19 +1159,19 @@ class Base(SubstitutionEnvironment):
self._dict[key] = val
else:
try:
- # Most straightforward: just try to add them
- # together. This will work in most cases, when the
- # original and new values are of compatible types.
- self._dict[key] = val + orig
- except TypeError:
+ # Check if the original looks like a dictionary.
+ # If it is, we can't just try adding the value because
+ # dictionaries don't have __add__() methods, and
+ # things like UserList will incorrectly coerce the
+ # original dict to a list (which we don't want).
+ update_dict = orig.update
+ except AttributeError:
try:
- # Try to update a dictionary value with another.
- # If orig isn't a dictionary, it won't have an
- # update() method; if val isn't a dictionary,
- # it won't have a keys() method. Either way,
- # it's an AttributeError.
- orig.update(val)
- except AttributeError:
+ # Most straightforward: just try to add them
+ # together. This will work in most cases, when the
+ # original and new values are of compatible types.
+ self._dict[key] = val + orig
+ except (KeyError, TypeError):
try:
# Check if the added value is a list.
add_to_val = val.append
@@ -1164,6 +1189,17 @@ class Base(SubstitutionEnvironment):
if orig:
add_to_val(orig)
self._dict[key] = val
+ else:
+ # The original looks like a dictionary, so update it
+ # based on what we think the value looks like.
+ if SCons.Util.is_List(val):
+ for v in val:
+ orig[v] = None
+ else:
+ try:
+ update_dict(val)
+ except (AttributeError, TypeError, ValueError):
+ orig[val] = None
self.scanner_map_delete(kw)
def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
@@ -1191,7 +1227,7 @@ class Base(SubstitutionEnvironment):
"""
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
- if not self._dict.has_key(key) or not self._dict[key]:
+ if not self._dict.has_key(key) or self._dict[key] in ('', None):
self._dict[key] = val
elif SCons.Util.is_Dict(self._dict[key]) and \
SCons.Util.is_Dict(val):
@@ -1504,25 +1540,37 @@ class Base(SubstitutionEnvironment):
try:
dnodes = self.arg2nodes(dir, self.fs.Dir)
except TypeError:
- raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir)
+ fmt = "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?"
+ raise SCons.Errors.UserError, fmt % str(dir)
try:
- sources = self.arg2nodes(source, self.fs.File)
+ sources = self.arg2nodes(source, self.fs.Entry)
except TypeError:
if SCons.Util.is_List(source):
- raise SCons.Errors.UserError, "Source `%s' of Install() contains one or more non-files. Install() source must be one or more files." % repr(map(str, source))
+ s = repr(map(str, source))
else:
- raise SCons.Errors.UserError, "Source `%s' of Install() is not a file. Install() source must be one or more files." % str(source)
+ s = str(source)
+ fmt = "Source `%s' of Install() is neither a file nor a directory. Install() source must be one or more files or directories"
+ raise SCons.Errors.UserError, fmt % s
tgt = []
for dnode in dnodes:
for src in sources:
- target = self.fs.File(src.name, dnode)
+ target = self.fs.Entry(src.name, dnode)
tgt.extend(InstallBuilder(self, target, src))
return tgt
def InstallAs(self, target, source):
"""Install sources as targets."""
- sources = self.arg2nodes(source, self.fs.File)
- targets = self.arg2nodes(target, self.fs.File)
+ sources = self.arg2nodes(source, self.fs.Entry)
+ targets = self.arg2nodes(target, self.fs.Entry)
+ if len(sources) != len(targets):
+ if not SCons.Util.is_List(target):
+ target = [target]
+ if not SCons.Util.is_List(source):
+ source = [source]
+ t = repr(map(str, target))
+ s = repr(map(str, source))
+ fmt = "Target (%s) and source (%s) lists of InstallAs() must be the same length."
+ raise SCons.Errors.UserError, fmt % (t, s)
result = []
for src, tgt in map(lambda x, y: (x, y), sources, targets):
result.extend(InstallBuilder(self, tgt, src))
@@ -1632,10 +1680,10 @@ class Base(SubstitutionEnvironment):
else:
raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
- def Value(self, value):
+ def Value(self, value, built_value=None):
"""
"""
- return SCons.Node.Python.Value(value)
+ return SCons.Node.Python.Value(value, built_value)
class OverrideEnvironment(Base):
"""A proxy that overrides variables in a wrapped construction
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index c56f1f5..52aac23 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -23,6 +23,7 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import copy
import os
import string
import StringIO
@@ -595,15 +596,17 @@ sys.exit(1)
save_stderr = sys.stderr
+ python = '"' + sys.executable + '"'
+
try:
- cmd = '%s %s' % (sys.executable, test.workpath('stdout.py'))
+ cmd = '%s %s' % (python, test.workpath('stdout.py'))
output = env.backtick(cmd)
assert output == 'this came from stdout.py\n', output
sys.stderr = StringIO.StringIO()
- cmd = '%s %s' % (sys.executable, test.workpath('stderr.py'))
+ cmd = '%s %s' % (python, test.workpath('stderr.py'))
output = env.backtick(cmd)
errout = sys.stderr.getvalue()
@@ -612,7 +615,7 @@ sys.exit(1)
sys.stderr = StringIO.StringIO()
- cmd = '%s %s' % (sys.executable, test.workpath('fail.py'))
+ cmd = '%s %s' % (python, test.workpath('fail.py'))
try:
env.backtick(cmd)
except OSError, e:
@@ -747,6 +750,24 @@ class BaseTestCase(unittest.TestCase,TestEnvironmentFixture):
assert not env1.has_key('__env__')
assert not env2.has_key('__env__')
+ def test_options(self):
+ """Test that options only get applied once."""
+ class FakeOptions:
+ def __init__(self, key, val):
+ self.calls = 0
+ self.key = key
+ self.val = val
+ def keys(self):
+ return [self.key]
+ def Update(self, env):
+ env[self.key] = self.val
+ self.calls = self.calls + 1
+
+ o = FakeOptions('AAA', 'fake_opt')
+ env = Environment(options=o, AAA='keyword_arg')
+ assert o.calls == 1, o.calls
+ assert env['AAA'] == 'fake_opt', env['AAA']
+
def test_get(self):
"""Test the get() method."""
env = self.TestEnvironment(aaa = 'AAA')
@@ -1212,6 +1233,8 @@ def exists(env):
b2 = Environment()['BUILDERS']
assert b1 == b2, diff_dict(b1, b2)
+ import UserDict
+ UD = UserDict.UserDict
import UserList
UL = UserList.UserList
@@ -1243,6 +1266,18 @@ def exists(env):
UL(['i7']), [''], UL(['i7', '']),
UL(['i8']), UL(['']), UL(['i8', '']),
+ {'d1':1}, 'D1', {'d1':1, 'D1':None},
+ {'d2':1}, ['D2'], {'d2':1, 'D2':None},
+ {'d3':1}, UL(['D3']), {'d3':1, 'D3':None},
+ {'d4':1}, {'D4':1}, {'d4':1, 'D4':1},
+ {'d5':1}, UD({'D5':1}), UD({'d5':1, 'D5':1}),
+
+ UD({'u1':1}), 'U1', UD({'u1':1, 'U1':None}),
+ UD({'u2':1}), ['U2'], UD({'u2':1, 'U2':None}),
+ UD({'u3':1}), UL(['U3']), UD({'u3':1, 'U3':None}),
+ UD({'u4':1}), {'U4':1}, UD({'u4':1, 'U4':1}),
+ UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}),
+
'', 'M1', 'M1',
'', ['M2'], ['M2'],
'', UL(['M3']), UL(['M3']),
@@ -1293,14 +1328,21 @@ def exists(env):
failed = 0
while cases:
input, append, expect = cases[:3]
- env['XXX'] = input
- env.Append(XXX = append)
- result = env['XXX']
- if result != expect:
+ env['XXX'] = copy.copy(input)
+ try:
+ env.Append(XXX = append)
+ except Exception, e:
if failed == 0: print
- print " %s Append %s => %s did not match %s" % \
- (repr(input), repr(append), repr(result), repr(expect))
+ print " %s Append %s exception: %s" % \
+ (repr(input), repr(append), e)
failed = failed + 1
+ else:
+ result = env['XXX']
+ if result != expect:
+ if failed == 0: print
+ print " %s Append %s => %s did not match %s" % \
+ (repr(input), repr(append), repr(result), repr(expect))
+ failed = failed + 1
del cases[:3]
assert failed == 0, "%d Append() cases failed" % failed
@@ -1399,6 +1441,33 @@ def exists(env):
assert env['CCC1'] == 'c1', env['CCC1']
assert env['CCC2'] == ['c2'], env['CCC2']
+ env['CLVar'] = CLVar([])
+ env.AppendUnique(CLVar = 'bar')
+ result = env['CLVar']
+ if sys.version[0] == '1':
+ # Python 1.5.2 has a quirky behavior where CLVar([]) actually
+ # matches '' and [] due to different __coerce__() semantics
+ # in the UserList implementation. It isn't worth a lot of
+ # effort to get this corner case to work identically (support
+ # for Python 1.5 support will die soon anyway), so just treat
+ # it separately for now.
+ assert result == 'bar', result
+ else:
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['bar'], result
+
+ env['CLVar'] = CLVar(['abc'])
+ env.AppendUnique(CLVar = 'bar')
+ result = env['CLVar']
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['abc', 'bar'], result
+
+ env['CLVar'] = CLVar(['bar'])
+ env.AppendUnique(CLVar = 'bar')
+ result = env['CLVar']
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['bar'], result
+
def test_Copy(self):
"""Test construction environment copying
@@ -1784,6 +1853,8 @@ f5: \
def test_Prepend(self):
"""Test prepending to construction variables in an Environment
"""
+ import UserDict
+ UD = UserDict.UserDict
import UserList
UL = UserList.UserList
@@ -1815,6 +1886,18 @@ f5: \
UL(['i7']), [''], UL(['', 'i7']),
UL(['i8']), UL(['']), UL(['', 'i8']),
+ {'d1':1}, 'D1', {'d1':1, 'D1':None},
+ {'d2':1}, ['D2'], {'d2':1, 'D2':None},
+ {'d3':1}, UL(['D3']), {'d3':1, 'D3':None},
+ {'d4':1}, {'D4':1}, {'d4':1, 'D4':1},
+ {'d5':1}, UD({'D5':1}), UD({'d5':1, 'D5':1}),
+
+ UD({'u1':1}), 'U1', UD({'u1':1, 'U1':None}),
+ UD({'u2':1}), ['U2'], UD({'u2':1, 'U2':None}),
+ UD({'u3':1}), UL(['U3']), UD({'u3':1, 'U3':None}),
+ UD({'u4':1}), {'U4':1}, UD({'u4':1, 'U4':1}),
+ UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}),
+
'', 'M1', 'M1',
'', ['M2'], ['M2'],
'', UL(['M3']), UL(['M3']),
@@ -1865,14 +1948,21 @@ f5: \
failed = 0
while cases:
input, prepend, expect = cases[:3]
- env['XXX'] = input
- env.Prepend(XXX = prepend)
- result = env['XXX']
- if result != expect:
+ env['XXX'] = copy.copy(input)
+ try:
+ env.Prepend(XXX = prepend)
+ except Exception, e:
if failed == 0: print
- print " %s Prepend %s => %s did not match %s" % \
- (repr(input), repr(prepend), repr(result), repr(expect))
+ print " %s Prepend %s exception: %s" % \
+ (repr(input), repr(prepend), e)
failed = failed + 1
+ else:
+ result = env['XXX']
+ if result != expect:
+ if failed == 0: print
+ print " %s Prepend %s => %s did not match %s" % \
+ (repr(input), repr(prepend), repr(result), repr(expect))
+ failed = failed + 1
del cases[:3]
assert failed == 0, "%d Prepend() cases failed" % failed
@@ -1965,6 +2055,33 @@ f5: \
assert env['CCC1'] == 'c1', env['CCC1']
assert env['CCC2'] == ['c2'], env['CCC2']
+ env['CLVar'] = CLVar([])
+ env.PrependUnique(CLVar = 'bar')
+ result = env['CLVar']
+ if sys.version[0] == '1':
+ # Python 1.5.2 has a quirky behavior where CLVar([]) actually
+ # matches '' and [] due to different __coerce__() semantics
+ # in the UserList implementation. It isn't worth a lot of
+ # effort to get this corner case to work identically (support
+ # for Python 1.5 support will die soon anyway), so just treat
+ # it separately for now.
+ assert result == 'bar', result
+ else:
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['bar'], result
+
+ env['CLVar'] = CLVar(['abc'])
+ env.PrependUnique(CLVar = 'bar')
+ result = env['CLVar']
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['bar', 'abc'], result
+
+ env['CLVar'] = CLVar(['bar'])
+ env.PrependUnique(CLVar = 'bar')
+ result = env['CLVar']
+ assert isinstance(result, CLVar), repr(result)
+ assert result == ['bar'], result
+
def test_Replace(self):
"""Test replacing construction variables in an Environment
@@ -2650,23 +2767,23 @@ def generate(env):
for tnode in tgt:
assert tnode.builder == InstallBuilder
- exc_caught = None
- try:
- tgt = env.Install('export', 'export')
- except SCons.Errors.UserError, e:
- exc_caught = 1
- assert exc_caught, "UserError should be thrown when Install() target is not a file."
- match = str(e) == "Source `export' of Install() is not a file. Install() source must be one or more files."
- assert match, e
+ tgt = env.Install('export', 'build')
+ paths = map(str, tgt)
+ paths.sort()
+ expect = ['export/build']
+ assert paths == expect, paths
+ for tnode in tgt:
+ assert tnode.builder == InstallBuilder
- exc_caught = None
- try:
- tgt = env.Install('export', ['export', 'build/foo1'])
- except SCons.Errors.UserError, e:
- exc_caught = 1
- assert exc_caught, "UserError should be thrown when Install() target containins non-files."
- match = str(e) == "Source `['export', 'build/foo1']' of Install() contains one or more non-files. Install() source must be one or more files."
- assert match, e
+ tgt = env.Install('export', ['build', 'build/foo1'])
+ paths = map(str, tgt)
+ paths.sort()
+ expect = ['export/build', 'export/foo1']
+ assert paths == expect, paths
+ for tnode in tgt:
+ assert tnode.builder == InstallBuilder
+
+ env.File('export/foo1')
exc_caught = None
try:
@@ -2674,8 +2791,8 @@ def generate(env):
except SCons.Errors.UserError, e:
exc_caught = 1
assert exc_caught, "UserError should be thrown reversing the order of Install() targets."
- match = str(e) == "Target `export/foo1' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?"
- assert match, e
+ expect = "Target `export/foo1' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?"
+ assert str(e) == expect, e
def test_InstallAs(self):
"""Test the InstallAs method"""
@@ -2956,6 +3073,9 @@ def generate(env):
assert not v1 is v2
assert v1.value == v2.value
+ v3 = env.Value('c', 'build-c')
+ assert v3.value == 'c', v3.value
+
def test_Environment_global_variable(type):
diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py
index 134206f..c2a3027 100644
--- a/src/engine/SCons/Memoize.py
+++ b/src/engine/SCons/Memoize.py
@@ -798,9 +798,14 @@ else:
# Make sure filename has os.sep+'SCons'+os.sep so that
# SCons.Script.find_deepest_user_frame doesn't stop here
import inspect # It's OK, can't get here for Python < 2.1
+ filename = inspect.getsourcefile(_MeMoIZeR_superinit)
+ if not filename:
+ # This file was compiled at a path name different from
+ # how it's invoked now, so just make up something.
+ filename = whoami('superinit', '???')
superinitcode = compile(
"lambda self, *args, **kw: MPI(self, cls, args, kw)",
- inspect.getsourcefile(_MeMoIZeR_superinit) or '<unknown>',
+ filename,
"eval")
superinit = eval(superinitcode,
{'cls':cls,
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index ce5bcc0..382bca3 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -95,10 +95,33 @@ def save_strings(val):
# there should be *no* changes to the external file system(s)...
#
-def _copy_func(src, dest):
+if hasattr(os, 'link'):
+ def _hardlink_func(fs, src, dst):
+ # If the source is a symlink, we can't just hard-link to it
+ # because a relative symlink may point somewhere completely
+ # different. We must disambiguate the symlink and then
+ # hard-link the final destination file.
+ while fs.islink(src):
+ link = fs.readlink(src)
+ if not os.path.isabs(link):
+ src = link
+ else:
+ src = os.path.join(os.path.dirname(src), link)
+ fs.link(src, dst)
+else:
+ _hardlink_func = None
+
+if hasattr(os, 'symlink'):
+ def _softlink_func(fs, src, dst):
+ fs.symlink(src, dst)
+else:
+ _softlink_func = None
+
+def _copy_func(fs, src, dest):
shutil.copy2(src, dest)
- st=os.stat(src)
- os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+ st = fs.stat(src)
+ fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+
Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
'hard-copy', 'soft-copy', 'copy']
@@ -113,16 +136,6 @@ def set_duplicate(duplicate):
# underlying implementations. We do this inside this function,
# not in the top-level module code, so that we can remap os.link
# and os.symlink for testing purposes.
- try:
- _hardlink_func = os.link
- except AttributeError:
- _hardlink_func = None
-
- try:
- _softlink_func = os.symlink
- except AttributeError:
- _softlink_func = None
-
link_dict = {
'hard' : _hardlink_func,
'soft' : _softlink_func,
@@ -152,10 +165,11 @@ def LinkFunc(target, source, env):
if not Link_Funcs:
# Set a default order of link functions.
set_duplicate('hard-soft-copy')
+ fs = source[0].fs
# Now link the files with the previously specified order.
for func in Link_Funcs:
try:
- func(src,dest)
+ func(fs, src, dest)
break
except (IOError, OSError):
# An OSError indicates something happened like a permissions
@@ -213,13 +227,15 @@ def CacheRetrieveFunc(target, source, env):
t = target[0]
fs = t.fs
cachedir, cachefile = t.cachepath()
- if fs.exists(cachefile):
- if SCons.Action.execute_actions:
- fs.copy2(cachefile, t.path)
- st = fs.stat(cachefile)
- fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
- return 0
- return 1
+ if not fs.exists(cachefile):
+ fs.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
+ return 1
+ fs.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
+ if SCons.Action.execute_actions:
+ fs.copy2(cachefile, t.path)
+ st = fs.stat(cachefile)
+ fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+ return 0
def CacheRetrieveString(target, source, env):
t = target[0]
@@ -237,9 +253,18 @@ def CachePushFunc(target, source, env):
fs = t.fs
cachedir, cachefile = t.cachepath()
if fs.exists(cachefile):
- # Don't bother copying it if it's already there.
+ # Don't bother copying it if it's already there. Note that
+ # usually this "shouldn't happen" because if the file already
+ # existed in cache, we'd have retrieved the file from there,
+ # not built it. This can happen, though, in a race, if some
+ # other person running the same build pushes their copy to
+ # the cache after we decide we need to build it but before our
+ # build completes.
+ fs.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
return
+ fs.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
+
if not fs.isdir(cachedir):
fs.makedirs(cachedir)
@@ -258,7 +283,6 @@ def CachePushFunc(target, source, env):
SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
"Unable to copy %s to cache. Cache file is %s"
% (str(target), cachefile))
- return
CachePush = SCons.Action.Action(CachePushFunc, None)
@@ -835,6 +859,14 @@ class LocalFS:
def islink(self, path):
return 0 # no symlinks
+ if hasattr(os, 'readlink'):
+ def readlink(self, file):
+ return os.readlink(file)
+ else:
+ def readlink(self, file):
+ return ''
+
+
if SCons.Memoize.use_old_memoization():
_FSBase = LocalFS
class LocalFS(SCons.Memoize.Memoizer, _FSBase):
@@ -1139,6 +1171,21 @@ class FS(LocalFS):
result.extend(dir.get_all_rdirs())
return result
+ def CacheDebugWrite(self, fmt, target, cachefile):
+ self.CacheDebugFP.write(fmt % (target, os.path.split(cachefile)[1]))
+
+ def CacheDebugQuiet(self, fmt, target, cachefile):
+ pass
+
+ CacheDebug = CacheDebugQuiet
+
+ def CacheDebugEnable(self, file):
+ if file == '-':
+ self.CacheDebugFP = sys.stdout
+ else:
+ self.CacheDebugFP = open(file, 'w')
+ self.CacheDebug = self.CacheDebugWrite
+
def CacheDir(self, path):
self.CachePath = path
@@ -1776,7 +1823,7 @@ class File(Base):
__cacheable__"""
if not scanner:
return []
- return map(lambda N: N.disambiguate(), scanner(self, env, path))
+ return scanner(self, env, path)
def _createDir(self):
# ensure that the directories for this node are
@@ -1818,11 +1865,14 @@ class File(Base):
if self.fs.cache_show:
if CacheRetrieveSilent(self, [], None, execute=1) == 0:
self.build(presub=0, execute=0)
+ self.set_state(SCons.Node.executed)
return 1
elif CacheRetrieve(self, [], None, execute=1) == 0:
+ self.set_state(SCons.Node.executed)
return 1
return None
+
def built(self):
"""Called just after this node is successfully built.
__cache_reset__"""
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 98e08a9..1b38ffe 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -425,57 +425,39 @@ class BuildDirTestCase(unittest.TestCase):
class LinkSimulator :
"""A class to intercept os.[sym]link() calls and track them."""
- def __init__( self, duplicate ) :
+ def __init__( self, duplicate, link, symlink, copy ) :
self.duplicate = duplicate
- self._reset()
-
- def _reset( self ) :
- """Reset the simulator if necessary"""
- if not self._need_reset() : return # skip if not needed now
- self.links_to_be_called = self.duplicate
-
- def _need_reset( self ) :
- """
- Determines whether or not the simulator needs to be reset.
- A reset is necessary if the object is first being initialized,
- or if all three methods have been tried already.
- """
- return (
- ( not hasattr( self , "links_to_be_called" ) )
- or
- (self.links_to_be_called == "")
- )
+ self.have = {}
+ self.have['hard'] = link
+ self.have['soft'] = symlink
+ self.have['copy'] = copy
+
+ self.links_to_be_called = []
+ for link in string.split(self.duplicate, '-'):
+ if self.have[link]:
+ self.links_to_be_called.append(link)
def link_fail( self , src , dest ) :
- self._reset()
- l = string.split(self.links_to_be_called, "-")
- next_link = l[0]
- assert next_link == "hard", \
+ next_link = self.links_to_be_called.pop(0)
+ assert next_link == "hard", \
"Wrong link order: expected %s to be called "\
"instead of hard" % next_link
- self.links_to_be_called = string.join(l[1:], '-')
raise OSError( "Simulating hard link creation error." )
def symlink_fail( self , src , dest ) :
- self._reset()
- l = string.split(self.links_to_be_called, "-")
- next_link = l[0]
- assert next_link == "soft", \
+ next_link = self.links_to_be_called.pop(0)
+ assert next_link == "soft", \
"Wrong link order: expected %s to be called "\
"instead of soft" % next_link
- self.links_to_be_called = string.join(l[1:], '-')
raise OSError( "Simulating symlink creation error." )
def copy( self , src , dest ) :
- self._reset()
- l = string.split(self.links_to_be_called, "-")
- next_link = l[0]
- assert next_link == "copy", \
+ next_link = self.links_to_be_called.pop(0)
+ assert next_link == "copy", \
"Wrong link order: expected %s to be called "\
"instead of copy" % next_link
- self.links_to_be_called = string.join(l[1:], '-')
# copy succeeds, but use the real copy
- self._real_copy(src, dest)
+ self.have['copy'](src, dest)
# end class LinkSimulator
try:
@@ -485,32 +467,31 @@ class BuildDirTestCase(unittest.TestCase):
pass
for duplicate in SCons.Node.FS.Valid_Duplicates:
- simulator = LinkSimulator(duplicate)
-
# save the real functions for later restoration
- real_link = None
- real_symlink = None
try:
real_link = os.link
except AttributeError:
- pass
+ real_link = None
try:
real_symlink = os.symlink
except AttributeError:
- pass
+ real_symlink = None
real_copy = shutil.copy2
- simulator._real_copy = real_copy # the simulator needs the real one
+
+ simulator = LinkSimulator(duplicate, real_link, real_symlink, real_copy)
# override the real functions with our simulation
os.link = simulator.link_fail
os.symlink = simulator.symlink_fail
shutil.copy2 = simulator.copy
- SCons.Node.FS.set_duplicate(duplicate)
-
- src_foo = test.workpath('src', 'foo')
- build_foo = test.workpath('build', 'foo')
try:
+
+ SCons.Node.FS.set_duplicate(duplicate)
+
+ src_foo = test.workpath('src', 'foo')
+ build_foo = test.workpath('build', 'foo')
+
test.write(src_foo, 'src/foo\n')
os.chmod(src_foo, stat.S_IRUSR)
try:
diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py
index 99dc5b0..a639aee 100644
--- a/src/engine/SCons/Node/Python.py
+++ b/src/engine/SCons/Node/Python.py
@@ -45,16 +45,18 @@ class Value(SCons.Node.Node):
NodeInfo = ValueNodeInfo
BuildInfo = ValueBuildInfo
- def __init__(self, value):
+ def __init__(self, value, built_value=None):
SCons.Node.Node.__init__(self)
self.value = value
+ if not built_value is None:
+ self.built_value = built_value
def __str__(self):
return repr(self.value)
- def build(self):
- """A "builder" for Values."""
- pass
+ def build(self, **kw):
+ if not hasattr(self, 'built_value'):
+ apply (SCons.Node.Node.build, (self,), kw)
current = SCons.Node.Node.children_are_up_to_date
@@ -64,9 +66,23 @@ class Value(SCons.Node.Node):
# are outside the filesystem:
return 1
+ def write(self, built_value):
+ """Set the value of the node."""
+ self.built_value = built_value
+
+ def read(self):
+ """Return the value. If necessary, the value is built."""
+ self.build()
+ if not hasattr(self, 'built_value'):
+ self.built_value = self.value
+ return self.built_value
+
def get_contents(self):
- """The contents of a Value are the concatenation
- of all the contents of its sources with the node's value itself."""
+ """By the assumption that the node.built_value is a
+ deterministic product of the sources, the contents of a Value
+ are the concatenation of all the contents of its sources. As
+ the value need not be built when get_contents() is called, we
+ cannot use the actual node.built_value."""
contents = str(self.value)
for kid in self.children(None):
contents = contents + kid.get_contents()
diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py
index 0801fb3..62bcf8b 100644
--- a/src/engine/SCons/Node/PythonTests.py
+++ b/src/engine/SCons/Node/PythonTests.py
@@ -45,6 +45,44 @@ class ValueTestCase(unittest.TestCase):
assert not v1 is v2
assert v1.value == v2.value
+ v3 = SCons.Node.Python.Value('c', 'cb')
+ assert v3.built_value == 'cb'
+
+ def test_build(self):
+ """Test "building" a Value Node
+ """
+ class fake_executor:
+ def __call__(self, node, exitstatfunc):
+ node.write('faked')
+
+ v1 = SCons.Node.Python.Value('b', 'built')
+ v1.executor = fake_executor()
+ v1.build()
+ assert v1.built_value == 'built', v1.built_value
+
+ v2 = SCons.Node.Python.Value('b')
+ v2.executor = fake_executor()
+ v2.build()
+ assert v2.built_value == 'faked', v2.built_value
+
+ def test_read(self):
+ """Test the Value.read() method
+ """
+ v1 = SCons.Node.Python.Value('a')
+ x = v1.read()
+ assert x == 'a', x
+
+ def test_write(self):
+ """Test the Value.write() method
+ """
+ v1 = SCons.Node.Python.Value('a')
+ assert v1.value == 'a', v1.value
+ assert not hasattr(v1, 'built_value')
+
+ v1.write('new')
+ assert v1.value == 'a', v1.value
+ assert v1.built_value == 'new', v1.built_value
+
def test_get_csig(self):
"""Test calculating the content signature of a Value() object
"""
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index bda3a48..42be5b1 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -474,6 +474,7 @@ class Node:
d = filter(lambda x, seen=seen: not seen.has_key(x),
n.get_found_includes(env, scanner, path))
if d:
+ d = map(lambda N: N.disambiguate(), d)
deps.extend(d)
for n in d:
seen[n] = 1
@@ -538,7 +539,7 @@ class Node:
# Here's where we implement --implicit-cache.
if implicit_cache and not implicit_deps_changed:
implicit = self.get_stored_implicit()
- if implicit:
+ if implicit is not None:
factory = build_env.get_factory(self.builder.source_factory)
nodes = []
for i in implicit:
diff --git a/src/engine/SCons/Options/ListOption.py b/src/engine/SCons/Options/ListOption.py
index d14b22a..5aa508a 100644
--- a/src/engine/SCons/Options/ListOption.py
+++ b/src/engine/SCons/Options/ListOption.py
@@ -65,7 +65,7 @@ class _ListOption(UserList.UserList):
UserList.UserList.__init__(self, filter(None, initlist))
self.allowedElems = allowedElems[:]
self.allowedElems.sort()
-
+
def __cmp__(self, other):
raise NotImplementedError
def __eq__(self, other):
@@ -88,7 +88,7 @@ class _ListOption(UserList.UserList):
return string.join(self, ',')
def __repr__(self):
return self.__str__()
-
+
def _converter(val, allowedElems, mapdict):
"""
"""
diff --git a/src/engine/SCons/Options/ListOptionTests.py b/src/engine/SCons/Options/ListOptionTests.py
index a892e18..22378bc 100644
--- a/src/engine/SCons/Options/ListOptionTests.py
+++ b/src/engine/SCons/Options/ListOptionTests.py
@@ -122,9 +122,6 @@ class ListOptionTestCase(unittest.TestCase):
l = o.converter('all')
n = l.__class__(copy.copy(l))
- def test___repr__(self):
- """Test copying a ListOption like an Environment would"""
-
if __name__ == "__main__":
suite = unittest.makeSuite(ListOptionTestCase, 'test_')
if not unittest.TextTestRunner().run(suite).wasSuccessful():
diff --git a/src/engine/SCons/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py
index aeffe2d..1d9b851 100644
--- a/src/engine/SCons/Options/OptionsTests.py
+++ b/src/engine/SCons/Options/OptionsTests.py
@@ -59,6 +59,19 @@ def checkSave(file, expected):
class OptionsTestCase(unittest.TestCase):
+ def test_keys(self):
+ """Test the Options.keys() method"""
+ opts = SCons.Options.Options()
+
+ opts.Add('VAR1')
+ opts.Add('VAR2',
+ 'THE answer to THE question',
+ "42",
+ check,
+ lambda x: int(x) + 12)
+ keys = opts.keys()
+ assert keys == ['VAR1', 'VAR2'], keys
+
def test_Add(self):
"""Test adding to an Options object"""
opts = SCons.Options.Options()
diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py
index 7333985..83798b3 100644
--- a/src/engine/SCons/Options/__init__.py
+++ b/src/engine/SCons/Options/__init__.py
@@ -76,6 +76,11 @@ class Options:
self.options.append(option)
+ def keys(self):
+ """
+ Returns the keywords for the options
+ """
+ return map(lambda o: o.key, self.options)
def Add(self, key, help="", default=None, validator=None, converter=None, **kw):
"""
@@ -100,7 +105,6 @@ class Options:
self._do_add(key, help, default, validator, converter)
-
def AddOptions(self, *optlist):
"""
Add a list of options.
diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py
index 675167e..1d4e9f7 100644
--- a/src/engine/SCons/Platform/posix.py
+++ b/src/engine/SCons/Platform/posix.py
@@ -70,7 +70,7 @@ def exec_spawnvpe(l, env):
# returned by os.waitpid() or os.system().
return stat
-def exec_fork(l, env):
+def exec_fork(l, env):
pid = os.fork()
if not pid:
# Child process.
@@ -78,7 +78,7 @@ def exec_fork(l, env):
try:
os.execvpe(l[0], l, env)
except OSError, e:
- exitval = exitvalmap[e[0]]
+ exitval = exitvalmap.get(e[0], e[0])
sys.stderr.write("scons: %s: %s\n" % (l[0], e[1]))
os._exit(exitval)
else:
@@ -159,7 +159,7 @@ def exec_piped_fork(l, env, stdout, stderr):
try:
os.execvpe(l[0], l, env)
except OSError, e:
- exitval = exitvalmap[e[0]]
+ exitval = exitvalmap.get(e[0], e[0])
stderr.write("scons: %s: %s\n" % (l[0], e[1]))
os._exit(exitval)
else:
diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py
index 6188098..148d9df 100644
--- a/src/engine/SCons/Platform/win32.py
+++ b/src/engine/SCons/Platform/win32.py
@@ -90,7 +90,11 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
ret = os.spawnve(os.P_WAIT, sh, args, env)
except OSError, e:
# catch any error
- ret = exitvalmap[e[0]]
+ try:
+ ret = exitvalmap[e[0]]
+ except KeyError:
+ result = 127
+ sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e[0], cmd, e[1]))
if stderr != None:
stderr.write("scons: %s: %s\n" % (cmd, e[1]))
# copy child output from tempfiles to our streams
@@ -114,8 +118,19 @@ def exec_spawn(l, env):
try:
result = os.spawnve(os.P_WAIT, l[0], l, env)
except OSError, e:
- result = exitvalmap[e[0]]
- sys.stderr.write("scons: %s: %s\n" % (l[0], e[1]))
+ try:
+ result = exitvalmap[e[0]]
+ sys.stderr.write("scons: %s: %s\n" % (l[0], e[1]))
+ except KeyError:
+ result = 127
+ if len(l) > 2:
+ if len(l[2]) < 1000:
+ command = string.join(l[0:3])
+ else:
+ command = l[0]
+ else:
+ command = l[0]
+ sys.stderr.write("scons: unknown OSError exception code %d - '%s': %s\n" % (e[0], command, e[1]))
return result
def spawn(sh, escape, cmd, args, env):
@@ -124,9 +139,15 @@ def spawn(sh, escape, cmd, args, env):
return 127
return exec_spawn([sh, '/C', escape(string.join(args))], env)
-# Windows does not allow special characters in file names anyway, so
-# no need for a complex escape function, we will just quote the arg.
-escape = lambda x: '"' + x + '"'
+# Windows does not allow special characters in file names anyway, so no
+# need for a complex escape function, we will just quote the arg, except
+# that "cmd /c" requires that if an argument ends with a backslash it
+# needs to be escaped so as not to interfere with closing double quote
+# that we add.
+def escape(x):
+ if x[-1] == '\\':
+ x = x + '\\'
+ return '"' + x + '"'
# Get the windows system directory name
def get_system_root():
diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py
index c57e7f7..2ca522b 100644
--- a/src/engine/SCons/Scanner/CTests.py
+++ b/src/engine/SCons/Scanner/CTests.py
@@ -221,6 +221,7 @@ def deps_match(self, deps, headers):
class CScannerTestCase1(unittest.TestCase):
def runTest(self):
+ """Find local files with no CPPPATH"""
env = DummyEnvironment(CPPPATH=[])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -230,6 +231,7 @@ class CScannerTestCase1(unittest.TestCase):
class CScannerTestCase2(unittest.TestCase):
def runTest(self):
+ """Find a file in a CPPPATH directory"""
env = DummyEnvironment(CPPPATH=[test.workpath("d1")])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -239,6 +241,7 @@ class CScannerTestCase2(unittest.TestCase):
class CScannerTestCase3(unittest.TestCase):
def runTest(self):
+ """Find files in explicit subdirectories, ignore missing file"""
env = DummyEnvironment(CPPPATH=[test.workpath("d1")])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -248,6 +251,7 @@ class CScannerTestCase3(unittest.TestCase):
class CScannerTestCase4(unittest.TestCase):
def runTest(self):
+ """Find files in explicit subdirectories"""
env = DummyEnvironment(CPPPATH=[test.workpath("d1"), test.workpath("d1/d2")])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -257,6 +261,7 @@ class CScannerTestCase4(unittest.TestCase):
class CScannerTestCase5(unittest.TestCase):
def runTest(self):
+ """Make sure files in repositories will get scanned"""
env = DummyEnvironment(CPPPATH=[])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -280,6 +285,7 @@ class CScannerTestCase5(unittest.TestCase):
class CScannerTestCase6(unittest.TestCase):
def runTest(self):
+ """Find a same-named file in different directories when CPPPATH changes"""
env1 = DummyEnvironment(CPPPATH=[test.workpath("d1")])
env2 = DummyEnvironment(CPPPATH=[test.workpath("d1/d2")])
s = SCons.Scanner.C.CScanner()
@@ -294,6 +300,7 @@ class CScannerTestCase6(unittest.TestCase):
class CScannerTestCase8(unittest.TestCase):
def runTest(self):
+ """Find files in a subdirectory relative to the current directory"""
env = DummyEnvironment(CPPPATH=["include"])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -310,6 +317,7 @@ class CScannerTestCase8(unittest.TestCase):
class CScannerTestCase9(unittest.TestCase):
def runTest(self):
+ """Generate a warning when we can't find a #included file"""
SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning)
class TestOut:
def __call__(self, x):
@@ -334,6 +342,7 @@ class CScannerTestCase9(unittest.TestCase):
class CScannerTestCase10(unittest.TestCase):
def runTest(self):
+ """Find files in the local directory when the scanned file is elsewhere"""
fs = SCons.Node.FS.FS(test.workpath(''))
fs.chdir(fs.Dir('include'))
env = DummyEnvironment(CPPPATH=[])
@@ -348,6 +357,7 @@ class CScannerTestCase10(unittest.TestCase):
class CScannerTestCase11(unittest.TestCase):
def runTest(self):
+ """Handle dependencies on a derived .h file in a non-existent directory"""
os.chdir(test.workpath('work'))
fs = SCons.Node.FS.FS(test.workpath('work'))
fs.Repository(test.workpath('repository'))
@@ -367,6 +377,7 @@ class CScannerTestCase11(unittest.TestCase):
class CScannerTestCase12(unittest.TestCase):
def runTest(self):
+ """Find files in BuildDir() directories"""
os.chdir(test.workpath('work'))
fs = SCons.Node.FS.FS(test.workpath('work'))
fs.BuildDir('build1', 'src', 1)
@@ -388,6 +399,7 @@ class CScannerTestCase12(unittest.TestCase):
class CScannerTestCase13(unittest.TestCase):
def runTest(self):
+ """Find files in directories named in a substituted environment variable"""
class SubstEnvironment(DummyEnvironment):
def subst(self, arg, test=test):
return test.workpath("d1")
@@ -400,6 +412,7 @@ class CScannerTestCase13(unittest.TestCase):
class CScannerTestCase14(unittest.TestCase):
def runTest(self):
+ """Find files when there's no space between "#include" and the name"""
env = DummyEnvironment(CPPPATH=[])
s = SCons.Scanner.C.CScanner()
path = s.path(env)
@@ -409,6 +422,7 @@ class CScannerTestCase14(unittest.TestCase):
class CScannerTestCase15(unittest.TestCase):
def runTest(self):
+ """Verify scanner initialization with the suffixes in $CPPSUFFIXES"""
suffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
".h", ".H", ".hxx", ".hpp", ".hh",
".F", ".fpp", ".FPP",
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index 73f96e2..2c18112 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -588,29 +588,29 @@ def _create_path(plist):
path = path + '/' + d
return path
+def version_string(label, module):
+ fmt = "\t%s: v%s.%s, %s, by %s on %s\n"
+ return fmt % (label,
+ module.__version__,
+ module.__build__,
+ module.__date__,
+ module.__developer__,
+ module.__buildsys__)
class OptParser(OptionParser):
def __init__(self):
import __main__
- import SCons
+
parts = ["SCons by Steven Knight et al.:\n"]
try:
- parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
- __main__.__build__,
- __main__.__date__,
- __main__.__developer__,
- __main__.__buildsys__))
+ parts.append(version_string("script", __main__))
except KeyboardInterrupt:
raise
except:
# On Windows there is no scons.py, so there is no
# __main__.__version__, hence there is no script version.
pass
- parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
- SCons.__build__,
- SCons.__date__,
- SCons.__developer__,
- SCons.__buildsys__))
+ parts.append(version_string("engine", SCons))
parts.append("__COPYRIGHT__")
OptionParser.__init__(self, version=string.join(parts, ''),
usage="usage: scons [OPTION] [TARGET] ...")
@@ -630,6 +630,10 @@ class OptParser(OptionParser):
metavar="DIR",
help="Change to DIR before doing anything.")
+ self.add_option('--cache-debug', action="store",
+ dest="cache_debug", metavar="FILE",
+ help="Print CacheDir debug info to FILE.")
+
self.add_option('--cache-disable', '--no-cache',
action="store_true", dest='cache_disable', default=0,
help="Do not retrieve built targets from CacheDir.")
@@ -1048,6 +1052,9 @@ def _main(args, parser):
display.set_mode(0)
if options.silent:
SCons.Action.print_actions = None
+
+ if options.cache_debug:
+ fs.CacheDebugEnable(options.cache_debug)
if options.cache_disable:
def disable(self): pass
fs.CacheDir = disable
@@ -1291,8 +1298,21 @@ def _exec_main():
import pdb
pdb.Pdb().runcall(_main, args, parser)
elif options.profile_file:
- import profile
- prof = profile.Profile()
+ from profile import Profile
+
+ # Some versions of Python 2.4 shipped a profiler that had the
+ # wrong 'c_exception' entry in its dispatch table. Make sure
+ # we have the right one. (This may put an unnecessary entry
+ # in the table in earlier versions of Python, but its presence
+ # shouldn't hurt anything).
+ try:
+ dispatch = Profile.dispatch
+ except AttributeError:
+ pass
+ else:
+ dispatch['c_exception'] = Profile.trace_dispatch_return
+
+ prof = Profile()
try:
prof.runcall(_main, args, parser)
except SystemExit:
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index f932d30..dc896a0 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -176,7 +176,7 @@ def _SConscript(fs, *files, **kw):
# fs match so we can open the SConscript.
fs.chdir(top, change_os_dir=1)
if f.rexists():
- _file_ = open(f.rstr(), "r")
+ _file_ = open(f.rfile().get_abspath(), "r")
elif f.has_src_builder():
# The SConscript file apparently exists in a source
# code management system. Build it, but then clear
@@ -185,7 +185,7 @@ def _SConscript(fs, *files, **kw):
f.build()
f.builder_set(None)
if f.exists():
- _file_ = open(str(f), "r")
+ _file_ = open(f.get_abspath(), "r")
if _file_:
# Chdir to the SConscript directory. Use a path
# name relative to the SConstruct file so that if
@@ -197,7 +197,18 @@ def _SConscript(fs, *files, **kw):
# where the SConstruct and SConscript files might be
# in different Repositories. For now, cross that
# bridge when someone comes to it.
- ldir = fs.Dir(f.dir.get_path(sd))
+ try:
+ src_dir = kw['src_dir']
+ except KeyError:
+ ldir = fs.Dir(f.dir.get_path(sd))
+ else:
+ ldir = fs.Dir(src_dir)
+ if not ldir.is_under(f.dir):
+ # They specified a source directory, but
+ # it's above the SConscript directory.
+ # Do the sensible thing and just use the
+ # SConcript directory.
+ ldir = fs.Dir(f.dir.get_path(sd))
try:
fs.chdir(ldir, change_os_dir=sconscript_chdir)
except OSError:
@@ -208,6 +219,7 @@ def _SConscript(fs, *files, **kw):
# interpret the stuff within the SConscript file
# relative to where we are logically.
fs.chdir(ldir, change_os_dir=0)
+ # TODO Not sure how to handle src_dir here
os.chdir(f.rfile().dir.get_abspath())
# Append the SConscript directory to the beginning
@@ -223,7 +235,16 @@ def _SConscript(fs, *files, **kw):
# SConscript can base the printed frames at this
# level and not show SCons internals as well.
call_stack[-1].globals.update({stack_bottom:1})
- exec _file_ in call_stack[-1].globals
+ old_file = call_stack[-1].globals.get('__file__')
+ try:
+ del call_stack[-1].globals['__file__']
+ except KeyError:
+ pass
+ try:
+ exec _file_ in call_stack[-1].globals
+ finally:
+ if old_file is not None:
+ call_stack[-1].globals.update({__file__:old_file})
else:
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
"Ignoring missing SConscript '%s'" % f.path)
@@ -374,6 +395,7 @@ class SConsEnvironment(SCons.Environment.Base):
src_dir = kw.get('src_dir')
if not src_dir:
src_dir, fname = os.path.split(str(files[0]))
+ files = [os.path.join(str(build_dir), fname)]
else:
if not isinstance(src_dir, SCons.Node.Node):
src_dir = self.fs.Dir(src_dir)
@@ -383,11 +405,11 @@ class SConsEnvironment(SCons.Environment.Base):
if fn.is_under(src_dir):
# Get path relative to the source directory.
fname = fn.get_path(src_dir)
+ files = [os.path.join(str(build_dir), fname)]
else:
- # Fast way to only get the terminal path component of a Node.
- fname = fn.get_path(fn.dir)
+ files = [fn.abspath]
+ kw['src_dir'] = build_dir
self.fs.BuildDir(build_dir, src_dir, duplicate)
- files = [os.path.join(str(build_dir), fname)]
return (files, exports)
@@ -490,8 +512,8 @@ class SConsEnvironment(SCons.Environment.Base):
subst_kw[key] = val
files, exports = self._get_SConscript_filenames(ls, subst_kw)
-
- return apply(_SConscript, [self.fs,] + files, {'exports' : exports})
+ subst_kw['exports'] = exports
+ return apply(_SConscript, [self.fs,] + files, subst_kw)
def SConscriptChdir(self, flag):
global sconscript_chdir
diff --git a/src/engine/SCons/Tool/as.py b/src/engine/SCons/Tool/as.py
index 5a267a5..1b1b4b3 100644
--- a/src/engine/SCons/Tool/as.py
+++ b/src/engine/SCons/Tool/as.py
@@ -52,11 +52,15 @@ def generate(env):
for suffix in ASSuffixes:
static_obj.add_action(suffix, SCons.Defaults.ASAction)
+ shared_obj.add_action(suffix, SCons.Defaults.ASAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
+ shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
for suffix in ASPPSuffixes:
static_obj.add_action(suffix, SCons.Defaults.ASPPAction)
+ shared_obj.add_action(suffix, SCons.Defaults.ASPPAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
+ shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
env['AS'] = env.Detect(assemblers) or 'as'
env['ASFLAGS'] = SCons.Util.CLVar('')
diff --git a/src/engine/SCons/Tool/fortran.py b/src/engine/SCons/Tool/fortran.py
index b694a58..b83e7d3 100644
--- a/src/engine/SCons/Tool/fortran.py
+++ b/src/engine/SCons/Tool/fortran.py
@@ -146,7 +146,7 @@ def add_to_env(env):
env['FORTRANMODDIR'] = '' # where the compiler should place .mod files
env['FORTRANMODDIRPREFIX'] = '' # some prefix to $FORTRANMODDIR - similar to $INCPREFIX
env['FORTRANMODDIRSUFFIX'] = '' # some suffix to $FORTRANMODDIR - similar to $INCSUFFIX
- env['_FORTRANMODFLAG'] = '$( ${_concat(FORTRANMODDIRPREFIX, FORTRANMODDIR, FORTRANMODDIRSUFFIX, __env__)} $)'
+ env['_FORTRANMODFLAG'] = '$( ${_concat(FORTRANMODDIRPREFIX, FORTRANMODDIR, FORTRANMODDIRSUFFIX, __env__, RDirs)} $)'
env.AppendUnique(FORTRANSUFFIXES = FortranSuffixes + FortranPPSuffixes)
diff --git a/src/engine/SCons/Tool/gcc.py b/src/engine/SCons/Tool/gcc.py
index 1be665a..ad02e0d 100644
--- a/src/engine/SCons/Tool/gcc.py
+++ b/src/engine/SCons/Tool/gcc.py
@@ -46,7 +46,7 @@ def generate(env):
cc.generate(env)
env['CC'] = env.Detect(compilers) or 'gcc'
- if env['PLATFORM'] == 'cygwin':
+ if env['PLATFORM'] in ['cygwin', 'win32']:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
else:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')
diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py
index 5765e25..d679b53 100644
--- a/src/engine/SCons/Tool/mingw.py
+++ b/src/engine/SCons/Tool/mingw.py
@@ -118,7 +118,7 @@ def generate(env):
# Most of mingw is the same as gcc and friends...
- gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas']
+ gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'm4']
for tool in gnu_tools:
SCons.Tool.Tool(tool)(env)
@@ -130,7 +130,6 @@ def generate(env):
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
env['SHLINKCOM'] = shlib_action
env.Append(SHLIBEMITTER = [shlib_emitter])
- env['LINK'] = 'g++'
env['AS'] = 'as'
env['WIN32DEFPREFIX'] = ''
diff --git a/src/engine/SCons/Tool/mslink.xml b/src/engine/SCons/Tool/mslink.xml
index 58cfb1d..6499cfe 100644
--- a/src/engine/SCons/Tool/mslink.xml
+++ b/src/engine/SCons/Tool/mslink.xml
@@ -37,6 +37,20 @@ Example:
<example>
env['PDB'] = 'hello.pdb'
</example>
+
+The Visual C++ compiler switch that SCons uses by default
+to generate PDB information is <option>/Z7</option>.
+This works correctly with parallel (<option>-j</option>) builds
+because it embeds the debug information in the intermediate object files,
+as opposed to sharing a single PDB file between multiple object files.
+This is also the only way to get debug information
+embedded into a static library.
+Using the <option>/Zi</option> instead may yield improved
+link-time performance,
+although parallel builds will no longer work.
+You can generate PDB files with the <option>/Zi</option>
+switch by overriding the default &cv-CCPDBFLAGS; variable;
+see the entry for that variable for specific examples.
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py
index 86cde78..80b5926 100644
--- a/src/engine/SCons/Tool/msvc.py
+++ b/src/engine/SCons/Tool/msvc.py
@@ -517,9 +517,10 @@ def _get_msvc8_default_paths(env, version, suite, use_mfc_dirs):
include_paths.append( env_include_path )
if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKSDKDIR'):
- include_paths.append( os.path.join( paths['FRAMEWORKSDKDIR'], 'include' ) )
- lib_paths.append( os.path.join( paths['FRAMEWORKSDKDIR'], 'lib' ) )
- exe_paths.append( paths['FRAMEWORKSDKDIR'], 'bin' )
+ fwdir = paths['FRAMEWORKSDKDIR']
+ include_paths.append( os.path.join( fwdir, 'include' ) )
+ lib_paths.append( os.path.join( fwdir, 'lib' ) )
+ exe_paths.append( os.path.join( fwdir, 'bin' ) )
if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKDIR') and paths.has_key('FRAMEWORKVERSION'):
exe_paths.append( os.path.join( paths['FRAMEWORKDIR'], paths['FRAMEWORKVERSION'] ) )
@@ -658,7 +659,10 @@ pch_builder = SCons.Builder.Builder(action=pch_action, suffix='.pch',
emitter=pch_emitter,
source_scanner=SCons.Tool.SourceFileScanner)
res_action = SCons.Action.Action('$RCCOM', '$RCCOMSTR')
-res_builder = SCons.Builder.Builder(action=res_action, suffix='.res',
+res_builder = SCons.Builder.Builder(action=res_action,
+ src_suffix='.rc',
+ suffix='.res',
+ src_builder=[],
source_scanner=SCons.Tool.SourceFileScanner)
SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan)
diff --git a/src/engine/SCons/Tool/msvc.xml b/src/engine/SCons/Tool/msvc.xml
index 252d962..1c0f3fa 100644
--- a/src/engine/SCons/Tool/msvc.xml
+++ b/src/engine/SCons/Tool/msvc.xml
@@ -47,6 +47,53 @@ env.RES('resource.rc')
</summary>
</builder>
+<cvar name="CCPCHFLAGS">
+<summary>
+Options added to the compiler command line
+to support building with precompiled headers.
+The default value expands expands to the appropriate
+Microsoft Visual C++ command-line options
+when the &cv-PCH; construction variable is set.
+</summary>
+
+<cvar name="CCPDBFLAGS">
+<summary>
+Options added to the compiler command line
+to support storing debugging information in a
+Microsoft Visual C++ PDB file.
+The default value expands expands to appropriate
+Microsoft Visual C++ command-line options
+when the &cv-PDB; construction variable is set.
+
+The Visual C++ compiler option that SCons uses by default
+to generate PDB information is <option>/Z7</option>.
+This works correctly with parallel (<option>-j</option>) builds
+because it embeds the debug information in the intermediate object files,
+as opposed to sharing a single PDB file between multiple object files.
+This is also the only way to get debug information
+embedded into a static library.
+Using the <option>/Zi</option> instead may yield improved
+link-time performance,
+although parallel builds will no longer work.
+
+You can generate PDB files with the <option>/Zi</option>
+switch by overriding the default &cv-CCPDBFLAGS; variable as follows:
+
+<example>
+import SCons.Util
+env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Zi /Fd%s" % File(PDB)) or ""}'])
+</example>
+
+An alternative would be to use the <option>/Zi</option>
+to put the debugging information in a separate <filename>.pdb</filename>
+file for each object file by overriding
+the &cv-CCPDBFLAGS; variable as follows:
+
+<example>
+env['CCPDBFLAGS'] = '/Zi /Fd${TARGET}.pdb'
+</example>
+</summary>
+
<cvar name="PCH">
<summary>
The Microsoft Visual C++ precompiled header that will be used when compiling
diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py
index e8aaf83..e35c92a 100644
--- a/src/engine/SCons/Tool/msvs.py
+++ b/src/engine/SCons/Tool/msvs.py
@@ -297,7 +297,7 @@ class _DSPGenerator:
for n in sourcenames:
self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
- def AddConfig(variant, buildtarget, outdir, runfile, cmdargs):
+ def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
config = Config()
config.buildtarget = buildtarget
config.outdir = outdir
@@ -316,7 +316,7 @@ class _DSPGenerator:
print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
for i in range(len(variants)):
- AddConfig(variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
+ AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
self.platforms = []
for key in self.configs.keys():
@@ -690,6 +690,29 @@ class _GenerateV7DSP(_DSPGenerator):
pdata = base64.encodestring(pdata)
self.file.write(pdata + '-->\n')
+ def printSources(self, hierarchy, commonprefix):
+ sorteditems = hierarchy.items()
+ sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
+
+ # First folders, then files
+ for key, value in sorteditems:
+ if SCons.Util.is_Dict(value):
+ self.file.write('\t\t\t<Filter\n'
+ '\t\t\t\tName="%s"\n'
+ '\t\t\t\tFilter="">\n' % (key))
+ self.printSources(value, commonprefix)
+ self.file.write('\t\t\t</Filter>\n')
+
+ for key, value in sorteditems:
+ if SCons.Util.is_String(value):
+ file = value
+ if commonprefix:
+ file = os.path.join(commonprefix, value)
+ file = os.path.normpath(file)
+ self.file.write('\t\t\t<File\n'
+ '\t\t\t\tRelativePath="%s">\n'
+ '\t\t\t</File>\n' % (file))
+
def PrintSourceFiles(self):
categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
'Header Files': 'h;hpp;hxx;hm;inl',
@@ -708,43 +731,26 @@ class _GenerateV7DSP(_DSPGenerator):
'\t\t\tName="%s"\n'
'\t\t\tFilter="%s">\n' % (kind, categories[kind]))
-
- def printSources(hierarchy, commonprefix):
- sorteditems = hierarchy.items()
- sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
-
- # First folders, then files
- for key, value in sorteditems:
- if SCons.Util.is_Dict(value):
- self.file.write('\t\t\t<Filter\n'
- '\t\t\t\tName="%s"\n'
- '\t\t\t\tFilter="">\n' % (key))
- printSources(value, commonprefix)
- self.file.write('\t\t\t</Filter>\n')
-
- for key, value in sorteditems:
- if SCons.Util.is_String(value):
- file = value
- if commonprefix:
- file = os.path.join(commonprefix, value)
- file = os.path.normpath(file)
- self.file.write('\t\t\t<File\n'
- '\t\t\t\tRelativePath="%s">\n'
- '\t\t\t</File>\n' % (file))
-
sources = self.sources[kind]
# First remove any common prefix
commonprefix = None
if len(sources) > 1:
s = map(os.path.normpath, sources)
- cp = os.path.commonprefix(s)
+ # take the dirname because the prefix may include parts
+ # of the filenames (e.g. if you have 'dir\abcd' and
+ # 'dir\acde' then the cp will be 'dir\a' )
+ cp = os.path.dirname( os.path.commonprefix(s) )
if cp and s[0][len(cp)] == os.sep:
- sources = map(lambda s, l=len(cp): s[l:], sources)
+ # +1 because the filename starts after the separator
+ sources = map(lambda s, l=len(cp)+1: s[l:], sources)
commonprefix = cp
+ elif len(sources) == 1:
+ commonprefix = os.path.dirname( sources[0] )
+ sources[0] = os.path.basename( sources[0] )
hierarchy = makeHierarchy(sources)
- printSources(hierarchy, commonprefix=commonprefix)
+ self.printSources(hierarchy, commonprefix=commonprefix)
if len(cats)>1:
self.file.write('\t\t</Filter>\n')
@@ -873,7 +879,7 @@ class _GenerateV7DSW(_DSWGenerator):
if self.nokeep == 0 and os.path.exists(self.dswfile):
self.Parse()
- def AddConfig(variant):
+ def AddConfig(self, variant, dswfile=dswfile):
config = Config()
match = re.match('(.*)\|(.*)', variant)
@@ -892,10 +898,10 @@ class _GenerateV7DSW(_DSWGenerator):
"You must specify a 'variant' argument (i.e. 'Debug' or " +\
"'Release') to create an MSVS Solution File."
elif SCons.Util.is_String(env['variant']):
- AddConfig(env['variant'])
+ AddConfig(self, env['variant'])
elif SCons.Util.is_List(env['variant']):
for variant in env['variant']:
- AddConfig(variant)
+ AddConfig(self, variant)
self.platforms = []
for key in self.configs.keys():
@@ -1127,32 +1133,24 @@ def GenerateDSW(dswfile, source, env):
def get_default_visualstudio_version(env):
"""Returns the version set in the env, or the latest version
installed, if it can find it, or '6.0' if all else fails. Also
- updated the environment with what it found."""
+ updates the environment with what it found."""
- version = '6.0'
- versions = [version]
+ versions = ['6.0']
if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
- env['MSVS'] = {}
-
- if env['MSVS'].has_key('VERSIONS'):
- versions = env['MSVS']['VERSIONS']
- elif SCons.Util.can_read_reg:
- v = get_visualstudio_versions()
- if v:
- versions = v
- if env.has_key('MSVS_VERSION'):
- version = env['MSVS_VERSION']
- else:
- version = versions[0] #use highest version by default
-
- env['MSVS_VERSION'] = version
- env['MSVS']['VERSIONS'] = versions
- env['MSVS']['VERSION'] = version
+ v = get_visualstudio_versions()
+ if v:
+ versions = v
+ env['MSVS'] = {'VERSIONS' : versions}
else:
- version = env['MSVS']['VERSION']
+ versions = env['MSVS'].get('VERSIONS', versions)
+
+ if not env.has_key('MSVS_VERSION'):
+ env['MSVS_VERSION'] = versions[0] #use highest version by default
+
+ env['MSVS']['VERSION'] = env['MSVS_VERSION']
- return version
+ return env['MSVS_VERSION']
def get_visualstudio_versions():
"""
diff --git a/src/engine/SCons/Tool/msvsTests.py b/src/engine/SCons/Tool/msvsTests.py
index 6095dff..b1125e7 100644
--- a/src/engine/SCons/Tool/msvsTests.py
+++ b/src/engine/SCons/Tool/msvsTests.py
@@ -471,6 +471,18 @@ class msvsTestCase(unittest.TestCase):
assert env['MSVS']['VERSION'] == '7.0', env['MSVS']['VERSION']
assert v2 == '7.0', v2
+ env = DummyEnv()
+ v3 = get_default_visualstudio_version(env)
+ if v3 == '7.1':
+ override = '7.0'
+ else:
+ override = '7.1'
+ env['MSVS_VERSION'] = override
+ v3 = get_default_visualstudio_version(env)
+ assert env['MSVS_VERSION'] == override, env['MSVS_VERSION']
+ assert env['MSVS']['VERSION'] == override, env['MSVS']['VERSION']
+ assert v3 == override, v3
+
def test_get_visual_studio_versions(self):
"""Test retrieval of the list of visual studio versions"""
v1 = get_visualstudio_versions()
diff --git a/src/engine/SCons/cpp.py b/src/engine/SCons/cpp.py
new file mode 100644
index 0000000..9bf5779
--- /dev/null
+++ b/src/engine/SCons/cpp.py
@@ -0,0 +1,561 @@
+#
+# __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__"
+
+__doc__ = """
+SCons C Pre-Processor module
+"""
+
+import os
+import re
+import string
+import sys
+
+
+
+import __builtin__
+try:
+ __builtin__.zip
+except AttributeError:
+ def zip(*lists):
+ result = []
+ for i in xrange(len(lists[0])):
+ result.append(tuple(map(lambda l, i=i: l[i], lists)))
+ return result
+
+
+
+#
+# First "subsystem" of regular expressions that we set up:
+#
+# Stuff to turn the C preprocessor directives in a file's contents into
+# a list of tuples that we can process easily.
+#
+
+# A table of regular expressions that fetch the arguments from the rest of
+# a C preprocessor line. Different directives have different arguments
+# that we want to fetch, using the regular expressions to which the lists
+# of preprocessor directives map.
+cpp_lines_dict = {
+ # Fetch the rest of a #if/#elif/#ifdef/#ifndef/#import/#include/
+ # #include_next line as one argument.
+ ('if', 'elif', 'ifdef', 'ifndef', 'import', 'include', 'include_next',)
+ : '\s+(.+)',
+
+ # We don't care what comes after a #else or #endif line.
+ ('else', 'endif',) : '',
+
+ # Fetch three arguments from a #define line:
+ # 1) The #defined keyword.
+ # 2) The optional parentheses and arguments (if it's a function-like
+ # macro, '' if it's not).
+ # 3) The expansion value.
+ ('define',) : '\s+([_A-Za-z][_A-Za-z0-9_]+)(\([^)]*\))?\s*(.*)',
+
+ # Fetch the #undefed keyword from a #undef line.
+ ('undef',) : '\s+([_A-Za-z][A-Za-z0-9_]+)',
+}
+
+# Create a table that maps each individual C preprocessor directive to
+# the corresponding compiled regular expression that fetches the arguments
+# we care about.
+Table = {}
+for op_list, expr in cpp_lines_dict.items():
+ e = re.compile(expr)
+ for op in op_list:
+ Table[op] = e
+del e
+del op
+del op_list
+
+# Create a list of the expressions we'll use to match all of the
+# preprocessor directives. These are the same as the directives
+# themselves *except* that we must use a negative lookahead assertion
+# when matching "if" so it doesn't match the "if" in "ifdef."
+override = {
+ 'if' : 'if(?!def)',
+}
+l = map(lambda x, o=override: o.get(x, x), Table.keys())
+
+
+# Turn the list of expressions into one big honkin' regular expression
+# that will match all the preprocessor lines at once. This will return
+# a list of tuples, one for each preprocessor line. The preprocessor
+# directive will be the first element in each tuple, and the rest of
+# the line will be the second element.
+e = '^\s*#\s*(' + string.join(l, '|') + ')(.*)$'
+
+# And last but not least, compile the expression.
+CPP_Expression = re.compile(e, re.M)
+
+
+
+
+#
+# Second "subsystem" of regular expressions that we set up:
+#
+# Stuff to translate a C preprocessor expression (as found on a #if or
+# #elif line) into an equivalent Python expression that we can eval().
+#
+
+# A dictionary that maps the C representation of Boolean operators
+# to their Python equivalents.
+CPP_to_Python_Ops_Dict = {
+ '!' : ' not ',
+ '!=' : ' != ',
+ '&&' : ' and ',
+ '||' : ' or ',
+ '?' : ' and ',
+ ':' : ' or ',
+ '\r' : '',
+}
+
+CPP_to_Python_Ops_Sub = lambda m, d=CPP_to_Python_Ops_Dict: d[m.group(0)]
+
+# We have to sort the keys by length so that longer expressions
+# come *before* shorter expressions--in particular, "!=" must
+# come before "!" in the alternation. Without this, the Python
+# re module, as late as version 2.2.2, empirically matches the
+# "!" in "!=" first, instead of finding the longest match.
+# What's up with that?
+l = CPP_to_Python_Ops_Dict.keys()
+l.sort(lambda a, b: cmp(len(b), len(a)))
+
+# Turn the list of keys into one regular expression that will allow us
+# to substitute all of the operators at once.
+expr = string.join(map(re.escape, l), '|')
+
+# ...and compile the expression.
+CPP_to_Python_Ops_Expression = re.compile(expr)
+
+# A separate list of expressions to be evaluated and substituted
+# sequentially, not all at once.
+CPP_to_Python_Eval_List = [
+ ['defined\s+(\w+)', '__dict__.has_key("\\1")'],
+ ['defined\s*\((\w+)\)', '__dict__.has_key("\\1")'],
+ ['/\*.*\*/', ''],
+ ['/\*.*', ''],
+ ['//.*', ''],
+ ['(0x[0-9A-Fa-f]*)[UL]+', '\\1L'],
+]
+
+# Replace the string representations of the regular expressions in the
+# list with compiled versions.
+for l in CPP_to_Python_Eval_List:
+ l[0] = re.compile(l[0])
+
+# Wrap up all of the above into a handy function.
+def CPP_to_Python(s):
+ """
+ Converts a C pre-processor expression into an equivalent
+ Python expression that can be evaluated.
+ """
+ s = CPP_to_Python_Ops_Expression.sub(CPP_to_Python_Ops_Sub, s)
+ for expr, repl in CPP_to_Python_Eval_List:
+ s = expr.sub(repl, s)
+ return s
+
+
+
+del expr
+del l
+del override
+
+
+
+class FunctionEvaluator:
+ """
+ Handles delayed evaluation of a #define function call.
+ """
+ def __init__(self, name, args, expansion):
+ """
+ Squirrels away the arguments and expansion value of a #define
+ macro function for later evaluation when we must actually expand
+ a value that uses it.
+ """
+ self.name = name
+ self.args = function_arg_separator.split(args)
+ self.expansion = string.split(expansion, '##')
+ def __call__(self, *values):
+ """
+ Evaluates the expansion of a #define macro function called
+ with the specified values.
+ """
+ if len(self.args) != len(values):
+ raise ValueError, "Incorrect number of arguments to `%s'" % self.name
+ # Create a dictionary that maps the macro arguments to the
+ # corresponding values in this "call." We'll use this when we
+ # eval() the expansion so that arguments will get expanded to
+ # the right values.
+ locals = {}
+ for k, v in zip(self.args, values):
+ locals[k] = v
+
+ parts = []
+ for s in self.expansion:
+ if not s in self.args:
+ s = repr(s)
+ parts.append(s)
+ statement = string.join(parts, ' + ')
+
+ return eval(statement, globals(), locals)
+
+
+
+# Find line continuations.
+line_continuations = re.compile('\\\\\r?\n')
+
+# Search for a "function call" macro on an expansion. Returns the
+# two-tuple of the "function" name itself, and a string containing the
+# arguments within the call parentheses.
+function_name = re.compile('(\S+)\(([^)]*)\)')
+
+# Split a string containing comma-separated function call arguments into
+# the separate arguments.
+function_arg_separator = re.compile(',\s*')
+
+
+
+class PreProcessor:
+ """
+ The main workhorse class for handling C pre-processing.
+ """
+ def __init__(self, current='.', cpppath=[], dict={}, all=0):
+ global Table
+
+ self.searchpath = {
+ '"' : [current] + cpppath,
+ '<' : cpppath + [current],
+ }
+
+ # Initialize our C preprocessor namespace for tracking the
+ # values of #defined keywords. We use this namespace to look
+ # for keywords on #ifdef/#ifndef lines, and to eval() the
+ # expressions on #if/#elif lines (after massaging them from C to
+ # Python).
+ self.cpp_namespace = dict.copy()
+ self.cpp_namespace['__dict__'] = self.cpp_namespace
+
+ if all:
+ self.do_include = self.all_include
+
+ # For efficiency, a dispatch table maps each C preprocessor
+ # directive (#if, #define, etc.) to the method that should be
+ # called when we see it. We accomodate state changes (#if,
+ # #ifdef, #ifndef) by pushing the current dispatch table on a
+ # stack and changing what method gets called for each relevant
+ # directive we might see next at this level (#else, #elif).
+ # #endif will simply pop the stack.
+ d = {}
+ for op in Table.keys():
+ d[op] = getattr(self, 'do_' + op)
+ self.default_table = d
+
+ # Controlling methods.
+
+ def tupleize(self, contents):
+ """
+ Turns the contents of a file into a list of easily-processed
+ tuples describing the CPP lines in the file.
+
+ The first element of each tuple is the line's preprocessor
+ directive (#if, #include, #define, etc., minus the initial '#').
+ The remaining elements are specific to the type of directive, as
+ pulled apart by the regular expression.
+ """
+ global CPP_Expression, Table
+ contents = line_continuations.sub('', contents)
+ cpp_tuples = CPP_Expression.findall(contents)
+ return map(lambda m, t=Table:
+ (m[0],) + t[m[0]].match(m[1]).groups(),
+ cpp_tuples)
+
+ def __call__(self, contents):
+ """
+ Pre-processes a file contents.
+
+ This is the main entry point, which
+ """
+ self.stack = []
+ self.dispatch_table = self.default_table.copy()
+ self.tuples = self.tupleize(contents)
+
+ self.result = []
+ while self.tuples:
+ t = self.tuples.pop(0)
+ # Uncomment to see the list of tuples being processed (e.g.,
+ # to validate the CPP lines are being translated correctly).
+ #print t
+ self.dispatch_table[t[0]](t)
+
+ return self.result
+
+ # Dispatch table stack manipulation methods.
+
+ def save(self):
+ """
+ Pushes the current dispatch table on the stack and re-initializes
+ the current dispatch table to the default.
+ """
+ self.stack.append(self.dispatch_table)
+ self.dispatch_table = self.default_table.copy()
+
+ def restore(self):
+ """
+ Pops the previous dispatch table off the stack and makes it the
+ current one.
+ """
+ try: self.dispatch_table = self.stack.pop()
+ except IndexError: pass
+
+ # Utility methods.
+
+ def do_nothing(self, t):
+ """
+ Null method for when we explicitly want the action for a
+ specific preprocessor directive to do nothing.
+ """
+ pass
+
+ def eval_expression(self, t):
+ """
+ Evaluates a C preprocessor expression.
+
+ This is done by converting it to a Python equivalent and
+ eval()ing it in the C preprocessor namespace we use to
+ track #define values.
+ """
+ t = CPP_to_Python(string.join(t[1:]))
+ try: return eval(t, self.cpp_namespace)
+ except (NameError, TypeError): return 0
+
+ def find_include_file(self, t):
+ """
+ Finds the #include file for a given preprocessor tuple.
+ """
+ fname = t[2]
+ for d in self.searchpath[t[1]]:
+ f = os.path.join(d, fname)
+ if os.path.isfile(f):
+ return f
+ return None
+
+ # Start and stop processing include lines.
+
+ def start_handling_includes(self, t=None):
+ """
+ Causes the PreProcessor object to start processing #import,
+ #include and #include_next lines.
+
+ This method will be called when a #if, #ifdef, #ifndef or #elif
+ evaluates True, or when we reach the #else in a #if, #ifdef,
+ #ifndef or #elif block where a condition already evaluated
+ False.
+
+ """
+ d = self.dispatch_table
+ d['import'] = self.do_import
+ d['include'] = self.do_include
+ d['include_next'] = self.do_include
+
+ def stop_handling_includes(self, t=None):
+ """
+ Causes the PreProcessor object to stop processing #import,
+ #include and #include_next lines.
+
+ This method will be called when a #if, #ifdef, #ifndef or #elif
+ evaluates False, or when we reach the #else in a #if, #ifdef,
+ #ifndef or #elif block where a condition already evaluated True.
+ """
+ d = self.dispatch_table
+ d['import'] = self.do_nothing
+ d['include'] = self.do_nothing
+ d['include_next'] = self.do_nothing
+
+ # Default methods for handling all of the preprocessor directives.
+ # (Note that what actually gets called for a given directive at any
+ # point in time is really controlled by the dispatch_table.)
+
+ def _do_if_else_condition(self, condition):
+ """
+ Common logic for evaluating the conditions on #if, #ifdef and
+ #ifndef lines.
+ """
+ self.save()
+ d = self.dispatch_table
+ if condition:
+ self.start_handling_includes()
+ d['elif'] = self.stop_handling_includes
+ d['else'] = self.stop_handling_includes
+ else:
+ self.stop_handling_includes()
+ d['elif'] = self.do_elif
+ d['else'] = self.start_handling_includes
+
+ def do_ifdef(self, t):
+ """
+ Default handling of a #ifdef line.
+ """
+ self._do_if_else_condition(self.cpp_namespace.has_key(t[1]))
+
+ def do_ifndef(self, t):
+ """
+ Default handling of a #ifndef line.
+ """
+ self._do_if_else_condition(not self.cpp_namespace.has_key(t[1]))
+
+ def do_if(self, t):
+ """
+ Default handling of a #if line.
+ """
+ self._do_if_else_condition(self.eval_expression(t))
+
+ def do_elif(self, t):
+ """
+ Default handling of a #elif line.
+ """
+ d = self.dispatch_table
+ if self.eval_expression(t):
+ self.start_handling_includes()
+ d['elif'] = self.stop_handling_includes
+ d['else'] = self.stop_handling_includes
+
+ def do_else(self, t):
+ """
+ Default handling of a #else line.
+ """
+ pass
+
+ def do_endif(self, t):
+ """
+ Default handling of a #endif line.
+ """
+ self.restore()
+
+ def do_define(self, t):
+ """
+ Default handling of a #define line.
+ """
+ _, name, args, expansion = t
+ try:
+ expansion = int(expansion)
+ except (TypeError, ValueError):
+ pass
+ if args:
+ evaluator = FunctionEvaluator(name, args[1:-1], expansion)
+ self.cpp_namespace[name] = evaluator
+ else:
+ self.cpp_namespace[name] = expansion
+
+ def do_undef(self, t):
+ """
+ Default handling of a #undef line.
+ """
+ try: del self.cpp_namespace[t[1]]
+ except KeyError: pass
+
+ def do_import(self, t):
+ """
+ Default handling of a #import line.
+ """
+ # XXX finish this -- maybe borrow/share logic from do_include()...?
+ pass
+
+ def do_include(self, t):
+ """
+ Default handling of a #include line.
+ """
+ t = self.resolve_include(t)
+ include_file = self.find_include_file(t)
+ if include_file:
+ #print "include_file =", include_file
+ self.result.append(include_file)
+ contents = open(include_file).read()
+ new_tuples = self.tupleize(contents)
+ self.tuples[:] = new_tuples + self.tuples
+
+ # Date: Tue, 22 Nov 2005 20:26:09 -0500
+ # From: Stefan Seefeld <seefeld@sympatico.ca>
+ #
+ # By the way, #include_next is not the same as #include. The difference
+ # being that #include_next starts its search in the path following the
+ # path that let to the including file. In other words, if your system
+ # include paths are ['/foo', '/bar'], and you are looking at a header
+ # '/foo/baz.h', it might issue an '#include_next <baz.h>' which would
+ # correctly resolve to '/bar/baz.h' (if that exists), but *not* see
+ # '/foo/baz.h' again. See http://www.delorie.com/gnu/docs/gcc/cpp_11.html
+ # for more reasoning.
+ #
+ # I have no idea in what context 'import' might be used.
+
+ # XXX is #include_next really the same as #include ?
+ do_include_next = do_include
+
+ # Utility methods for handling resolution of include files.
+
+ def resolve_include(self, t):
+ """Resolve a tuple-ized #include line.
+
+ This handles recursive expansion of values without "" or <>
+ surrounding the name until an initial " or < is found, to handle
+ #include FILE
+ where FILE is a #define somewhere else.
+ """
+ s = t[1]
+ while not s[0] in '<"':
+ #print "s =", s
+ try:
+ s = self.cpp_namespace[s]
+ except KeyError:
+ m = function_name.search(s)
+ s = self.cpp_namespace[m.group(1)]
+ if callable(s):
+ args = function_arg_separator.split(m.group(2))
+ s = apply(s, args)
+ if not s:
+ return None
+ return (t[0], s[0], s[1:-1])
+
+ def all_include(self, t):
+ """
+ """
+ self.result.append(self.resolve_include(t))
+
+class DumbPreProcessor(PreProcessor):
+ """A preprocessor that ignores all #if/#elif/#else/#endif directives
+ and just reports back *all* of the #include files (like the classic
+ SCons scanner did).
+
+ This is functionally equivalent to using a regular expression to
+ find all of the #include lines, only slower. It exists mainly as
+ an example of how the main PreProcessor class can be sub-classed
+ to tailor its behavior.
+ """
+ def __init__(self, *args, **kw):
+ apply(PreProcessor.__init__, (self,)+args, kw)
+ d = self.default_table
+ for func in ['if', 'elif', 'else', 'endif', 'ifdef', 'ifndef']:
+ d[func] = d[func] = self.do_nothing
+
+del __revision__
diff --git a/src/engine/SCons/cppTests.py b/src/engine/SCons/cppTests.py
new file mode 100644
index 0000000..0959e2c
--- /dev/null
+++ b/src/engine/SCons/cppTests.py
@@ -0,0 +1,573 @@
+#
+# __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 sys
+import unittest
+
+print sys.path
+import cpp
+
+
+
+basic_input = """
+#include "file1-yes"
+#include <file2-yes>
+"""
+
+
+substitution_input = """
+#define FILE3 "file3-yes"
+#define FILE4 <file4-yes>
+
+#include FILE3
+#include FILE4
+
+#define XXX_FILE5 YYY_FILE5
+#define YYY_FILE5 ZZZ_FILE5
+#define ZZZ_FILE5 FILE5
+
+#define FILE5 "file5-yes"
+#define FILE6 <file6-yes>
+
+#define XXX_FILE6 YYY_FILE6
+#define YYY_FILE6 ZZZ_FILE6
+#define ZZZ_FILE6 FILE6
+
+#include XXX_FILE5
+#include XXX_FILE6
+"""
+
+
+ifdef_input = """
+#define DEFINED 0
+
+#ifdef DEFINED
+#include "file7-yes"
+#else
+#include "file7-no"
+#endif
+
+#ifdef NOT_DEFINED
+#include <file8-no>
+#else
+#include <file8-yes>
+#endif
+"""
+
+
+if_boolean_input = """
+#define ZERO 0
+#define ONE 1
+
+#if ZERO
+#include "file9-no"
+#else
+#include "file9-yes"
+#endif
+
+#if ONE
+#include <file10-yes>
+#else
+#include <file10-no>
+#endif
+
+#if ZERO
+#include "file11-no-1"
+#elif ZERO
+#include "file11-no-2"
+#else
+#include "file11-yes"
+#endif
+
+#if ZERO
+#include <file12-no-1>
+#elif ONE
+#include <file12-yes>
+#else
+#include <file12-no-2>
+#endif
+
+#if ONE
+#include "file13-yes"
+#elif ZERO
+#include "file13-no-1"
+#else
+#include "file13-no-2"
+#endif
+
+#if ONE
+#include <file14-yes>
+#elif ONE
+#include <file14-no-1>
+#else
+#include <file14-no-2>
+#endif
+"""
+
+
+if_defined_input = """
+#define DEFINED 0
+
+#if defined(DEFINED)
+#include "file15-yes"
+#endif
+
+#if ! defined(DEFINED)
+#include <file16-no>
+#else
+#include <file16-yes>
+#endif
+
+#if defined DEFINED
+#include "file17-yes"
+#endif
+
+#if ! defined DEFINED
+#include <file18-no>
+#else
+#include <file18-yes>
+#endif
+"""
+
+
+expression_input = """
+#define ZERO 0
+#define ONE 1
+
+#if ZERO && ZERO
+#include "file19-no"
+#else
+#include "file19-yes"
+#endif
+
+#if ZERO && ONE
+#include <file20-no>
+#else
+#include <file20-yes>
+#endif
+
+#if ONE && ZERO
+#include "file21-no"
+#else
+#include "file21-yes"
+#endif
+
+#if ONE && ONE
+#include <file22-yes>
+#else
+#include <file22-no>
+#endif
+
+#if ZERO || ZERO
+#include "file23-no"
+#else
+#include "file23-yes"
+#endif
+
+#if ZERO || ONE
+#include <file24-yes>
+#else
+#include <file24-no>
+#endif
+
+#if ONE || ZERO
+#include "file25-yes"
+#else
+#include "file25-no"
+#endif
+
+#if ONE || ONE
+#include <file26-yes>
+#else
+#include <file26-no>
+#endif
+
+#if ONE == ONE
+#include "file27-yes"
+#else
+#include "file27-no"
+#endif
+
+#if ONE != ONE
+#include <file28-no>
+#else
+#include <file28-yes>
+#endif
+
+#if ! (ONE == ONE)
+#include "file29-no"
+#else
+#include "file29-yes"
+#endif
+
+#if ! (ONE != ONE)
+#include <file30-yes>
+#else
+#include <file30-no>
+#endif
+"""
+
+
+undef_input = """
+#define UNDEFINE 0
+
+#ifdef UNDEFINE
+#include "file31-yes"
+#else
+#include "file31-no"
+#endif
+
+#undef UNDEFINE
+
+#ifdef UNDEFINE
+#include <file32-no>
+#else
+#include <file32-yes>
+#endif
+"""
+
+
+macro_function_input = """
+#define ZERO 0
+#define ONE 1
+
+#define FUNC33(x) "file33-yes"
+#define FUNC34(x) <file34-yes>
+
+#include FUNC33(ZERO)
+#include FUNC34(ZERO)
+
+#define FILE35 "file35-yes"
+#define FILE36 <file36-yes>
+
+#define FUNC35(x, y) FILE35
+#define FUNC36(x, y) FILE36
+
+#include FUNC35(ZERO, ONE)
+#include FUNC36(ZERO, ONE)
+
+#define FILE37 "file37-yes"
+#define FILE38 <file38-yes>
+
+#define FUNC37a(x, y) FILE37
+#define FUNC38a(x, y) FILE38
+
+#define FUNC37b(x, y) FUNC37a(x, y)
+#define FUNC38b(x, y) FUNC38a(x, y)
+
+#define FUNC37c(x, y) FUNC37b(x, y)
+#define FUNC38c(x, y) FUNC38b(x, y)
+
+#include FUNC37c(ZERO, ONE)
+#include FUNC38c(ZERO, ONE)
+
+#define FILE39 "file39-yes"
+#define FILE40 <file40-yes>
+
+#define FUNC39a(x0, y0) FILE39
+#define FUNC40a(x0, y0) FILE40
+
+#define FUNC39b(x1, y2) FUNC39a(x1, y1)
+#define FUNC40b(x1, y2) FUNC40a(x1, y1)
+
+#define FUNC39c(x2, y2) FUNC39b(x2, y2)
+#define FUNC40c(x2, y2) FUNC40b(x2, y2)
+
+#include FUNC39c(ZERO, ONE)
+#include FUNC40c(ZERO, ONE)
+"""
+
+
+token_pasting_input = """
+#define PASTE_QUOTE(q, name) q##name##-yes##q
+#define PASTE_ANGLE(name) <##name##-yes>
+
+#define FUNC41 PASTE_QUOTE(", file41)
+#define FUNC42 PASTE_ANGLE(file42)
+
+#include FUNC41
+#include FUNC42
+"""
+
+
+
+# pp_class = PreProcessor
+# #pp_class = DumbPreProcessor
+
+# pp = pp_class(current = ".",
+# cpppath = ['/usr/include'],
+# print_all = 1)
+# #pp(open(sys.argv[1]).read())
+# pp(input)
+
+
+class cppTestCase(unittest.TestCase):
+ def setUp(self):
+ self.cpp = self.cpp_class(current = ".",
+ cpppath = ['/usr/include'])
+
+ def test_basic(self):
+ """Test basic #include scanning"""
+ expect = self.basic_expect
+ result = self.cpp(basic_input)
+ assert expect == result, (expect, result)
+
+ def test_substitution(self):
+ """Test substitution of #include files using CPP variables"""
+ expect = self.substitution_expect
+ result = self.cpp(substitution_input)
+ assert expect == result, (expect, result)
+
+ def test_ifdef(self):
+ """Test basic #ifdef processing"""
+ expect = self.ifdef_expect
+ result = self.cpp(ifdef_input)
+ assert expect == result, (expect, result)
+
+ def test_if_boolean(self):
+ """Test #if with Boolean values"""
+ expect = self.if_boolean_expect
+ result = self.cpp(if_boolean_input)
+ assert expect == result, (expect, result)
+
+ def test_if_defined(self):
+ """Test #if defined() idioms"""
+ expect = self.if_defined_expect
+ result = self.cpp(if_defined_input)
+ assert expect == result, (expect, result)
+
+ def test_expression(self):
+ """Test #if with arithmetic expressions"""
+ expect = self.expression_expect
+ result = self.cpp(expression_input)
+ assert expect == result, (expect, result)
+
+ def test_undef(self):
+ """Test #undef handling"""
+ expect = self.undef_expect
+ result = self.cpp(undef_input)
+ assert expect == result, (expect, result)
+
+ def test_macro_function(self):
+ """Test using macro functions to express file names"""
+ expect = self.macro_function_expect
+ result = self.cpp(macro_function_input)
+ assert expect == result, (expect, result)
+
+ def test_token_pasting(self):
+ """Test taken-pasting to construct file names"""
+ expect = self.token_pasting_expect
+ result = self.cpp(token_pasting_input)
+ assert expect == result, (expect, result)
+
+class cppAllTestCase(cppTestCase):
+ def setUp(self):
+ self.cpp = self.cpp_class(current = ".",
+ cpppath = ['/usr/include'],
+ all=1)
+
+class PreProcessorTestCase(cppAllTestCase):
+ cpp_class = cpp.PreProcessor
+
+ basic_expect = [
+ ('include', '"', 'file1-yes'),
+ ('include', '<', 'file2-yes'),
+ ]
+
+ substitution_expect = [
+ ('include', '"', 'file3-yes'),
+ ('include', '<', 'file4-yes'),
+ ('include', '"', 'file5-yes'),
+ ('include', '<', 'file6-yes'),
+ ]
+
+ ifdef_expect = [
+ ('include', '"', 'file7-yes'),
+ ('include', '<', 'file8-yes'),
+ ]
+
+ if_boolean_expect = [
+ ('include', '"', 'file9-yes'),
+ ('include', '<', 'file10-yes'),
+ ('include', '"', 'file11-yes'),
+ ('include', '<', 'file12-yes'),
+ ('include', '"', 'file13-yes'),
+ ('include', '<', 'file14-yes'),
+ ]
+
+ if_defined_expect = [
+ ('include', '"', 'file15-yes'),
+ ('include', '<', 'file16-yes'),
+ ('include', '"', 'file17-yes'),
+ ('include', '<', 'file18-yes'),
+ ]
+
+ expression_expect = [
+ ('include', '"', 'file19-yes'),
+ ('include', '<', 'file20-yes'),
+ ('include', '"', 'file21-yes'),
+ ('include', '<', 'file22-yes'),
+ ('include', '"', 'file23-yes'),
+ ('include', '<', 'file24-yes'),
+ ('include', '"', 'file25-yes'),
+ ('include', '<', 'file26-yes'),
+ ('include', '"', 'file27-yes'),
+ ('include', '<', 'file28-yes'),
+ ('include', '"', 'file29-yes'),
+ ('include', '<', 'file30-yes'),
+ ]
+
+ undef_expect = [
+ ('include', '"', 'file31-yes'),
+ ('include', '<', 'file32-yes'),
+ ]
+
+ macro_function_expect = [
+ ('include', '"', 'file33-yes'),
+ ('include', '<', 'file34-yes'),
+ ('include', '"', 'file35-yes'),
+ ('include', '<', 'file36-yes'),
+ ('include', '"', 'file37-yes'),
+ ('include', '<', 'file38-yes'),
+ ('include', '"', 'file39-yes'),
+ ('include', '<', 'file40-yes'),
+ ]
+
+ token_pasting_expect = [
+ ('include', '"', 'file41-yes'),
+ ('include', '<', 'file42-yes'),
+ ]
+
+class DumbPreProcessorTestCase(cppAllTestCase):
+ cpp_class = cpp.DumbPreProcessor
+
+ basic_expect = [
+ ('include', '"', 'file1-yes'),
+ ('include', '<', 'file2-yes'),
+ ]
+
+ substitution_expect = [
+ ('include', '"', 'file3-yes'),
+ ('include', '<', 'file4-yes'),
+ ('include', '"', 'file5-yes'),
+ ('include', '<', 'file6-yes'),
+ ]
+
+ ifdef_expect = [
+ ('include', '"', 'file7-yes'),
+ ('include', '"', 'file7-no'),
+ ('include', '<', 'file8-no'),
+ ('include', '<', 'file8-yes'),
+ ]
+
+ if_boolean_expect = [
+ ('include', '"', 'file9-no'),
+ ('include', '"', 'file9-yes'),
+ ('include', '<', 'file10-yes'),
+ ('include', '<', 'file10-no'),
+ ('include', '"', 'file11-no-1'),
+ ('include', '"', 'file11-no-2'),
+ ('include', '"', 'file11-yes'),
+ ('include', '<', 'file12-no-1'),
+ ('include', '<', 'file12-yes'),
+ ('include', '<', 'file12-no-2'),
+ ('include', '"', 'file13-yes'),
+ ('include', '"', 'file13-no-1'),
+ ('include', '"', 'file13-no-2'),
+ ('include', '<', 'file14-yes'),
+ ('include', '<', 'file14-no-1'),
+ ('include', '<', 'file14-no-2'),
+ ]
+
+ if_defined_expect = [
+ ('include', '"', 'file15-yes'),
+ ('include', '<', 'file16-no'),
+ ('include', '<', 'file16-yes'),
+ ('include', '"', 'file17-yes'),
+ ('include', '<', 'file18-no'),
+ ('include', '<', 'file18-yes'),
+ ]
+
+ expression_expect = [
+ ('include', '"', 'file19-no'),
+ ('include', '"', 'file19-yes'),
+ ('include', '<', 'file20-no'),
+ ('include', '<', 'file20-yes'),
+ ('include', '"', 'file21-no'),
+ ('include', '"', 'file21-yes'),
+ ('include', '<', 'file22-yes'),
+ ('include', '<', 'file22-no'),
+ ('include', '"', 'file23-no'),
+ ('include', '"', 'file23-yes'),
+ ('include', '<', 'file24-yes'),
+ ('include', '<', 'file24-no'),
+ ('include', '"', 'file25-yes'),
+ ('include', '"', 'file25-no'),
+ ('include', '<', 'file26-yes'),
+ ('include', '<', 'file26-no'),
+ ('include', '"', 'file27-yes'),
+ ('include', '"', 'file27-no'),
+ ('include', '<', 'file28-no'),
+ ('include', '<', 'file28-yes'),
+ ('include', '"', 'file29-no'),
+ ('include', '"', 'file29-yes'),
+ ('include', '<', 'file30-yes'),
+ ('include', '<', 'file30-no'),
+ ]
+
+ undef_expect = [
+ ('include', '"', 'file31-yes'),
+ ('include', '"', 'file31-no'),
+ ('include', '<', 'file32-no'),
+ ('include', '<', 'file32-yes'),
+ ]
+
+ macro_function_expect = [
+ ('include', '"', 'file33-yes'),
+ ('include', '<', 'file34-yes'),
+ ('include', '"', 'file35-yes'),
+ ('include', '<', 'file36-yes'),
+ ('include', '"', 'file37-yes'),
+ ('include', '<', 'file38-yes'),
+ ('include', '"', 'file39-yes'),
+ ('include', '<', 'file40-yes'),
+ ]
+
+ token_pasting_expect = [
+ ('include', '"', 'file41-yes'),
+ ('include', '<', 'file42-yes'),
+ ]
+
+if __name__ == '__main__':
+ suite = unittest.TestSuite()
+ tclasses = [ PreProcessorTestCase,
+ DumbPreProcessorTestCase,
+ ]
+ for tclass in tclasses:
+ names = unittest.getTestCaseNames(tclass, 'test_')
+ suite.addTests(map(tclass, names))
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
+