summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-05-06 05:58:31 (GMT)
committerSteven Knight <knight@baldmt.com>2003-05-06 05:58:31 (GMT)
commitaaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d (patch)
treec399116a6a33ae9ad69d04dd43c2f272737c2074 /src/engine
parente591784eb29edfaf2760024f57ee1462f1bae1f3 (diff)
downloadSCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.zip
SCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.tar.gz
SCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.tar.bz2
Refactor to use real Nodes for command-line attributes and eliminate PathList. (Charles Crain)
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/SCons/Action.py36
-rw-r--r--src/engine/SCons/ActionTests.py99
-rw-r--r--src/engine/SCons/Builder.py2
-rw-r--r--src/engine/SCons/BuilderTests.py18
-rw-r--r--src/engine/SCons/Environment.py53
-rw-r--r--src/engine/SCons/EnvironmentTests.py76
-rw-r--r--src/engine/SCons/Node/FS.py95
-rw-r--r--src/engine/SCons/Node/FSTests.py70
-rw-r--r--src/engine/SCons/Node/NodeTests.py40
-rw-r--r--src/engine/SCons/Node/__init__.py43
-rw-r--r--src/engine/SCons/Platform/win32.py5
-rw-r--r--src/engine/SCons/Script/SConscript.py6
-rw-r--r--src/engine/SCons/Tool/Perforce.py2
-rw-r--r--src/engine/SCons/Tool/jar.py8
-rw-r--r--src/engine/SCons/Tool/javac.py82
-rw-r--r--src/engine/SCons/Tool/linkloc.py4
-rw-r--r--src/engine/SCons/Tool/mingw.py4
-rw-r--r--src/engine/SCons/Tool/mslink.py59
-rw-r--r--src/engine/SCons/Tool/zip.py2
-rw-r--r--src/engine/SCons/Util.py366
-rw-r--r--src/engine/SCons/UtilTests.py254
21 files changed, 759 insertions, 565 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index d7be127..befac1e 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -171,9 +171,6 @@ def _string_from_cmd_list(cmd_list):
cl.append(arg)
return string.join(cl)
-_rm = re.compile(r'\$[()]')
-_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
-
class CommandAction(ActionBase):
"""Class for command-execution actions."""
def __init__(self, cmd):
@@ -183,7 +180,8 @@ class CommandAction(ActionBase):
self.cmd_list = cmd
def strfunction(self, target, source, env):
- cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm,
+ cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env,
+ SCons.Util.SUBST_CMD,
target, source)
return map(_string_from_cmd_list, cmd_list)
@@ -228,7 +226,8 @@ class CommandAction(ActionBase):
else:
raise SCons.Errors.UserError('Missing SPAWN construction variable.')
- cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm,
+ cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env,
+ SCons.Util.SUBST_CMD,
target, source)
for cmd_line in cmd_list:
if len(cmd_line):
@@ -259,21 +258,12 @@ class CommandAction(ActionBase):
def get_raw_contents(self, target, source, env):
"""Return the complete contents of this action's command line.
"""
- # We've discusssed using the real target and source names in
- # a CommandAction's signature contents. This would have the
- # advantage of recompiling when a file's name changes (keeping
- # debug info current), but it would currently break repository
- # logic that will change the file name based on whether the
- # files come from a repository or locally. If we ever move to
- # that scheme, though, here's how we'd do it:
- #return SCons.Util.scons_subst(string.join(self.cmd_list),
- # self.subst_dict(target, source, env),
- # {})
cmd = self.cmd_list
if not SCons.Util.is_List(cmd):
cmd = [ cmd ]
return SCons.Util.scons_subst(string.join(map(str, cmd)),
- env)
+ env, SCons.Util.SUBST_RAW,
+ target, source)
def get_contents(self, target, source, env):
"""Return the signature contents of this action's command line.
@@ -281,23 +271,13 @@ class CommandAction(ActionBase):
This strips $(-$) and everything in between the string,
since those parts don't affect signatures.
"""
- # We've discusssed using the real target and source names in
- # a CommandAction's signature contents. This would have the
- # advantage of recompiling when a file's name changes (keeping
- # debug info current), but it would currently break repository
- # logic that will change the file name based on whether the
- # files come from a repository or locally. If we ever move to
- # that scheme, though, here's how we'd do it:
- #return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)),
- # self.subst_dict(target, source, env),
- # {},
- # _remove)
cmd = self.cmd_list
if not SCons.Util.is_List(cmd):
cmd = [ cmd ]
return SCons.Util.scons_subst(string.join(map(str, cmd)),
env,
- _remove)
+ SCons.Util.SUBST_SIG,
+ target, source)
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 4aee748..33bf57e 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -127,6 +127,14 @@ class Environment:
d['SOURCE'] = d['SOURCES'][0]
return d
+class DummyNode:
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return self.name
+ def rfile(self):
+ return self
+
if os.name == 'java':
python = os.path.join(sys.prefix, 'jython')
else:
@@ -334,20 +342,23 @@ class CommandActionTestCase(unittest.TestCase):
def test_strfunction(self):
"""Test fetching the string representation of command Actions
"""
+
act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
s = act.strfunction([], [], Environment())
assert s == ['xyzzy'], s
- s = act.strfunction(['target'], ['source'], Environment())
+ s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
assert s == ['xyzzy target source'], s
- s = act.strfunction(['t1', 't2'], ['s1', 's2'], Environment())
+ s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
+ [DummyNode('s1'), DummyNode('s2')], Environment())
assert s == ['xyzzy t1 s1'], s
act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
s = act.strfunction([], [], Environment())
assert s == ['xyzzy'], s
- s = act.strfunction(['target'], ['source'], Environment())
+ s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
assert s == ['xyzzy target source'], s
- s = act.strfunction(['t1', 't2'], ['s1', 's2'], Environment())
+ s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
+ [DummyNode('s1'), DummyNode('s2')], Environment())
assert s == ['xyzzy t1 t2 s1 s2'], s
act = SCons.Action.CommandAction(['xyzzy',
@@ -355,9 +366,10 @@ class CommandActionTestCase(unittest.TestCase):
'$TARGETS', '$SOURCES'])
s = act.strfunction([], [], Environment())
assert s == ['xyzzy'], s
- s = act.strfunction(['target'], ['source'], Environment())
+ s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
assert s == ['xyzzy target source target source'], s
- s = act.strfunction(['t1', 't2'], ['s1', 's2'], Environment())
+ s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
+ [DummyNode('s1'), DummyNode('s2')], Environment())
assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s
def test_execute(self):
@@ -396,7 +408,7 @@ class CommandActionTestCase(unittest.TestCase):
cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
act = SCons.Action.CommandAction(cmd4)
- r = act([], ['one', 'two'], env.Copy())
+ r = act([], [DummyNode('one'), DummyNode('two')], env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'one' 'two'\n", c
@@ -405,8 +417,10 @@ class CommandActionTestCase(unittest.TestCase):
act = SCons.Action.CommandAction(cmd4)
r = act([],
- source = ['three', 'four', 'five'],
- env = env.Copy())
+ source = [DummyNode('three'),
+ DummyNode('four'),
+ DummyNode('five')],
+ env = env.Copy())
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'three' 'four'\n", c
@@ -426,7 +440,7 @@ class CommandActionTestCase(unittest.TestCase):
r = act(target = 'out5', source = [], env = env5)
act = SCons.Action.CommandAction(cmd5)
- r = act(target = 'out5',
+ r = act(target = DummyNode('out5'),
source = [],
env = env.Copy(ENV = {'XYZZY' : 'xyzzy',
'PATH' : PATH}))
@@ -439,6 +453,8 @@ class CommandActionTestCase(unittest.TestCase):
self._str = str
def __str__(self):
return self._str
+ def rfile(self):
+ return self
cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
@@ -559,8 +575,8 @@ class CommandActionTestCase(unittest.TestCase):
def test_get_raw_contents(self):
"""Test fetching the contents of a command Action
"""
- def CmdGen(target, source, env):
- assert target is None, target
+ def CmdGen(target, source, env, for_signature):
+ assert for_signature
return "%s %s" % \
(env["foo"], env["bar"])
@@ -581,47 +597,47 @@ class CommandActionTestCase(unittest.TestCase):
# that scheme, then all of the '__t1__' and '__s6__' file names
# in the asserts below would change to 't1' and 's6' and the
# like.
- t = ['t1', 't2', 't3', 't4', 't5', 't6']
- s = ['s1', 's2', 's3', 's4', 's5', 's6']
+ t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
+ s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
env = Environment()
a = SCons.Action.CommandAction(["$TARGET"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__t1__", c
+ assert c == "t1", c
a = SCons.Action.CommandAction(["$TARGETS"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__t1__ __t2__ __t3__ __t4__ __t5__ __t6__", c
+ assert c == "t1 t2 t3 t4 t5 t6", c
a = SCons.Action.CommandAction(["${TARGETS[2]}"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__t3__", c
+ assert c == "t3", c
a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__t4__ __t5__", c
+ assert c == "t4 t5", c
a = SCons.Action.CommandAction(["$SOURCE"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__s1__", c
+ assert c == "s1", c
a = SCons.Action.CommandAction(["$SOURCES"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__s1__ __s2__ __s3__ __s4__ __s5__ __s6__", c
+ assert c == "s1 s2 s3 s4 s5 s6", c
a = SCons.Action.CommandAction(["${SOURCES[2]}"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__s3__", c
+ assert c == "s3", c
a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "__s4__ __s5__", c
+ assert c == "s4 s5", c
def test_get_contents(self):
"""Test fetching the contents of a command Action
"""
- def CmdGen(target, source, env):
- assert target is None, target
+ def CmdGen(target, source, env, for_signature):
+ assert for_signature
return "%s %s" % \
(env["foo"], env["bar"])
@@ -642,41 +658,41 @@ class CommandActionTestCase(unittest.TestCase):
# that scheme, then all of the '__t1__' and '__s6__' file names
# in the asserts below would change to 't1' and 's6' and the
# like.
- t = ['t1', 't2', 't3', 't4', 't5', 't6']
- s = ['s1', 's2', 's3', 's4', 's5', 's6']
+ t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
+ s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
env = Environment()
a = SCons.Action.CommandAction(["$TARGET"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__t1__", c
+ assert c == "t1", c
a = SCons.Action.CommandAction(["$TARGETS"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__t1__ __t2__ __t3__ __t4__ __t5__ __t6__", c
+ assert c == "t1 t2 t3 t4 t5 t6", c
a = SCons.Action.CommandAction(["${TARGETS[2]}"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__t3__", c
+ assert c == "t3", c
a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__t4__ __t5__", c
+ assert c == "t4 t5", c
a = SCons.Action.CommandAction(["$SOURCE"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__s1__", c
+ assert c == "s1", c
a = SCons.Action.CommandAction(["$SOURCES"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__s1__ __s2__ __s3__ __s4__ __s5__ __s6__", c
+ assert c == "s1 s2 s3 s4 s5 s6", c
a = SCons.Action.CommandAction(["${SOURCES[2]}"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__s3__", c
+ assert c == "s3", c
a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
c = a.get_contents(target=t, source=s, env=env)
- assert c == "__s4__ __s5__", c
+ assert c == "s4 s5", c
class CommandGeneratorActionTestCase(unittest.TestCase):
@@ -732,6 +748,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
self.t = t
def rfile(self):
self.t.rfile_called = 1
+ return self
def f3(target, source, env, for_signature):
return ''
c = SCons.Action.CommandGeneratorAction(f3)
@@ -745,12 +762,18 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
foo = env['foo']
bar = env['bar']
assert for_signature, for_signature
- return [["guux", foo, "$(", "ignore", "$)", bar]]
+ return [["guux", foo, "$(", "$ignore", "$)", bar,
+ '${test("$( foo $bar $)")}' ]]
+
+ def test(mystr):
+ assert mystr == "$( foo $bar $)", mystr
+ return "test"
a = SCons.Action.CommandGeneratorAction(f)
c = a.get_contents(target=[], source=[],
- env=Environment(foo = 'FFF', bar = 'BBB'))
- assert c == "guux FFF BBB", c
+ env=Environment(foo = 'FFF', bar = 'BBB',
+ ignore = 'foo', test=test))
+ assert c == "guux FFF BBB test", c
class FunctionActionTestCase(unittest.TestCase):
@@ -972,7 +995,7 @@ class LazyActionTestCase(unittest.TestCase):
"""
a = SCons.Action.Action("${FOO}")
c = a.get_contents(target=[], source=[],
- env = Environment(FOO = [["This", "is", "$(", "a", "$)", "test"]]))
+ env = Environment(FOO = [["This", "is", "$(", "$a", "$)", "test"]]))
assert c == "This is test", c
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index 19cbe27..8b0ac85 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -462,7 +462,7 @@ class MultiStepBuilder(BuilderBase):
src_suffixes = self.src_suffixes(env)
for snode in slist:
- path, ext = SCons.Util.splitext(snode.abspath)
+ path, ext = SCons.Util.splitext(snode.get_abspath())
if sdict.has_key(ext):
src_bld = sdict[ext]
tgt = apply(src_bld, (env, path, snode), kw)
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index c71f18f..5f9a18c 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -248,20 +248,30 @@ class BuilderTestCase(unittest.TestCase):
"""Test returning the signature contents of a Builder
"""
+ class DummyNode:
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return self.name
+ def rfile(self):
+ return self
+
+ target = map(DummyNode, map(lambda x: "__t%d__" % x, range(1, 7)))
+ source = map(DummyNode, map(lambda x: "__s%d__" % x, range(1, 7)))
b1 = SCons.Builder.Builder(action = "foo ${TARGETS[5]}")
- contents = b1.get_contents([],[],Environment())
+ contents = b1.get_contents(target,source,Environment())
assert contents == "foo __t6__", contents
b1 = SCons.Builder.Builder(action = "bar ${SOURCES[3:5]}")
- contents = b1.get_contents([],[],Environment())
+ contents = b1.get_contents(target,source,Environment())
assert contents == "bar __s4__ __s5__", contents
b2 = SCons.Builder.Builder(action = Func)
- contents = b2.get_contents([],[],Environment())
+ contents = b2.get_contents(target,source,Environment())
assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents)
b3 = SCons.Builder.Builder(action = SCons.Action.ListAction(["foo", Func, "bar"]))
- contents = b3.get_contents([],[],Environment())
+ contents = b3.get_contents(target,source,Environment())
assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents)
def test_node_factory(self):
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index e3d29eb..f317556 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -141,34 +141,6 @@ class BuilderDict(UserDict):
for i, v in dict.items():
self.__setitem__(i, v)
-_rm = re.compile(r'\$[()]')
-
-class _lister:
- """This class is used to implement dummy targets and sources
- for signature calculation."""
- def __init__(self, fmt):
- self.fmt = fmt
- def _format(self, index):
- # For some reason, I originally made the fake names of
- # the targets and sources 1-based (['__t1__, '__t2__']),
- # not 0-based. We preserve this behavior by adding one
- # to the returned item names, so everyone's targets
- # won't get recompiled if they were using an old
- # version.
- return self.fmt % (index + 1)
- def __getitem__(self, index):
- return SCons.Util.PathList([self._format(index)])[0]
- def __getslice__(self, i, j):
- slice = []
- for x in range(i, j):
- slice.append(self._format(x))
- return SCons.Util.PathList(slice)
- def __getattr__(self, name):
- # If we don't find an attribute in this class, let's
- # look in PathList. self[0:2] returns a PathList instance
- # via __getslice__
- return getattr(self[0:2], name)
-
class Environment:
"""Base class for construction Environments. These are
the primary objects used to communicate dependency and
@@ -442,20 +414,20 @@ class Environment:
trailing characters.
"""
if raw:
- regex_remove = None
+ mode = SCons.Util.SUBST_RAW
else:
- regex_remove = _rm
- return SCons.Util.scons_subst(string, self, regex_remove,
+ mode = SCons.Util.SUBST_CMD
+ return SCons.Util.scons_subst(string, self, mode,
target, source)
def subst_list(self, string, raw=0, target=None, source=None):
"""Calls through to SCons.Util.scons_subst_list(). See
the documentation for that function."""
if raw:
- regex_remove = None
+ mode = SCons.Util.SUBST_RAW
else:
- regex_remove = _rm
- return SCons.Util.scons_subst_list(string, self, regex_remove,
+ mode = SCons.Util.SUBST_CMD
+ return SCons.Util.scons_subst_list(string, self, mode,
target, source)
def get_scanner(self, skey):
@@ -571,16 +543,3 @@ class Environment:
if name[-len(old_suffix):] == old_suffix:
name = name[:-len(old_suffix)]
return os.path.join(dir, new_prefix+name+new_suffix)
-
- def sig_dict(self):
- """Supply a dictionary for use in computing signatures.
-
- This fills in static TARGET, TARGETS, SOURCE and SOURCES
- variables so that signatures stay the same every time.
- """
- dict = self._dict.copy()
- dict['TARGETS'] = _lister('__t%d__')
- dict['TARGET'] = dict['TARGETS'][0]
- dict['SOURCES'] = _lister('__s%d__')
- dict['SOURCE'] = dict['SOURCES'][0]
- return dict
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index e99ea0d..28ea0e5 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -551,34 +551,42 @@ class EnvironmentTestCase(unittest.TestCase):
of variables into other variables.
"""
env = Environment(AAA = 'a', BBB = 'b')
- str = env.subst("$AAA ${AAA}A $BBBB $BBB")
- assert str == "a aA b", str
+ mystr = env.subst("$AAA ${AAA}A $BBBB $BBB")
+ assert mystr == "a aA b", str
# Changed the tests below to reflect a bug fix in
# subst()
env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
- str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
- assert str == "b bA bB b", str
+ mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
+ assert mystr == "b bA bB b", str
env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
- str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
- assert str == "c cA cB c", str
+ mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
+ assert mystr == "c cA cB c", str
env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ])
lst = env.subst_list([ "$AAA", "B $CCC" ])
assert lst == [ [ "a", "b" ], [ "c", "B a", "b" ], [ "c" ] ], lst
+ class DummyNode:
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return self.name
+ def rfile(self):
+ return self
+
# Test callables in the Environment
- def foo(target, source, env):
- assert target == 1, target
- assert source == 2, source
+ def foo(target, source, env, for_signature):
+ assert str(target) == 't', target
+ assert str(source) == 's', source
return env["FOO"]
env = Environment(BAR=foo, FOO='baz')
- subst = env.subst('test $BAR', target=1, source=2)
+ subst = env.subst('test $BAR', target=DummyNode('t'), source=DummyNode('s'))
assert subst == 'test baz', subst
- lst = env.subst_list('test $BAR', target=1, source=2)
+ lst = env.subst_list('test $BAR', target=DummyNode('t'), source=DummyNode('s'))
assert lst[0][0] == 'test', lst[0][0]
assert lst[0][1] == 'baz', lst[0][1]
@@ -847,52 +855,6 @@ class EnvironmentTestCase(unittest.TestCase):
'PREFIX', 'SUFFIX',
'LIBPREFIX', 'LIBSUFFIX')
- def test_sig_dict(self):
- """Test the sig_dict() method"""
- d = Environment(XYZZY = 'foo').sig_dict()
-
- assert d['XYZZY'] == 'foo'
-
- s = str(d['TARGET'])
- assert s == '__t1__', s
- s = str(d['TARGET'].dir)
- assert s == '', s
- s = str(d['TARGETS'])
- assert s == '__t1__ __t2__', s
- s = str(d['TARGETS'][1])
- assert s == '__t2__', s
- s = str(d['TARGETS'][2])
- assert s == '__t3__', s
- s = str(d['TARGETS'][87])
- assert s == '__t88__', s
- s = str(d['TARGETS'][87].dir)
- assert s == '', s
- s = map(str, d['TARGETS'][3:5])
- assert s == ['__t4__', '__t5__'], s
- s = map(lambda x: os.path.normcase(str(x)), d['TARGETS'].abspath)
- assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__t1__'),
- os.path.join(os.getcwd(), '__t2__') ])
-
- s = str(d['SOURCE'])
- assert s == '__s1__', s
- s = str(d['SOURCE'].dir)
- assert s == '', s
- s = str(d['SOURCES'])
- assert s == '__s1__ __s2__', s
- s = str(d['SOURCES'][1])
- assert s == '__s2__', s
- s = str(d['SOURCES'][2])
- assert s == '__s3__', s
- s = str(d['SOURCES'][87])
- assert s == '__s88__', s
- s = str(d['SOURCES'][87].dir)
- assert s == '', s
- s = map(str, d['SOURCES'][3:5])
- assert s == ['__s4__', '__s5__'], s
- s = map(lambda x: os.path.normcase(str(x)), d['SOURCES'].abspath)
- assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__s1__'),
- os.path.join(os.getcwd(), '__s2__') ])
-
if __name__ == "__main__":
suite = unittest.makeSuite(EnvironmentTestCase, 'test_')
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 0a016ad..e64aacc 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -41,11 +41,11 @@ import os.path
import shutil
import stat
import string
-from UserDict import UserDict
import SCons.Action
import SCons.Errors
import SCons.Node
+import SCons.Util
import SCons.Warnings
#
@@ -190,7 +190,7 @@ class ParentOfRoot:
This class is an instance of the Null object pattern.
"""
def __init__(self):
- self.abspath = ''
+ self.abspath_str = ''
self.path = ''
self.abspath_ = ''
self.path_ = ''
@@ -251,14 +251,14 @@ class Entry(SCons.Node.Node):
assert directory, "A directory must be provided"
- self.abspath = directory.abspath_ + name
+ self.abspath_str = directory.abspath_ + name
if directory.path == '.':
self.path = name
else:
self.path = directory.path_ + name
self.path_ = self.path
- self.abspath_ = self.abspath
+ self.abspath_ = self.abspath_str
self.dir = directory
self.cwd = None # will hold the SConscript directory for target nodes
self.duplicate = directory.duplicate
@@ -293,11 +293,11 @@ class Entry(SCons.Node.Node):
Since this should return the real contents from the file
system, we check to see into what sort of subclass we should
morph this Entry."""
- if os.path.isfile(self.abspath):
+ if os.path.isfile(self.abspath_str):
self.__class__ = File
self._morph()
return File.get_contents(self)
- if os.path.isdir(self.abspath):
+ if os.path.isdir(self.abspath_str):
self.__class__ = Dir
self._morph()
return Dir.get_contents(self)
@@ -307,7 +307,7 @@ class Entry(SCons.Node.Node):
try:
return self._exists
except AttributeError:
- self._exists = _existsp(self.abspath)
+ self._exists = _existsp(self.abspath_str)
return self._exists
def rexists(self):
@@ -403,6 +403,71 @@ class Entry(SCons.Node.Node):
self.sbuilder = scb
return scb
+ def get_base_path(self):
+ """Return the file's directory and file name, with the
+ suffix stripped."""
+ return os.path.splitext(self.get_path())[0]
+
+ def get_suffix(self):
+ """Return the file's suffix."""
+ return os.path.splitext(self.get_path())[1]
+
+ def get_file_name(self):
+ """Return the file's name without the path."""
+ return self.name
+
+ def get_file_base(self):
+ """Return the file name with path and suffix stripped."""
+ return os.path.splitext(self.name)[0]
+
+ def get_posix_path(self):
+ """Return the path with / as the path separator, regardless
+ of platform."""
+ if os.sep == '/':
+ return str(self)
+ else:
+ return string.replace(self.get_path(), os.sep, '/')
+
+ def get_abspath(self):
+ """Get the absolute path of the file."""
+ return self.abspath_str
+
+ def get_srcdir(self):
+ """Returns the directory containing the source node linked to this
+ node via BuildDir(), or the directory of this node if not linked."""
+ return self.srcnode().dir
+
+ dictSpecialAttrs = { "file" : get_file_name,
+ "base" : get_base_path,
+ "filebase" : get_file_base,
+ "suffix" : get_suffix,
+ "posix" : get_posix_path,
+ "abspath" : get_abspath,
+ "srcpath" : srcnode,
+ "srcdir" : get_srcdir }
+
+ def __getattr__(self, name):
+ # This is how we implement the "special" attributes
+ # such as base, suffix, basepath, etc.
+ #
+ # Note that we enclose values in a SCons.Util.Literal instance,
+ # so they will retain special characters during Environment variable
+ # substitution.
+ try:
+ attr = self.dictSpecialAttrs[name](self)
+ except KeyError:
+ raise AttributeError, '%s has no attribute: %s' % (self.__class__, name)
+ if SCons.Util.is_String(attr):
+ return SCons.Util.SpecialAttrWrapper(attr, self.name +
+ "_%s" % name)
+ return attr
+
+ def for_signature(self):
+ # Return just our name. Even an absolute path would not work,
+ # because that can change thanks to symlinks or remapped network
+ # paths.
+ return self.name
+
# This is for later so we can differentiate between Entry the class and Entry
# the method of the FS class.
_classEntry = Entry
@@ -491,7 +556,7 @@ class FS:
raise SCons.Errors.UserError
dir = Dir(drive, ParentOfRoot(), self)
dir.path = dir.path + os.sep
- dir.abspath = dir.abspath + os.sep
+ dir.abspath_str = dir.abspath_str + os.sep
self.Root[drive] = dir
directory = dir
path_comp = path_comp[1:]
@@ -577,7 +642,7 @@ class FS:
if not dir is None:
self._cwd = dir
if change_os_dir:
- os.chdir(dir.abspath)
+ os.chdir(dir.abspath_str)
except:
self._cwd = curr
raise
@@ -785,7 +850,7 @@ class Dir(Entry):
node) don't use signatures for currency calculation."""
self.path_ = self.path + os.sep
- self.abspath_ = self.abspath + os.sep
+ self.abspath_ = self.abspath_str + os.sep
self.repositories = []
self.srcdir = None
@@ -886,9 +951,9 @@ class Dir(Entry):
keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
kids = map(lambda x, s=self: s.entries[x], keys)
def c(one, two):
- if one.abspath < two.abspath:
+ if one.abspath_str < two.abspath_str:
return -1
- if one.abspath > two.abspath:
+ if one.abspath_str > two.abspath_str:
return 1
return 0
kids.sort(c)
@@ -1020,11 +1085,11 @@ class File(Entry):
def get_contents(self):
if not self.rexists():
return ''
- return open(self.rfile().abspath, "rb").read()
+ return open(self.rfile().abspath_str, "rb").read()
def get_timestamp(self):
if self.rexists():
- return os.path.getmtime(self.rfile().abspath)
+ return os.path.getmtime(self.rfile().abspath_str)
else:
return 0
@@ -1263,7 +1328,7 @@ class File(Entry):
# Duplicate from source path if we are set up to do this.
if self.duplicate and not self.has_builder() and not self.linked:
src=self.srcnode().rfile()
- if src.exists() and src.abspath != self.abspath:
+ if src.exists() and src.abspath_str != self.abspath_str:
self._createDir()
try:
Unlink(self, None, None)
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index f2d8dd9..209e80d 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -193,7 +193,7 @@ class BuildDirTestCase(unittest.TestCase):
# Build path does not exist
assert not f1.exists()
# ...but the actual file is not there...
- assert not os.path.exists(f1.abspath)
+ assert not os.path.exists(f1.get_abspath())
# And duplicate=0 should also work just like a Repository
assert f1.rexists()
# rfile() should point to the source path
@@ -614,9 +614,9 @@ class FSTestCase(unittest.TestCase):
assert dir.path_ == path_, \
"dir.path_ %s != expected path_ %s" % \
(dir.path_, path_)
- assert dir.abspath == abspath, \
+ assert dir.get_abspath() == abspath, \
"dir.abspath %s != expected absolute path %s" % \
- (dir.abspath, abspath)
+ (dir.get_abspath(), abspath)
assert dir.abspath_ == abspath_, \
"dir.abspath_ %s != expected absolute path_ %s" % \
(dir.abspath_, abspath_)
@@ -1052,6 +1052,12 @@ class FSTestCase(unittest.TestCase):
assert exc_caught, "Should have caught an OSError, r = " + str(r)
+ f = fs.Entry('foo/bar/baz')
+ assert f.for_signature() == 'baz', f.for_signature()
+ assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \
+ f.get_string(0)
+ assert f.get_string(1) == 'baz', f.get_string(1)
+
class RepositoryTestCase(unittest.TestCase):
def runTest(self):
@@ -1512,7 +1518,64 @@ class clearTestCase(unittest.TestCase):
assert not hasattr(f, '_exists')
assert not hasattr(f, '_rexists')
+class SpecialAttrTestCase(unittest.TestCase):
+ def runTest(self):
+ """Test special attributes of file nodes."""
+ test=TestCmd(workdir='')
+ fs = SCons.Node.FS.FS(test.workpath(''))
+ f=fs.Entry('foo/bar/baz.blat')
+ assert str(f.dir) == os.path.normpath('foo/bar'), str(f.dir)
+ assert f.dir.is_literal()
+ assert f.dir.for_signature() == 'bar', f.dir.for_signature()
+
+ assert str(f.file) == 'baz.blat', str(f.file)
+ assert f.file.is_literal()
+ assert f.file.for_signature() == 'baz.blat_file', \
+ f.file.for_signature()
+
+ assert str(f.base) == os.path.normpath('foo/bar/baz'), str(f.base)
+ assert f.base.is_literal()
+ assert f.base.for_signature() == 'baz.blat_base', \
+ f.base.for_signature()
+
+ assert str(f.filebase) == 'baz', str(f.filebase)
+ assert f.filebase.is_literal()
+ assert f.filebase.for_signature() == 'baz.blat_filebase', \
+ f.filebase.for_signature()
+
+ assert str(f.suffix) == '.blat', str(f.suffix)
+ assert f.suffix.is_literal()
+ assert f.suffix.for_signature() == 'baz.blat_suffix', \
+ f.suffix.for_signature()
+
+ assert str(f.abspath) == test.workpath('foo', 'bar', 'baz.blat'), str(f.abspath)
+ assert f.abspath.is_literal()
+ assert f.abspath.for_signature() == 'baz.blat_abspath', \
+ f.abspath.for_signature()
+
+ assert str(f.posix) == 'foo/bar/baz.blat', str(f.posix)
+ assert f.posix.is_literal()
+ if f.posix != f:
+ assert f.posix.for_signature() == 'baz.blat_posix', \
+ f.posix.for_signature()
+
+ fs.BuildDir('foo', 'baz')
+
+ assert str(f.srcpath) == os.path.normpath('baz/bar/baz.blat'), str(f.srcpath)
+ assert f.srcpath.is_literal()
+ assert isinstance(f.srcpath, SCons.Node.FS.Entry)
+
+ assert str(f.srcdir) == os.path.normpath('baz/bar'), str(f.srcdir)
+ assert f.srcdir.is_literal()
+ assert isinstance(f.srcdir, SCons.Node.FS.Dir)
+
+ # And now, combinations!!!
+ assert str(f.srcpath.base) == os.path.normpath('baz/bar/baz'), str(f.srcpath.base)
+ assert str(f.srcpath.dir) == str(f.srcdir), str(f.srcpath.dir)
+ assert str(f.srcpath.posix) == 'baz/bar/baz.blat', str(f.srcpath.posix)
+
+
if __name__ == "__main__":
suite = unittest.TestSuite()
@@ -1527,5 +1590,6 @@ if __name__ == "__main__":
suite.addTest(SConstruct_dirTestCase())
suite.addTest(CacheDirTestCase())
suite.addTest(clearTestCase())
+ suite.addTest(SpecialAttrTestCase())
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index 45c4af9..b188f81 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -294,6 +294,22 @@ class NodeTestCase(unittest.TestCase):
c = node.builder_sig_adapter().get_contents()
assert c == 7, c
+ class ListBuilder:
+ def __init__(self, targets):
+ self.tgt = targets
+ def targets(self, t):
+ return self.tgt
+ def get_contents(self, target, source, env):
+ assert target == self.tgt
+ return 8
+
+ node1 = SCons.Node.Node()
+ node2 = SCons.Node.Node()
+ node.builder_set(ListBuilder([node1, node2]))
+ node.env_set(Environment())
+ c = node.builder_sig_adapter().get_contents()
+ assert c == 8, c
+
def test_current(self):
"""Test the default current() method
"""
@@ -747,6 +763,30 @@ class NodeTestCase(unittest.TestCase):
n1 = MyNode("n1")
assert n1.rstr() == 'n1', n1.rstr()
+ def test_abspath(self):
+ """Test the get_abspath() method."""
+ n = MyNode("foo")
+ assert n.get_abspath() == str(n), n.get_abspath()
+
+ def test_for_signature(self):
+ """Test the for_signature() method."""
+ n = MyNode("foo")
+ assert n.for_signature() == str(n), n.get_abspath()
+
+ def test_get_string(self):
+ """Test the get_string() method."""
+ class TestNode(MyNode):
+ def __init__(self, name, sig):
+ MyNode.__init__(self, name)
+ self.sig = sig
+
+ def for_signature(self):
+ return self.sig
+
+ n = TestNode("foo", "bar")
+ assert n.get_string(0) == "foo", n.get_string(0)
+ assert n.get_string(1) == "bar", n.get_string(1)
+
def test_arg2nodes(self):
"""Test the arg2nodes function."""
dict = {}
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 1c23684..883d757 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -262,7 +262,7 @@ class Node:
def __init__(self, node):
self.node = node
def get_contents(self):
- return self.node.builder.get_contents(self.node, self.node.sources, self.node.generate_build_env())
+ return self.node.builder.get_contents(self.node.builder.targets(self.node), self.node.sources, self.node.generate_build_env())
def get_timestamp(self):
return None
return Adapter(self)
@@ -597,6 +597,47 @@ class Node:
else:
return None
+ def get_abspath(self):
+ """
+ Return an absolute path to the Node. This will return simply
+ str(Node) by default, but for Node types that have a concept of
+ relative path, this might return something different.
+ """
+ return str(self)
+
+ def for_signature(self):
+ """
+ Return a string representation of the Node that will always
+ be the same for this particular Node, no matter what. This
+ is by contrast to the __str__() method, which might, for
+ instance, return a relative path for a file Node. The purpose
+ of this method is to generate a value to be used in signature
+ calculation for the command line used to build a target, and
+ we use this method instead of str() to avoid unnecessary
+ rebuilds. This method does not need to return something that
+ would actually work in a command line; it can return any kind of
+ nonsense, so long as it does not change.
+ """
+ return str(self)
+
+ def get_string(self, for_signature):
+ """This is a convenience function designed primarily to be
+ used in command generators (i.e., CommandGeneratorActions or
+ Environment variables that are callable), which are called
+ with a for_signature argument that is nonzero if the command
+ generator is being called to generate a signature for the
+ command line, which determines if we should rebuild or not.
+
+ Such command generators should use this method in preference
+ to str(Node) when converting a Node to a string, passing
+ in the for_signature parameter, such that we will call
+ Node.for_signature() or str(Node) properly, depending on whether
+ we are calculating a signature or actually constructing a
+ command line."""
+ if for_signature:
+ return self.for_signature()
+ return str(self)
+
def get_children(node, parent): return node.children()
def ignore_cycle(node, stack): pass
def do_nothing(node, parent): pass
diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py
index 39c0ba8..b36d611 100644
--- a/src/engine/SCons/Platform/win32.py
+++ b/src/engine/SCons/Platform/win32.py
@@ -54,9 +54,9 @@ class TempFileMunge:
def __init__(self, cmd):
self.cmd = cmd
- def __call__(self, target, source, env):
+ def __call__(self, target, source, env, for_signature):
cmd = env.subst_list(self.cmd, 0, target, source)[0]
- if target is None or \
+ if for_signature or \
(reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048:
return self.cmd
else:
@@ -76,7 +76,6 @@ class TempFileMunge:
if env['SHELL'] and env['SHELL'] == 'sh':
native_tmp = string.replace(native_tmp, '\\', r'\\\\')
-
args = map(SCons.Util.quote_spaces, cmd[1:])
open(tmp, 'w').write(string.join(args, " ") + "\n")
return [ cmd[0], '@' + native_tmp + '\n' + rm, native_tmp ]
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index af8cc79..5258d90 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -266,12 +266,12 @@ def SConscript(*ls, **kw):
# interpret the stuff within the SConscript file
# relative to where we are logically.
default_fs.chdir(ldir, change_os_dir=0)
- os.chdir(f.rfile().dir.abspath)
+ os.chdir(f.rfile().dir.get_abspath())
# Append the SConscript directory to the beginning
# of sys.path so Python modules in the SConscript
# directory can be easily imported.
- sys.path = [ f.dir.abspath ] + sys.path
+ sys.path = [ f.dir.get_abspath() ] + sys.path
# This is the magic line that actually reads up and
# executes the stuff in the SConscript file. We
@@ -294,7 +294,7 @@ def SConscript(*ls, **kw):
# Repository directory. Like above, we do this
# directly.
default_fs.chdir(frame.prev_dir, change_os_dir=0)
- os.chdir(frame.prev_dir.rdir().abspath)
+ os.chdir(frame.prev_dir.rdir().get_abspath())
results.append(frame.retval)
diff --git a/src/engine/SCons/Tool/Perforce.py b/src/engine/SCons/Tool/Perforce.py
index 3c574b4..3526952 100644
--- a/src/engine/SCons/Tool/Perforce.py
+++ b/src/engine/SCons/Tool/Perforce.py
@@ -70,7 +70,7 @@ def generate(env):
# calling getcwd() for itself, which is odd. If no PWD variable
# is present, p4 WILL call getcwd, but this seems to cause problems
# with good ol' Win32's tilde-mangling for long file names.
- environ['PWD'] = SCons.Node.FS.default_fs.Dir('#').abspath
+ environ['PWD'] = SCons.Node.FS.default_fs.Dir('#').get_abspath()
for var in _import_env:
v = os.environ.get(var)
diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py
index b1164b5..fa21f3c 100644
--- a/src/engine/SCons/Tool/jar.py
+++ b/src/engine/SCons/Tool/jar.py
@@ -38,15 +38,15 @@ import os.path
import SCons.Builder
+JarBuilder = SCons.Builder.Builder(action = '$JARCOM',
+ source_factory = SCons.Node.FS.default_fs.Entry,
+ suffix = '$JARSUFFIX')
+
def generate(env):
"""Add Builders and construction variables for jar to an Environment."""
try:
bld = env['BUILDERS']['Jar']
except KeyError:
- JarBuilder = SCons.Builder.Builder(action = '$JARCOM',
- source_factory = SCons.Node.FS.default_fs.Entry,
- suffix = '$JARSUFFIX')
-
env['BUILDERS']['Jar'] = JarBuilder
env['JAR'] = 'jar'
diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py
index 331e183..8606c02 100644
--- a/src/engine/SCons/Tool/javac.py
+++ b/src/engine/SCons/Tool/javac.py
@@ -225,50 +225,50 @@ else:
"""
return os.path.split(file)
+def emit_java_files(target, source, env):
+ """Create and return lists of source java files
+ and their corresponding target class files.
+ """
+ env['_JAVACLASSDIR'] = target[0]
+ env['_JAVASRCDIR'] = source[0].rdir()
+ java_suffix = env.get('JAVASUFFIX', '.java')
+ class_suffix = env.get('JAVACLASSSUFFIX', '.class')
+
+ slist = []
+ js = _my_normcase(java_suffix)
+ def visit(arg, dirname, names, js=js, dirnode=source[0].rdir()):
+ java_files = filter(lambda n, js=js:
+ _my_normcase(n[-len(js):]) == js,
+ names)
+ mydir = dirnode.Dir(dirname)
+ java_paths = map(lambda f, d=mydir: d.File(f), java_files)
+ arg.extend(java_paths)
+ os.path.walk(source[0].rdir().get_abspath(), visit, slist)
+
+ tlist = []
+ for file in slist:
+ pkg_dir, classes = parse_java(file.get_abspath())
+ if pkg_dir:
+ for c in classes:
+ tlist.append(target[0].Dir(pkg_dir).File(c+class_suffix))
+ elif classes:
+ for c in classes:
+ tlist.append(target[0].File(c+class_suffix))
+ else:
+ # This is an odd end case: no package and no classes.
+ # Just do our best based on the source file name.
+ tlist.append(target[0].File(str(file)[:-len(java_suffix)] + class_suffix))
+
+ return tlist, slist
+
+JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM',
+ emitter = emit_java_files,
+ target_factory = SCons.Node.FS.default_fs.Dir,
+ source_factory = SCons.Node.FS.default_fs.Dir)
+
def generate(env):
"""Add Builders and construction variables for javac to an Environment."""
- def emit_java_files(target, source, env):
- """Create and return lists of source java files
- and their corresponding target class files.
- """
- env['_JAVACLASSDIR'] = target[0]
- env['_JAVASRCDIR'] = source[0].rdir()
- java_suffix = env.get('JAVASUFFIX', '.java')
- class_suffix = env.get('JAVACLASSSUFFIX', '.class')
-
- slist = []
- js = _my_normcase(java_suffix)
- def visit(arg, dirname, names, js=js, dirnode=source[0].rdir()):
- java_files = filter(lambda n, js=js:
- _my_normcase(n[-len(js):]) == js,
- names)
- mydir = dirnode.Dir(dirname)
- java_paths = map(lambda f, d=mydir: d.File(f), java_files)
- arg.extend(java_paths)
- os.path.walk(source[0].rdir().abspath, visit, slist)
-
- tlist = []
- for file in slist:
- pkg_dir, classes = parse_java(file.abspath)
- if pkg_dir:
- for c in classes:
- tlist.append(target[0].Dir(pkg_dir).File(c+class_suffix))
- elif classes:
- for c in classes:
- tlist.append(target[0].File(c+class_suffix))
- else:
- # This is an odd end case: no package and no classes.
- # Just do our best based on the source file name.
- tlist.append(target[0].File(str(file)[:-len(java_suffix)] + class_suffix))
-
- return tlist, slist
-
- JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM',
- emitter = emit_java_files,
- target_factory = SCons.Node.FS.default_fs.Dir,
- source_factory = SCons.Node.FS.default_fs.Dir)
-
env['BUILDERS']['Java'] = JavaBuilder
env['JAVAC'] = 'javac'
diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py
index fc315d9..3aafe6c 100644
--- a/src/engine/SCons/Tool/linkloc.py
+++ b/src/engine/SCons/Tool/linkloc.py
@@ -65,8 +65,8 @@ class LinklocGenerator:
def __init__(self, cmdline):
self.cmdline = cmdline
- def __call__(self, env, target, source):
- if target is None:
+ def __call__(self, env, target, source, for_signature):
+ if for_signature:
# Expand the contents of any linker command files recursively
subs = 1
strsub = env.subst(self.cmdline)
diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py
index c89bc21..cd18bb5 100644
--- a/src/engine/SCons/Tool/mingw.py
+++ b/src/engine/SCons/Tool/mingw.py
@@ -55,10 +55,10 @@ def shlib_generator(target, source, env, for_signature):
cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS'])
implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
- if implib: cmd.append('-Wl,--out-implib,'+str(implib))
+ if implib: cmd.append('-Wl,--out-implib,'+implib.get_string(for_signature))
def_target = env.FindIxes(target, 'WIN32DEFPREFIX', 'WIN32DEFSUFFIX')
- if def_target: cmd.append('-Wl,--output-def,'+str(def_target))
+ if def_target: cmd.append('-Wl,--output-def,'+def_target.get_string(for_signature))
return [cmd]
diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py
index 4bf8f0e..8aa6f09 100644
--- a/src/engine/SCons/Tool/mslink.py
+++ b/src/engine/SCons/Tool/mslink.py
@@ -44,41 +44,34 @@ import msvc
from SCons.Tool.msvc import get_msdev_paths
-def pdbGenerator(env, target, source):
+def pdbGenerator(env, target, source, for_signature):
if target and env.has_key('PDB') and env['PDB']:
- return ['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG']
-
-def win32ShlinkTargets(target, source, env):
- if target:
- listCmd = []
- dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
- if dll: listCmd.append("/out:%s"%dll)
-
- implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
- if implib: listCmd.append("/implib:%s"%implib)
-
- return listCmd
- else:
- # For signature calculation
- return '/out:$TARGET'
-
-def win32ShlinkSources(target, source, env):
- if target:
- listCmd = []
-
- deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX")
- for src in source:
- if src == deffile:
- # Treat this source as a .def file.
- listCmd.append("/def:%s" % src)
- else:
- # Just treat it as a generic source file.
- listCmd.append(str(src))
- return listCmd
- else:
- # For signature calculation
- return "$SOURCES"
+ return ['/PDB:%s'%target[0].File(env['PDB']).get_string(for_signature),
+ '/DEBUG']
+def win32ShlinkTargets(target, source, env, for_signature):
+ listCmd = []
+ dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
+ if dll: listCmd.append("/out:%s"%dll.get_string(for_signature))
+
+ implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
+ if implib: listCmd.append("/implib:%s"%implib.get_string(for_signature))
+
+ return listCmd
+
+def win32ShlinkSources(target, source, env, for_signature):
+ listCmd = []
+
+ deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX")
+ for src in source:
+ if src == deffile:
+ # Treat this source as a .def file.
+ listCmd.append("/def:%s" % src.get_string(for_signature))
+ else:
+ # Just treat it as a generic source file.
+ listCmd.append(src)
+ return listCmd
+
def win32LibEmitter(target, source, env):
msvc.validate_vars(env)
diff --git a/src/engine/SCons/Tool/zip.py b/src/engine/SCons/Tool/zip.py
index 4404247..510aa6e 100644
--- a/src/engine/SCons/Tool/zip.py
+++ b/src/engine/SCons/Tool/zip.py
@@ -59,7 +59,7 @@ try:
internal_zip = 1
except ImportError:
- zip = "$ZIP $ZIPFLAGS $( ${TARGET.abspath} $) $SOURCES"
+ zip = "$ZIP $ZIPFLAGS ${TARGET.abspath} $SOURCES"
internal_zip = 0
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 47aa37e..b7aac31 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -77,6 +77,19 @@ def updrive(path):
path = string.upper(drive) + rest
return path
+if hasattr(types, 'UnicodeType'):
+ def to_String(s):
+ if isinstance(s, UserString):
+ t = type(s.data)
+ else:
+ t = type(s)
+ if t is types.UnicodeType:
+ return unicode(s)
+ else:
+ return str(s)
+else:
+ to_String = str
+
class Literal:
"""A wrapper for a string. If you use this object wrapped
around a string, then it will be interpreted as literal.
@@ -91,144 +104,82 @@ class Literal:
def is_literal(self):
return 1
-class PathList(UserList.UserList):
- """This class emulates the behavior of a list, but also implements
- the special "path dissection" attributes we can use to find
- suffixes, base names, etc. of the paths in the list.
-
- One other special attribute of this class is that, by
- overriding the __str__ and __repr__ methods, this class
- represents itself as a space-concatenated string of
- the list elements, as in:
-
- >>> pl=PathList(["/foo/bar.txt", "/baz/foo.txt"])
- >>> pl
- '/foo/bar.txt /baz/foo.txt'
- >>> pl.base
- 'bar foo'
- """
+class SpecialAttrWrapper(Literal):
+ """This is a wrapper for what we call a 'Node special attribute.'
+ This is any of the attributes of a Node that we can reference from
+ Environment variable substitution, such as $TARGET.abspath or
+ $SOURCES[1].filebase. We inherit from Literal so we can handle
+ special characters, plus we implement a for_signature method,
+ such that we can return some canonical string during signatutre
+ calculation to avoid unnecessary rebuilds."""
+
+ def __init__(self, lstr, for_signature=None):
+ """The for_signature parameter, if supplied, will be the
+ canonical string we return from for_signature(). Else
+ we will simply return lstr."""
+ Literal.__init__(self, lstr)
+ if for_signature:
+ self.forsig = for_signature
+ else:
+ self.forsig = lstr
+
+ def for_signature(self):
+ return self.forsig
+
+class CallableComposite(UserList.UserList):
+ """A simple composite callable class that, when called, will invoke all
+ of its contained callables with the same arguments."""
def __init__(self, seq = []):
UserList.UserList.__init__(self, seq)
- def __getattr__(self, name):
- # This is how we implement the "special" attributes
- # such as base, suffix, basepath, etc.
- try:
- return self.dictSpecialAttrs[name](self)
- except KeyError:
- raise AttributeError, 'PathList has no attribute: %s' % name
-
- def __splitPath(self, split_func=os.path.split):
- """This method calls the supplied split_func on each element
- in the contained list. We expect split_func to return a
- 2-tuple, usually representing two elements of a split file path,
- such as those returned by os.path.split().
-
- We return a 2-tuple of lists, each equal in length to the contained
- list. The first list represents all the elements from the
- first part of the split operation, the second represents
- all elements from the second part."""
- list1 = []
- list2 = []
- for strPath in self.data:
- first_part, second_part = split_func(strPath)
- list1.append(first_part)
- list2.append(second_part)
- # Note that we return explicit PathList() instances, not
- # self.__class__(). This makes sure the right attributes are
- # available even if this object is a Lister, not a PathList.
- return (PathList(list1), PathList(list2))
-
- def __getBasePath(self):
- """Return the file's directory and file name, with the
- suffix stripped."""
- return self.__splitPath(splitext)[0]
-
- def __getSuffix(self):
- """Return the file's suffix."""
- return self.__splitPath(splitext)[1]
-
- def __getFileName(self):
- """Return the file's name without the path."""
- return self.__splitPath()[1]
-
- def __getDir(self):
- """Return the file's path."""
- return self.__splitPath()[0]
-
- def __getBase(self):
- """Return the file name with path and suffix stripped."""
- return self.__getFileName().__splitPath(splitext)[0]
-
- def __getAbsPath(self):
- """Return the absolute path"""
- # Note that we return an explicit PathList() instance, not
- # self.__class__(). This makes sure the right attributes are
- # available even if this object is a Lister, not a PathList.
- return PathList(map(lambda x: updrive(os.path.abspath(x)), self.data))
-
- def __getSrcDir(self):
- """Return the directory containing the linked
- source file, or this file path, if not linked"""
- sp = self.__splitPath()[0]
- rv = []
- for dir in sp:
- dn = SCons.Node.FS.default_fs.Dir(str(dir))
- if (dn == None):
- rv = rv + ['']
- else:
- rv = rv + [str(dn.srcnode())]
- return PathList(rv)
-
- def __getSrcPath(self):
- """Return the path to the linked source file,
- or this file path, if not linked"""
- rv = []
- for dir in self.data:
- fn = SCons.Node.FS.default_fs.File(str(dir))
- if (fn == None):
- rv = rv + ['']
- else:
- rv = rv + [str(fn.srcnode())]
- return PathList(rv)
-
- def __posix(self):
- if os.sep == '/':
- return self
- else:
- return PathList(map(lambda x: string.replace(x, os.sep, '/'), self.data))
-
- dictSpecialAttrs = { "file" : __getFileName,
- "base" : __getBasePath,
- "filebase" : __getBase,
- "dir" : __getDir,
- "suffix" : __getSuffix,
- "abspath" : __getAbsPath,
- "srcpath" : __getSrcPath,
- "srcdir" : __getSrcDir,
- "posix" : __posix
- }
+ def __call__(self, *args, **kwargs):
+ retvals = map(lambda x, args=args, kwargs=kwargs: apply(x,
+ args,
+ kwargs),
+ self.data)
+ if self.data and (len(self.data) == len(filter(callable, retvals))):
+ return self.__class__(retvals)
+ return NodeList(retvals)
+
+class NodeList(UserList.UserList):
+ """This class is almost exactly like a regular list of Nodes
+ (actually it can hold any object), with one important difference.
+ If you try to get an attribute from this list, it will return that
+ attribute from every item in the list. For example:
+
+ >>> someList = NodeList([ ' foo ', ' bar ' ])
+ >>> someList.strip()
+ [ 'foo', 'bar' ]
+ """
+ def __init__(self, seq = []):
+ UserList.UserList.__init__(self, seq)
- def is_literal(self):
- return 1
+ def __nonzero__(self):
+ return len(self.data) != 0
def __str__(self):
- return string.join(self.data)
+ return string.join(map(str, self.data))
- def to_String(self):
- # Used by our variable-interpolation to interpolate a string.
- # The interpolation doesn't use __str__() for this because then
- # it interpolates other lists as "['x', 'y']".
- return string.join(self.data)
-
- def __repr__(self):
- return repr(string.join(self.data))
+ def __getattr__(self, name):
+ if not self.data:
+ # If there is nothing in the list, then we have no attributes to
+ # pass through, so raise AttributeError for everything.
+ raise AttributeError, "NodeList has no attribute: %s" % name
+
+ # Return a list of the attribute, gotten from every element
+ # in the list
+ attrList = map(lambda x, n=name: getattr(x, n), self.data)
+
+ # Special case. If the attribute is callable, we do not want
+ # to return a list of callables. Rather, we want to return a
+ # single callable that, when called, will invoke the function on
+ # all elements of this list.
+ if self.data and (len(self.data) == len(filter(callable, attrList))):
+ return CallableComposite(attrList)
+ return self.__class__(attrList)
- def __getitem__(self, item):
- # We must do this to ensure that single items returned
- # by index access have the special attributes such as
- # suffix and basepath.
- return self.__class__([ UserList.UserList.__getitem__(self, item), ])
+ def is_literal(self):
+ return 1
_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
@@ -278,11 +229,11 @@ def quote_spaces(arg):
# so that we do not accidentally smush two variables
# together during the recursive interpolation process.
-_cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
+_cv = re.compile(r'\$([_a-zA-Z][\.\w]*|{[^}]*})')
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
_newline = re.compile(r'[\r\n]+')
-def _convertArg(x):
+def _convertArg(x, strconv=to_String):
"""This function converts an individual argument. If the
argument is to be interpreted literally, with all special
characters escaped, then we insert a special code in front
@@ -298,16 +249,16 @@ def _convertArg(x):
if not literal:
# escape newlines as '\0\2', '\0\1' denotes an argument split
# Also escape double-dollar signs to mean the literal dollar sign.
- return string.replace(_newline.sub('\0\2', to_String(x)), '$$', '\0\4')
+ return string.replace(_newline.sub('\0\2', strconv(x)), '$$', '\0\4')
else:
# Interpret non-string args as literals.
# The special \0\3 code will tell us to encase this string
# in a Literal instance when we are all done
# Also escape out any $ signs because we don't want
# to continue interpolating a literal.
- return '\0\3' + string.replace(str(x), '$', '\0\4')
+ return '\0\3' + string.replace(strconv(x), '$', '\0\4')
-def _convert(x):
+def _convert(x, strconv = to_String):
"""This function is used to convert construction variable
values or the value of strSubst to a string for interpolation.
This function follows the rules outlined in the documentaion
@@ -316,12 +267,13 @@ def _convert(x):
return ''
elif is_String(x):
# escape newlines as '\0\2', '\0\1' denotes an argument split
- return _convertArg(_space_sep.sub('\0\1', x))
+ return _convertArg(_space_sep.sub('\0\1', x), strconv)
elif is_List(x):
# '\0\1' denotes an argument split
- return string.join(map(_convertArg, x), '\0\1')
+ return string.join(map(lambda x, s=strconv: _convertArg(x, s), x),
+ '\0\1')
else:
- return _convertArg(x)
+ return _convertArg(x, strconv)
class CmdStringHolder:
"""This is a special class used to hold strings generated
@@ -414,24 +366,47 @@ def subst_dict(target, source, env):
if not is_List(target):
target = [target]
- dict['TARGETS'] = PathList(map(os.path.normpath, map(str, target)))
+ dict['TARGETS'] = NodeList(target)
if dict['TARGETS']:
dict['TARGET'] = dict['TARGETS'][0]
- def rstr(x):
- try:
- return x.rstr()
- except AttributeError:
- return str(x)
if not is_List(source):
source = [source]
- dict['SOURCES'] = PathList(map(os.path.normpath, map(rstr, source)))
+ dict['SOURCES'] = NodeList(map(lambda x: x.rfile(), source))
if dict['SOURCES']:
dict['SOURCE'] = dict['SOURCES'][0]
return dict
-def scons_subst_list(strSubst, env, remove=None, target=None,
+# Constants for the "mode" parameter to scons_subst_list() and
+# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
+# gives a command line suitable for passing to a shell. SUBST_SIG
+# gives a command line appropriate for calculating the signature
+# of a command line...if this changes, we should rebuild.
+SUBST_RAW = 0
+SUBST_CMD = 1
+SUBST_SIG = 2
+
+_rm = re.compile(r'\$[()]')
+_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
+
+def _canonicalize(obj):
+ """Attempt to call the object's for_signature method,
+ which is expected to return a string suitable for use in calculating
+ a command line signature (i.e., it only changes when we should
+ rebuild the target). For instance, file Nodes will report only
+ their file name (with no path), so changing Repository settings
+ will not cause a rebuild."""
+ try:
+ return obj.for_signature()
+ except AttributeError:
+ return to_String(obj)
+
+# Indexed by the SUBST_* constants above.
+_regex_remove = [ None, _rm, _remove ]
+_strconv = [ to_String, to_String, _canonicalize ]
+
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None,
source=None):
"""
This function serves the same purpose as scons_subst(), except
@@ -457,39 +432,46 @@ def scons_subst_list(strSubst, env, remove=None, target=None,
(e.g. file names) to contain embedded newline characters.
"""
+ remove = _regex_remove[mode]
+ strconv = _strconv[mode]
+
if target != None:
dict = subst_dict(target, source, env)
else:
- dict = env.sig_dict()
+ dict = env.Dictionary()
def repl(m,
target=target,
source=source,
env=env,
local_vars = dict,
- global_vars = { "__env__" : env }):
+ global_vars = { "__env__" : env },
+ strconv=strconv,
+ sig=(mode != SUBST_CMD)):
key = m.group(1)
if key[0] == '{':
key = key[1:-1]
try:
e = eval(key, global_vars, local_vars)
- if callable(e):
- # We wait to evaluate callables until the end of everything
- # else. For now, we instert a special escape sequence
- # that we will look for later.
- return '\0\5' + _convert(e(target=target,
- source=source,
- env=env)) + '\0\5'
- else:
- # The \0\5 escape code keeps us from smushing two or more
- # variables together during recusrive substitution, i.e.
- # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
- return "\0\5" + _convert(e) + "\0\5"
except NameError:
return '\0\5'
+ if callable(e):
+ # We wait to evaluate callables until the end of everything
+ # else. For now, we instert a special escape sequence
+ # that we will look for later.
+ return '\0\5' + _convert(e(target=target,
+ source=source,
+ env=env,
+ for_signature=sig),
+ strconv) + '\0\5'
+ else:
+ # The \0\5 escape code keeps us from smushing two or more
+ # variables together during recusrive substitution, i.e.
+ # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
+ return "\0\5" + _convert(e, strconv) + "\0\5"
# Convert the argument to a string:
- strSubst = _convert(strSubst)
+ strSubst = _convert(strSubst, strconv)
# Do the interpolation:
n = 1
@@ -510,7 +492,7 @@ def scons_subst_list(strSubst, env, remove=None, target=None,
return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
listLines)
-def scons_subst(strSubst, env, remove=None, target=None,
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None,
source=None):
"""Recursively interpolates dictionary variables into
the specified string, returning the expanded result.
@@ -527,32 +509,48 @@ def scons_subst(strSubst, env, remove=None, target=None,
if target != None:
dict = subst_dict(target, source, env)
else:
- dict = env.sig_dict()
+ dict = env.Dictionary()
+
+ remove = _regex_remove[mode]
+ strconv = _strconv[mode]
def repl(m,
target=target,
source=source,
env=env,
local_vars = dict,
- global_vars = { '__env__' : env }):
+ global_vars = { '__env__' : env },
+ strconv=strconv,
+ sig=(mode != SUBST_CMD)):
key = m.group(1)
if key[0] == '{':
key = key[1:-1]
try:
e = eval(key, global_vars, local_vars)
- if callable(e):
- e = e(target=target, source=source, env=env)
- if e is None:
- s = ''
- elif is_List(e):
- try:
- s = e.to_String()
- except AttributeError:
- s = string.join(map(to_String, e), ' ')
- else:
- s = to_String(e)
except NameError:
+ return '\0\5'
+ if callable(e):
+ e = e(target=target, source=source, env=env, for_signature=sig)
+
+ def conv(arg, strconv=strconv):
+ literal = 0
+ try:
+ if arg.is_literal():
+ literal = 1
+ except AttributeError:
+ pass
+ ret = strconv(arg)
+ if literal:
+ # Escape dollar signs to prevent further
+ # substitution on literals.
+ ret = string.replace(ret, '$', '\0\4')
+ return ret
+ if e is None:
s = ''
+ elif is_List(e):
+ s = string.join(map(conv, e), ' ')
+ else:
+ s = conv(e)
# Insert placeholders to avoid accidentally smushing
# separate variables together.
return "\0\5" + s + "\0\5"
@@ -563,7 +561,8 @@ def scons_subst(strSubst, env, remove=None, target=None,
# escape double dollar signs
strSubst = string.replace(strSubst, '$$', '\0\4')
strSubst,n = _cv.subn(repl, strSubst)
- # and then remove remove
+
+ # remove the remove regex
if remove:
strSubst = remove.sub('', strSubst)
@@ -615,19 +614,6 @@ def is_Dict(e):
def is_List(e):
return type(e) is types.ListType or isinstance(e, UserList.UserList)
-if hasattr(types, 'UnicodeType'):
- def to_String(s):
- if isinstance(s, UserString):
- t = type(s.data)
- else:
- t = type(s)
- if t is types.UnicodeType:
- return unicode(s)
- else:
- return str(s)
-else:
- to_String = str
-
def argmunge(arg):
return Split(arg)
@@ -678,7 +664,7 @@ def mapPaths(paths, dir, env=None):
return str(dir)
if os.path.isabs(path) or path[0] == '#':
return path
- return dir.path_ + path
+ return str(dir) + os.sep + path
return path
if not is_List(paths):
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index b7aa478..e0543d9 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -25,17 +25,13 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
-import re
import string
import sys
import types
import unittest
-import SCons.Node
-import SCons.Node.FS
from SCons.Util import *
import TestCmd
-
class OutBuffer:
def __init__(self):
self.buffer = ""
@@ -58,33 +54,55 @@ class DummyEnv:
dict["SOURCES"] = 'ssig'
return dict
-def CmdGen1(target, source, env):
+def CmdGen1(target, source, env, for_signature):
# Nifty trick...since Environment references are interpolated,
# instantiate an instance of a callable class with this one,
# which will then get evaluated.
- assert target == 't', target
- assert source == 's', source
- return "${CMDGEN2('foo')}"
+ assert str(target) == 't', target
+ assert str(source) == 's', source
+ return "${CMDGEN2('foo', %d)}" % for_signature
class CmdGen2:
- def __init__(self, mystr):
+ def __init__(self, mystr, forsig):
self.mystr = mystr
+ self.expect_for_signature = forsig
- def __call__(self, target, source, env):
- assert target == 't', target
- assert source == 's', source
+ def __call__(self, target, source, env, for_signature):
+ assert str(target) == 't', target
+ assert str(source) == 's', source
+ assert for_signature == self.expect_for_signature, for_signature
return [ self.mystr, env.Dictionary('BAR') ]
class UtilTestCase(unittest.TestCase):
def test_subst(self):
"""Test the subst function"""
loc = {}
- target = [ "./foo/bar.exe",
- "/bar/baz.obj",
- "../foo/baz.obj" ]
- source = [ "./foo/blah.cpp",
- "/bar/ack.cpp",
- "../foo/ack.c" ]
+
+ class N:
+ """Simple node work-alike with some extra stuff for testing."""
+ def __init__(self, data):
+ self.data = os.path.normpath(data)
+
+ def __str__(self):
+ return self.data
+
+ def is_literal(self):
+ return 1
+
+ def get_stuff(self, extra):
+ return self.data + extra
+
+ def rfile(self):
+ return self
+
+ foo = 1
+
+ target = [ N("./foo/bar.exe"),
+ N("/bar/baz.obj"),
+ N("../foo/baz.obj") ]
+ source = [ N("./foo/blah.cpp"),
+ N("/bar/ack.cpp"),
+ N("../foo/ack.c") ]
loc['xxx'] = None
loc['zero'] = 0
loc['one'] = 1
@@ -122,58 +140,33 @@ class UtilTestCase(unittest.TestCase):
target=target, source=source)
assert newcom == cvt("test foo/bar.exe[0]")
- newcom = scons_subst("test ${TARGET.file}", env,
- target=target, source=source)
- assert newcom == cvt("test bar.exe")
-
- newcom = scons_subst("test ${TARGET.filebase}", env,
+ newcom = scons_subst("test $TARGETS.foo", env,
target=target, source=source)
- assert newcom == cvt("test bar")
+ assert newcom == "test 1 1 1", newcom
- newcom = scons_subst("test ${TARGET.suffix}", env,
+ newcom = scons_subst("test ${SOURCES[0:2].foo}", env,
target=target, source=source)
- assert newcom == cvt("test .exe")
+ assert newcom == "test 1 1", newcom
- newcom = scons_subst("test ${TARGET.base}", env,
+ newcom = scons_subst("test $SOURCE.foo", env,
target=target, source=source)
- assert newcom == cvt("test foo/bar")
+ assert newcom == "test 1", newcom
- newcom = scons_subst("test ${TARGET.dir}", env,
+ newcom = scons_subst("test ${TARGET.get_stuff('blah')}", env,
target=target, source=source)
- assert newcom == cvt("test foo")
+ assert newcom == cvt("test foo/bar.exeblah"), newcom
- newcom = scons_subst("test ${TARGET.abspath}", env,
+ newcom = scons_subst("test ${SOURCES.get_stuff('blah')}", env,
target=target, source=source)
- assert newcom == cvt("test %s/foo/bar.exe"%SCons.Util.updrive(os.getcwd())), newcom
+ assert newcom == cvt("test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah"), newcom
- newcom = scons_subst("test ${SOURCES.abspath}", env,
+ newcom = scons_subst("test ${SOURCES[0:2].get_stuff('blah')}", env,
target=target, source=source)
- assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(SCons.Util.updrive(os.getcwd()),
- SCons.Util.updrive(os.path.abspath(os.path.normpath("/bar/ack.cpp"))),
- SCons.Util.updrive(os.path.normpath(os.getcwd()+"/..")))), newcom
+ assert newcom == cvt("test foo/blah.cppblah /bar/ack.cppblah"), newcom
- newcom = scons_subst("test ${SOURCE.abspath}", env,
+ newcom = scons_subst("test ${SOURCES[0:2].get_stuff('blah')}", env,
target=target, source=source)
- assert newcom == cvt("test %s/foo/blah.cpp"%SCons.Util.updrive(os.getcwd())), newcom
-
- # Note that we don't use the cvt() helper function here,
- # because we're testing that the .posix attribute does its own
- # conversion of the path name backslashes to slashes.
- newcom = scons_subst("test ${TARGET.posix} ${SOURCE.posix}", env,
- target=target, source=source)
- assert newcom == "test foo/bar.exe foo/blah.cpp", newcom
-
- SCons.Node.FS.default_fs.BuildDir("#baz","#foo")
-
- newcom = scons_subst("test ${SOURCE.srcdir}", env,
- target=target, source=['baz/bar.c'])
-
- assert newcom == cvt("test foo"), newcom
-
- newcom = scons_subst("test ${SOURCE.srcpath}", env,
- target=target, source=['baz/bar.c'])
-
- assert newcom == cvt("test foo/bar.c"), newcom
+ assert newcom == cvt("test foo/blah.cppblah /bar/ack.cppblah"), newcom
newcom = scons_subst("test $xxx", env)
assert newcom == cvt("test"), newcom
@@ -184,10 +177,10 @@ class UtilTestCase(unittest.TestCase):
newcom = scons_subst("test $( $xxx $)", env)
assert newcom == cvt("test $( $)"), newcom
- newcom = scons_subst("test $($xxx$)", env, re.compile('\$[()]'))
+ newcom = scons_subst("test $($xxx$)", env, mode=SUBST_SIG)
assert newcom == cvt("test"), newcom
- newcom = scons_subst("test $( $xxx $)", env, re.compile('\$[()]'))
+ newcom = scons_subst("test $( $xxx $)", env, mode=SUBST_SIG)
assert newcom == cvt("test"), newcom
newcom = scons_subst("test $zero", env)
@@ -196,11 +189,8 @@ class UtilTestCase(unittest.TestCase):
newcom = scons_subst("test $one", env)
assert newcom == cvt("test 1"), newcom
- newcom = scons_subst("test aXbXcXd", env, re.compile('X'))
- assert newcom == cvt("test abcd"), newcom
-
newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
- env, target='t', source='s')
+ env, target=N('t'), source=N('s'))
assert newcom == cvt("test foo baz s t"), newcom
# Test against a former bug in scons_subst_list()
@@ -217,6 +207,23 @@ class UtilTestCase(unittest.TestCase):
newcom = scons_subst("$$FOO$BAZ", DummyEnv(glob))
assert newcom == "$FOOBLAT", newcom
+ class TestLiteral:
+ def __init__(self, literal):
+ self.literal = literal
+
+ def __str__(self):
+ return self.literal
+
+ def is_literal(self):
+ return 1
+
+ # Test that a literal will stop dollar-sign substitution
+ glob = { "FOO" : "BAR",
+ "BAZ" : TestLiteral("$FOO"),
+ "BAR" : "$FOO" }
+ newcom = scons_subst("$FOO $BAZ $BAR", DummyEnv(glob))
+ assert newcom == "BAR $FOO BAR", newcom
+
def test_splitext(self):
assert splitext('foo') == ('foo','')
assert splitext('foo.bar') == ('foo','.bar')
@@ -227,19 +234,21 @@ class UtilTestCase(unittest.TestCase):
class Node:
def __init__(self, name):
- self.name = name
+ self.name = os.path.normpath(name)
def __str__(self):
return self.name
def is_literal(self):
return 1
+ def rfile(self):
+ return self
loc = {}
- target = [ "./foo/bar.exe",
- "/bar/baz with spaces.obj",
- "../foo/baz.obj" ]
- source = [ "./foo/blah with spaces.cpp",
- "/bar/ack.cpp",
- "../foo/ack.c" ]
+ target = [ Node("./foo/bar.exe"),
+ Node("/bar/baz with spaces.obj"),
+ Node("../foo/baz.obj") ]
+ source = [ Node("./foo/blah with spaces.cpp"),
+ Node("/bar/ack.cpp"),
+ Node("../foo/ack.c") ]
loc['xxx'] = None
loc['NEWLINE'] = 'before\nafter'
@@ -307,7 +316,7 @@ class UtilTestCase(unittest.TestCase):
# Test interpolating a callable.
cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES", env,
- target='t', source='s')
+ target=Node('t'), source=Node('s'))
assert len(cmd_list) == 1, len(cmd_list)
assert cmd_list[0][0] == 'testing', cmd_list[0][0]
assert cmd_list[0][1] == 'foo', cmd_list[0][1]
@@ -335,8 +344,8 @@ class UtilTestCase(unittest.TestCase):
return '**' + foo + '**'
def quote_func(foo):
return foo
- glob = { "FOO" : PathList([ 'foo\nwith\nnewlines',
- 'bar\nwith\nnewlines' ]) }
+ glob = { "FOO" : [ Literal('foo\nwith\nnewlines'),
+ Literal('bar\nwith\nnewlines') ] }
cmd_list = scons_subst_list("$FOO", DummyEnv(glob))
assert cmd_list[0][0] == 'foo\nwith\nnewlines', cmd_list[0][0]
cmd_list[0][0].escape(escape_func)
@@ -595,11 +604,41 @@ class UtilTestCase(unittest.TestCase):
assert cmd_list[0] == 'BAZ', cmd_list[0]
assert cmd_list[1] == '**$BAR**', cmd_list[1]
+ def test_SpecialAttrWrapper(self):
+ """Test the SpecialAttrWrapper() function."""
+ input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
+
+ def escape_func(cmd):
+ return '**' + cmd + '**'
+
+
+ cmd_list = scons_subst_list(input_list,
+ DummyEnv({ 'FOO' : 'BAZ',
+ 'BAR' : 'BLAT' }))
+ map(lambda x, e=escape_func: x.escape(e), cmd_list[0])
+ cmd_list = map(str, cmd_list[0])
+ assert cmd_list[0] == 'BAZ', cmd_list[0]
+ assert cmd_list[1] == '**$BAR**', cmd_list[1]
+
+ cmd_list = scons_subst_list(input_list,
+ DummyEnv({ 'FOO' : 'BAZ',
+ 'BAR' : 'BLAT' }),
+ mode=SUBST_SIG)
+ map(lambda x, e=escape_func: x.escape(e), cmd_list[0])
+ cmd_list = map(str, cmd_list[0])
+ assert cmd_list[0] == 'BAZ', cmd_list[0]
+ assert cmd_list[1] == '**BLEH**', cmd_list[1]
+
def test_mapPaths(self):
"""Test the mapPaths function"""
- fs = SCons.Node.FS.FS()
- dir=fs.Dir('foo')
- file=fs.File('bar/file')
+ class MyFileNode:
+ def __init__(self, path):
+ self.path = path
+ def __str__(self):
+ return self.path
+
+ dir=MyFileNode('foo')
+ file=MyFileNode('bar/file')
class DummyEnv:
def subst(self, arg):
@@ -618,11 +657,11 @@ class UtilTestCase(unittest.TestCase):
def test_display(self):
old_stdout = sys.stdout
sys.stdout = OutBuffer()
- SCons.Util.display("line1")
+ display("line1")
display.set_mode(0)
- SCons.Util.display("line2")
+ display("line2")
display.set_mode(1)
- SCons.Util.display("line3")
+ display("line3")
assert sys.stdout.buffer == "line1\nline3\n"
sys.stdout = old_stdout
@@ -648,12 +687,12 @@ class UtilTestCase(unittest.TestCase):
"Removed " + os.path.join(base, xxx) + '\n' + \
"Removed directory " + base + '\n'
- SCons.Util.fs_delete(base, remove=0)
+ fs_delete(base, remove=0)
assert sys.stdout.buffer == exp, sys.stdout.buffer
assert os.path.exists(sub1_yyy)
sys.stdout.buffer = ""
- SCons.Util.fs_delete(base, remove=1)
+ fs_delete(base, remove=1)
assert sys.stdout.buffer == exp
assert not os.path.exists(base)
@@ -666,7 +705,7 @@ class UtilTestCase(unittest.TestCase):
filename = tempfile.mktemp()
str = '1234567890 ' + filename
open(filename, 'w').write(str)
- assert open(SCons.Util.get_native_path(filename)).read() == str
+ assert open(get_native_path(filename)).read() == str
def test_subst_dict(self):
"""Test substituting dictionary values in an Action
@@ -675,14 +714,24 @@ class UtilTestCase(unittest.TestCase):
assert d['a'] == 'A', d
assert d['b'] == 'B', d
- d = subst_dict(target = 't', source = 's', env=DummyEnv())
- assert str(d['TARGETS']) == 't', d['TARGETS']
+ class SimpleNode:
+ def __init__(self, data):
+ self.data = data
+ def __str__(self):
+ return self.data
+ def rfile(self):
+ return self
+ def is_literal(self):
+ return 1
+
+ d = subst_dict(target = SimpleNode('t'), source = SimpleNode('s'), env=DummyEnv())
+ assert str(d['TARGETS'][0]) == 't', d['TARGETS']
assert str(d['TARGET']) == 't', d['TARGET']
- assert str(d['SOURCES']) == 's', d['SOURCES']
+ assert str(d['SOURCES'][0]) == 's', d['SOURCES']
assert str(d['SOURCE']) == 's', d['SOURCE']
- d = subst_dict(target = ['t1', 't2'],
- source = ['s1', 's2'],
+ d = subst_dict(target = [SimpleNode('t1'), SimpleNode('t2')],
+ source = [SimpleNode('s1'), SimpleNode('s2')],
env = DummyEnv())
TARGETS = map(lambda x: str(x), d['TARGETS'])
TARGETS.sort()
@@ -698,11 +747,11 @@ class UtilTestCase(unittest.TestCase):
self.name = name
def __str__(self):
return self.name
- def rstr(self):
- return 'rstr-' + self.name
+ def rfile(self):
+ return self.__class__('rstr-' + self.name)
- d = subst_dict(target = [N('t3'), 't4'],
- source = ['s3', N('s4')],
+ d = subst_dict(target = [N('t3'), SimpleNode('t4')],
+ source = [SimpleNode('s3'), N('s4')],
env = DummyEnv())
TARGETS = map(lambda x: str(x), d['TARGETS'])
TARGETS.sort()
@@ -711,6 +760,29 @@ class UtilTestCase(unittest.TestCase):
SOURCES.sort()
assert SOURCES == ['rstr-s4', 's3'], d['SOURCES']
+ def test_NodeList(self):
+ """Test NodeList class"""
+ class TestClass:
+ def __init__(self, name, child=None):
+ self.child = child
+ self.bar = name
+ def foo(self):
+ return self.bar + "foo"
+ def getself(self):
+ return self
+
+ t1 = TestClass('t1', TestClass('t1child'))
+ t2 = TestClass('t2', TestClass('t2child'))
+ t3 = TestClass('t3')
+
+ nl = NodeList([t1, t2, t3])
+ assert nl.foo() == [ 't1foo', 't2foo', 't3foo' ], nl.foo()
+ assert nl.bar == [ 't1', 't2', 't3' ], nl.bar
+ assert nl.getself().bar == [ 't1', 't2', 't3' ], nl.getself().bar
+ assert nl[0:2].child.foo() == [ 't1childfoo', 't2childfoo' ], \
+ nl[0:2].child.foo()
+ assert nl[0:2].child.bar == [ 't1child', 't2child' ], \
+ nl[0:2].child.bar
if __name__ == "__main__":
suite = unittest.makeSuite(UtilTestCase, 'test_')