- Better documentation of the different ways to export variables to a
subsidiary SConscript file.
+ - Support fetching arbitrary files from the TARGETS or SOURCES lists
+ (e.g. ${SOURCES[2]}) when calculating the build signature of a
+ command.
+
From Lachlan O'Dea:
- Add SharedObject() support to the masm tool.
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),
+ # {})
return SCons.Util.scons_subst(string.join(self.cmd_list),
- self._sig_dict(target, source, env), {})
+ env.sig_dict(),
+ {})
def get_contents(self, target, source, env):
"""Return the signature contents of this action's command line.
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)
return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)),
- self._sig_dict(target, source, env), {}, _remove)
+ env.sig_dict(),
+ {},
+ _remove)
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
return self.d.get(key, value)
def items(self):
return self.d.items()
+ def sig_dict(self):
+ d = {}
+ for k,v in self.items(): d[k] = v
+ d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
+ d['TARGET'] = d['TARGETS'][0]
+ d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
+ d['SOURCE'] = d['SOURCES'][0]
+ return d
if os.name == 'java':
python = os.path.join(sys.prefix, 'jython')
def test_show(self):
"""Test the show() method
"""
+ save_stdout = sys.stdout
+
save = SCons.Action.print_actions
SCons.Action.print_actions = 0
assert s == "foobar\n", s
SCons.Action.print_actions = save
- sys.stdout = StringIO.StringIO()
+ sys.stdout = save_stdout
def test_get_actions(self):
"""Test the get_actions() method
env=Environment(foo = 'FFF', bar = 'BBB'))
assert c == "| $( FFF | BBB $) |", c
+ # We've discusssed using the real target and source names in a
+ # CommandAction's signature contents. This would have 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, 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']
+ env = Environment()
+
+ a = SCons.Action.CommandAction(["$TARGET"])
+ c = a.get_raw_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["${TARGETS[2]}"])
+ c = a.get_raw_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["$SOURCE"])
+ c = a.get_raw_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["${SOURCES[2]}"])
+ c = a.get_raw_contents(target=t, source=s, env=env)
+ 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
+
def test_get_contents(self):
"""Test fetching the contents of a command Action
"""
env=Environment(foo = 'FFF', bar = 'BBB'))
assert c == "| |", c
+ # We've discusssed using the real target and source names in a
+ # CommandAction's signature contents. This would have 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, 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']
+ env = Environment()
+
+ a = SCons.Action.CommandAction(["$TARGET"])
+ c = a.get_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["${TARGETS[2]}"])
+ c = a.get_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["$SOURCE"])
+ c = a.get_contents(target=t, source=s, env=env)
+ 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
+
+ a = SCons.Action.CommandAction(["${SOURCES[2]}"])
+ c = a.get_contents(target=t, source=s, env=env)
+ 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
+
class CommandGeneratorActionTestCase(unittest.TestCase):
def test_init(self):
return env
def items(self):
return self.d.items()
+ def sig_dict(self):
+ d = {}
+ for k,v in self.items(): d[k] = v
+ d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
+ d['TARGET'] = d['TARGETS'][0]
+ d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
+ d['SOURCE'] = d['SOURCES'][0]
+ return d
env = Environment()
"""Test returning the signature contents of a Builder
"""
- b1 = SCons.Builder.Builder(action = "foo")
+ b1 = SCons.Builder.Builder(action = "foo ${TARGETS[5]}")
contents = b1.get_contents([],[],Environment())
- assert contents == "foo", contents
+ assert contents == "foo __t6__", contents
+
+ b1 = SCons.Builder.Builder(action = "bar ${SOURCES[3:5]}")
+ contents = b1.get_contents([],[],Environment())
+ assert contents == "bar __s4__ __s5__", contents
b2 = SCons.Builder.Builder(action = Func)
contents = b2.get_contents([],[],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.
+ """
+ class lister:
+ 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 __str__(self):
+ return self._format(0) + " " + self._format(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)
+
+ dict = {}
+ for k,v in self.items(): dict[k] = v
+ dict['TARGETS'] = lister('__t%d__')
+ dict['TARGET'] = dict['TARGETS'][0]
+ dict['SOURCES'] = lister('__s%d__')
+ dict['SOURCE'] = dict['SOURCES'][0]
+ return dict
'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 = 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
+
if __name__ == "__main__":
suite = unittest.makeSuite(EnvironmentTestCase, 'test_')