Add explicit support for Builder wrapper functions (pseudo-Builders) in the BUILDERS...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 9 Nov 2004 16:08:23 +0000 (16:08 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 9 Nov 2004 16:08:23 +0000 (16:08 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1150 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
test/builder-wrappers.py [new file with mode: 0644]

index d9a397f30834b82b8a5606a8e11bfbc5997b9238..5f6968b341623bf1f8150c54650e9c2c75bac571 100644 (file)
@@ -113,6 +113,10 @@ RELEASE 0.97 - XXX
     for another target, to avoid trying to build it again when it comes
     up in the target list.
 
+  - Allow a function with the right calling signature to be put directly
+    in an Environment's BUILDERS dictionary, making for easier creation
+    and use of wrappers (pseudo-Builders) that call other Builders.
+
   From Wayne Lee:
 
   - Avoid "maximum recursion limit" errors when removing $(-$) pairs
index d6b7597b88c7659960a281178937e3163f84d786..40361d478c3d86e3b0cc69dd3e82d3435f89185a 100644 (file)
@@ -559,23 +559,15 @@ class BuilderBase:
 
         return tlist, slist
 
-    def _execute(self, env, target=None, source=_null, overwarn={}, executor_kw={}):
-        if source is _null:
-            source = target
-            target = None
-
-        if(self.single_source and
-           SCons.Util.is_List(source) and
-           len(source) > 1 and
-           target is None):
+    def _execute(self, env, target, source, overwarn={}, executor_kw={}):
+        # We now assume that target and source are lists or None.
+        if self.single_source and len(source) > 1 and target is None:
             result = []
             if target is None: target = [None]*len(source)
-            for k in range(len(source)):
-                t = self._execute(env, target[k], source[k], overwarn)
-                if SCons.Util.is_List(t):
-                    result.extend(t)
-                else:
-                    result.append(t)
+            for tgt, src in zip(target, source):
+                if not tgt is None: tgt = [tgt]
+                if not src is None: src = [src]
+                result.extend(self._execute(env, tgt, src, overwarn))
             return result
         
         tlist, slist = self._create_nodes(env, overwarn, target, source)
@@ -588,7 +580,10 @@ class BuilderBase:
 
         return tlist
 
-    def __call__(self, env, target=None, source=_null, chdir=_null, **kw):
+    def __call__(self, env, target=None, source=None, chdir=_null, **kw):
+        # We now assume that target and source are lists or None.
+        # The caller (typically Environment.BuilderWrapper) is
+        # responsible for converting any scalar values to lists.
         if chdir is _null:
             ekw = self.executor_kw
         else:
@@ -709,11 +704,8 @@ class MultiStepBuilder(BuilderBase):
         self.sdict = {}
         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
 
-    def _execute(self, env, target = None, source = _null, overwarn={}, executor_kw={}):
-        if source is _null:
-            source = target
-            target = None
-
+    def _execute(self, env, target, source, overwarn={}, executor_kw={}):
+        # We now assume that target and source are lists or None.
         slist = env.arg2nodes(source, self.source_factory)
         final_sources = []
 
@@ -736,7 +728,7 @@ class MultiStepBuilder(BuilderBase):
         for snode in slist:
             for srcsuf in src_suffixes:
                 if str(snode)[-len(srcsuf):] == srcsuf and sdict.has_key(srcsuf):
-                    tgt = sdict[srcsuf]._execute(env, None, snode, overwarn)
+                    tgt = sdict[srcsuf]._execute(env, None, [snode], overwarn)
                     # If the subsidiary Builder returned more than one target,
                     # then filter out any sources that this Builder isn't
                     # capable of building.
index 2075bf0e85d8bf51f0e4e480127a5a522714b8d5..98e5a58dd1db35c1d9959ead70a80aa338839070 100644 (file)
@@ -254,7 +254,7 @@ class BuilderTestCase(unittest.TestCase):
         n20 = MyNode_without_target_from_source('n20')
         flag = 0
         try:
-            target = builder(env, source=n20)
+            target = builder(env, None, source=n20)
         except SCons.Errors.UserError, e:
             flag = 1
         assert flag, "UserError should be thrown if a source node can't create a target."
@@ -264,7 +264,7 @@ class BuilderTestCase(unittest.TestCase):
                                         source_factory=MyNode,
                                         prefix='p-',
                                         suffix='.s')
-        target = builder(env, source='n21')[0]
+        target = builder(env, None, source='n21')[0]
         assert target.name == 'p-n21.s', target
 
         builder = SCons.Builder.Builder(misspelled_action="foo",
@@ -412,10 +412,10 @@ class BuilderTestCase(unittest.TestCase):
         tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
         assert tgt.path == 'libtgt2a tgt2b', \
                 "Target has unexpected name: %s" % tgt.path
-        tgt = builder(env, source = 'src3')[0]
+        tgt = builder(env, target = None, source = 'src3')[0]
         assert tgt.path == 'libsrc3', \
                 "Target has unexpected name: %s" % tgt.path
-        tgt = builder(env, source = 'lib/src4')[0]
+        tgt = builder(env, target = None, source = 'lib/src4')[0]
         assert tgt.path == os.path.join('lib', 'libsrc4'), \
                 "Target has unexpected name: %s" % tgt.path
         tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
@@ -439,17 +439,17 @@ class BuilderTestCase(unittest.TestCase):
                                                   '$FOO' : 'foo-',
                                                   '.zzz' : my_emit},
                                         action = '')
-        tgt = builder(my_env, source = 'f1')[0]
+        tgt = builder(my_env, target = None, source = 'f1')[0]
         assert tgt.path == 'default-f1', tgt.path
-        tgt = builder(my_env, source = 'f2.c')[0]
+        tgt = builder(my_env, target = None, source = 'f2.c')[0]
         assert tgt.path == 'default-f2', tgt.path
-        tgt = builder(my_env, source = 'f3.in')[0]
+        tgt = builder(my_env, target = None, source = 'f3.in')[0]
         assert tgt.path == 'out-f3', tgt.path
-        tgt = builder(my_env, source = 'f4.x')[0]
+        tgt = builder(my_env, target = None, source = 'f4.x')[0]
         assert tgt.path == 'y-f4', tgt.path
-        tgt = builder(my_env, source = 'f5.foo')[0]
+        tgt = builder(my_env, target = None, source = 'f5.foo')[0]
         assert tgt.path == 'foo-f5', tgt.path
-        tgt = builder(my_env, source = 'f6.zzz')[0]
+        tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
         assert tgt.path == 'emit-f6', tgt.path
 
     def test_src_suffix(self):
@@ -492,7 +492,7 @@ class BuilderTestCase(unittest.TestCase):
         b6 = SCons.Builder.Builder(action = '',
                                    src_suffix='_src.a',
                                    suffix='.b')
-        tgt = b6(env, source='foo_src.a')
+        tgt = b6(env, target=None, source='foo_src.a')
         assert str(tgt[0]) == 'foo.b', str(tgt[0])
 
         b7 = SCons.Builder.Builder(action = '',
@@ -501,16 +501,16 @@ class BuilderTestCase(unittest.TestCase):
         b8 = SCons.Builder.Builder(action = '',
                                    src_builder=b7,
                                    suffix='.c')
-        tgt = b8(env, source='foo_source.a')
+        tgt = b8(env, target=None, source='foo_source.a')
         assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
         src = SCons.Node.FS.default_fs.File('foo_source.a')
-        tgt = b8(env, source=src)
+        tgt = b8(env, target=None, source=src)
         assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
 
         b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'},
                                    suffix='.c')
         b9.add_action('_altsrc.b', 'altaction')
-        tgt = b9(env, source='foo_altsrc.b')
+        tgt = b9(env, target=None, source='foo_altsrc.b')
         assert str(tgt[0]) == 'foo.c', str(tgt[0])
 
     def test_suffix(self):
@@ -530,7 +530,7 @@ class BuilderTestCase(unittest.TestCase):
         tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
         assert tgt.path == 'tgt4a tgt4b.o', \
                 "Target has unexpected name: %s" % tgt.path
-        tgt = builder(env, source = 'src5')[0]
+        tgt = builder(env, target = None, source = 'src5')[0]
         assert tgt.path == 'src5.o', \
                 "Target has unexpected name: %s" % tgt.path
 
@@ -551,17 +551,17 @@ class BuilderTestCase(unittest.TestCase):
                                                   '$BAR' : '.new',
                                                   '.zzz' : my_emit},
                                         action='')
-        tgt = builder(my_env, source = 'f1')[0]
+        tgt = builder(my_env, target = None, source = 'f1')[0]
         assert tgt.path == 'f1.default', tgt.path
-        tgt = builder(my_env, source = 'f2.c')[0]
+        tgt = builder(my_env, target = None, source = 'f2.c')[0]
         assert tgt.path == 'f2.default', tgt.path
-        tgt = builder(my_env, source = 'f3.in')[0]
+        tgt = builder(my_env, target = None, source = 'f3.in')[0]
         assert tgt.path == 'f3.out', tgt.path
-        tgt = builder(my_env, source = 'f4.x')[0]
+        tgt = builder(my_env, target = None, source = 'f4.x')[0]
         assert tgt.path == 'f4.y', tgt.path
-        tgt = builder(my_env, source = 'f5.bar')[0]
+        tgt = builder(my_env, target = None, source = 'f5.bar')[0]
         assert tgt.path == 'f5.new', tgt.path
-        tgt = builder(my_env, source = 'f6.zzz')[0]
+        tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
         assert tgt.path == 'f6.emit', tgt.path
 
     def test_single_source(self):
@@ -589,7 +589,7 @@ class BuilderTestCase(unittest.TestCase):
         tgt.prepare()
         tgt.build()
         assert env['CNT'][0] == 2
-        tgts = builder(env, infiles[2:4])
+        tgts = builder(env, None, infiles[2:4])
         for t in tgts: t.prepare()
         tgts[0].build()
         tgts[1].build()
@@ -677,7 +677,7 @@ class BuilderTestCase(unittest.TestCase):
         assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
         assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
 
-        tgt = builder2(env, 'aaa.bar')[0]
+        tgt = builder2(env, None, 'aaa.bar')[0]
         assert str(tgt) == 'aaa', str(tgt)
         assert str(tgt.sources[0]) == 'aaa.foo', str(tgt.sources[0])
         assert str(tgt.sources[0].sources[0]) == 'aaa.bar', \
@@ -1184,11 +1184,11 @@ class BuilderTestCase(unittest.TestCase):
                                                   '.4b':emit4b},
                                          target_factory=MyNode,
                                          source_factory=MyNode)
-        tgt = builder4(env, source='aaa.4a')[0]
+        tgt = builder4(env, None, source='aaa.4a')[0]
         assert str(tgt) == 'emit4a-aaa', str(tgt)
-        tgt = builder4(env, source='bbb.4b')[0]
+        tgt = builder4(env, None, source='bbb.4b')[0]
         assert str(tgt) == 'emit4b-bbb', str(tgt)
-        tgt = builder4(env, source='ccc.4c')[0]
+        tgt = builder4(env, None, source='ccc.4c')[0]
         assert str(tgt) == 'ccc', str(tgt)
 
         def emit4c(target, source, env):
@@ -1196,7 +1196,7 @@ class BuilderTestCase(unittest.TestCase):
             target = map(lambda x: 'emit4c-' + x[:-3], source)
             return (target, source)
         builder4.add_emitter('.4c', emit4c)
-        tgt = builder4(env, source='ccc.4c')[0]
+        tgt = builder4(env, None, source='ccc.4c')[0]
         assert str(tgt) == 'emit4c-ccc', str(tgt)
 
         # Test a list of emitter functions.
@@ -1241,43 +1241,43 @@ class BuilderTestCase(unittest.TestCase):
         env = Environment()
         b = SCons.Builder.Builder(action='foo', suffix='.o')
 
-        tgt = b(env, 'aaa')[0]
+        tgt = b(env, None, 'aaa')[0]
         assert str(tgt) == 'aaa.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'aaa', map(str, tgt.sources)
 
-        tgt = b(env, 'bbb.c')[0]
+        tgt = b(env, None, 'bbb.c')[0]
         assert str(tgt) == 'bbb.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'bbb.c', map(str, tgt.sources)
 
-        tgt = b(env, 'ccc.x.c')[0]
+        tgt = b(env, None, 'ccc.x.c')[0]
         assert str(tgt) == 'ccc.x.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'ccc.x.c', map(str, tgt.sources)
 
-        tgt = b(env, ['d0.c', 'd1.c'])[0]
+        tgt = b(env, None, ['d0.c', 'd1.c'])[0]
         assert str(tgt) == 'd0.o', str(tgt)
         assert len(tgt.sources) == 2,  map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'd0.c', map(str, tgt.sources)
         assert str(tgt.sources[1]) == 'd1.c', map(str, tgt.sources)
 
-        tgt = b(env, source='eee')[0]
+        tgt = b(env, target = None, source='eee')[0]
         assert str(tgt) == 'eee.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'eee', map(str, tgt.sources)
 
-        tgt = b(env, source='fff.c')[0]
+        tgt = b(env, target = None, source='fff.c')[0]
         assert str(tgt) == 'fff.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'fff.c', map(str, tgt.sources)
 
-        tgt = b(env, source='ggg.x.c')[0]
+        tgt = b(env, target = None, source='ggg.x.c')[0]
         assert str(tgt) == 'ggg.x.o', str(tgt)
         assert len(tgt.sources) == 1, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'ggg.x.c', map(str, tgt.sources)
 
-        tgt = b(env, source=['h0.c', 'h1.c'])[0]
+        tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0]
         assert str(tgt) == 'h0.o', str(tgt)
         assert len(tgt.sources) == 2,  map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'h0.c', map(str, tgt.sources)
@@ -1285,7 +1285,7 @@ class BuilderTestCase(unittest.TestCase):
 
         w = b(env, target='i0.w', source=['i0.x'])[0]
         y = b(env, target='i1.y', source=['i1.z'])[0]
-        tgt = b(env, source=[w, y])[0]
+        tgt = b(env, None, source=[w, y])[0]
         assert str(tgt) == 'i0.o', str(tgt)
         assert len(tgt.sources) == 2, map(str, tgt.sources)
         assert str(tgt.sources[0]) == 'i0.w', map(str, tgt.sources)
index caa3b85bb63a4cbeea5c49bd102d41082e498aeb..443c6b050eb87dd1b561ed972137eaed985a91b9 100644 (file)
@@ -149,8 +149,15 @@ class BuilderWrapper:
         self.env = env
         self.builder = builder
 
-    def __call__(self, *args, **kw):
-        return apply(self.builder, (self.env,) + args, kw)
+    def __call__(self, target=None, source=_null, *args, **kw):
+        if source is _null:
+            source = target
+            target = None
+        if not target is None and not SCons.Util.is_List(target):
+            target = [target]
+        if not source is None and not SCons.Util.is_List(source):
+            source = [source]
+        return apply(self.builder, (self.env, target, source) + args, kw)
 
     # This allows a Builder to be executed directly
     # through the Environment to which it's attached.
index 27e7e8bc2a9afd2bce03beaceb1e1d4d70fce3b1..0a0f260d2c0e70c7874ed3c214117cb59f43b0d1 100644 (file)
@@ -87,8 +87,10 @@ class Builder:
     def __init__(self, name = None):
         self.name = name
 
-    def __call__(self, env, **kw):
+    def __call__(self, env, target=None, source=None, **kw):
         global called_it
+        called_it['target'] = target
+        called_it['source'] = source
         called_it.update(kw)
 
     def execute(self, target = None, **kw):
@@ -481,21 +483,21 @@ class EnvironmentTestCase(unittest.TestCase):
         env.Replace(BUILDERS = { 'builder1' : b1,
                                  'builder2' : b2 })
         called_it = {}
-        env.builder1(target = 'out1')
-        assert called_it['target'] == 'out1', called_it
-        assert not called_it.has_key('source')
+        env.builder1('in1')
+        assert called_it['target'] == None, called_it
+        assert called_it['source'] == ['in1'], called_it
 
         called_it = {}
-        env.builder2(target = 'out2', xyzzy = 1)
-        assert called_it['target'] == 'out2', called_it
+        env.builder2(source = 'in2', xyzzy = 1)
+        assert called_it['target'] == None, called_it
+        assert called_it['source'] == ['in2'], called_it
         assert called_it['xyzzy'] == 1, called_it
-        assert not called_it.has_key('source')
 
         called_it = {}
         env.builder1(foo = 'bar')
         assert called_it['foo'] == 'bar', called_it
-        assert not called_it.has_key('target')
-        assert not called_it.has_key('source')
+        assert called_it['target'] == None, called_it
+        assert called_it['source'] == None, called_it
 
 
 
diff --git a/test/builder-wrappers.py b/test/builder-wrappers.py
new file mode 100644 (file)
index 0000000..dbde302
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to use a direct Python function to wrap
+calls to other Builder(s).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+import string
+def cat(target, source, env):
+    fp = open(str(target[0]), 'w')
+    for s in map(str, source):
+        fp.write(open(s).read())
+Cat = Builder(action=cat)
+def Wrapper(env, target, source):
+    if not target:
+        target = [string.replace(str(source[0]), '.in', '.wout')]
+    t1 = 't1-'+str(target[0])
+    source = 's-'+str(source[0])
+    env.Cat(t1, source)
+    t2 = 't2-'+str(target[0])
+    env.Cat(t2, source)
+env = Environment(BUILDERS = {'Cat' : Cat,
+                              'Wrapper' : Wrapper})
+env.Wrapper('f1.out', 'f1.in')
+env.Wrapper('f2.in')
+""")
+
+test.write('s-f1.in', "s-f1.in\n")
+test.write('s-f2.in', "s-f2.in\n")
+
+test.run()
+
+test.must_match('t1-f1.out', "s-f1.in\n")
+test.must_match('t1-f2.wout', "s-f2.in\n")
+test.must_match('t2-f1.out', "s-f1.in\n")
+test.must_match('t2-f2.wout', "s-f2.in\n")
+
+test.pass_test()