From f78155c91f6da2f0db2433cbcb3ce4d2c9d50c9d Mon Sep 17 00:00:00 2001 From: stevenknight Date: Tue, 9 Nov 2004 16:08:23 +0000 Subject: [PATCH] Add explicit support for Builder wrapper functions (pseudo-Builders) in the BUILDERS dictionary. git-svn-id: http://scons.tigris.org/svn/scons/trunk@1150 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- src/CHANGES.txt | 4 ++ src/engine/SCons/Builder.py | 36 ++++++-------- src/engine/SCons/BuilderTests.py | 72 ++++++++++++++-------------- src/engine/SCons/Environment.py | 11 ++++- src/engine/SCons/EnvironmentTests.py | 20 ++++---- test/builder-wrappers.py | 68 ++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 69 deletions(-) create mode 100644 test/builder-wrappers.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index d9a397f3..5f6968b3 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index d6b7597b..40361d47 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -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. diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 2075bf0e..98e5a58d 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -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) diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index caa3b85b..443c6b05 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -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. diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 27e7e8bc..0a0f260d 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -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 index 00000000..dbde3024 --- /dev/null +++ b/test/builder-wrappers.py @@ -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() -- 2.26.2