Fix using more than two targets or sources in a list.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 2 Mar 2003 02:43:16 +0000 (02:43 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 2 Mar 2003 02:43:16 +0000 (02:43 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@603 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py

index 0ac801ceaf9fc8fe509ee401adef288fab6b7dba..22f0bdce11ccb2acac042010d7ed6b3c8c428191 100644 (file)
@@ -47,6 +47,10 @@ RELEASE 0.12 - XXX
   - 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.
index 960ffce95cea68fc5606cc6b8d9979ad6260a89d..ecad9b0b7e906cd00a45d7cd7c411883ba6c0b0c 100644 (file)
@@ -293,8 +293,19 @@ 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),
+        #                              {})
         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.
@@ -302,8 +313,21 @@ 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)
         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."""
index 61cea5371ec7739540f18ce70f2c2cd16ca9225b..de157ae7bf7beba4df7bbc7dd94c600cd5d5c224 100644 (file)
@@ -100,6 +100,14 @@ class Environment:
         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')
@@ -190,6 +198,8 @@ class ActionBaseTestCase(unittest.TestCase):
     def test_show(self):
         """Test the show() method
         """
+        save_stdout = sys.stdout
+
         save = SCons.Action.print_actions
         SCons.Action.print_actions = 0
 
@@ -209,7 +219,7 @@ class ActionBaseTestCase(unittest.TestCase):
         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
@@ -543,6 +553,51 @@ class CommandActionTestCase(unittest.TestCase):
                                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
         """
@@ -552,6 +607,51 @@ class CommandActionTestCase(unittest.TestCase):
                            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):
index 3ad6b238455c5ce7098e8cfaaae7054e56e312b5..433fadf7ade7895004e6aace8adf5559c89ce703 100644 (file)
@@ -98,6 +98,14 @@ class Environment:
         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()
 
@@ -239,9 +247,13 @@ class BuilderTestCase(unittest.TestCase):
         """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())
index 68571097fdd3bf7b92ad9c2d4049efaf4bbbcb2a..fdfa8b2e87dd805d3215f75c97e512637b968145 100644 (file)
@@ -542,4 +542,38 @@ 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.
+        """
+        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
index 1163545cc87d0417ddafccc160cac8033f036fb2..e590d7d21e3f634da935e069593dc13dcbf07a59 100644 (file)
@@ -794,6 +794,46 @@ 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 = 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_')