diff options
-rw-r--r-- | doc/man/scons.1 | 25 | ||||
-rw-r--r-- | src/engine/SCons/Action.py | 58 | ||||
-rw-r--r-- | src/engine/SCons/ActionTests.py | 36 | ||||
-rw-r--r-- | src/engine/SCons/Builder.py | 7 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 9 | ||||
-rw-r--r-- | test/CommandGenerator.py | 70 |
6 files changed, 204 insertions, 1 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 4d1ea77..78bf54b 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -1170,6 +1170,31 @@ Specifies a builder to use when a source file name suffix does not match any of the suffixes of the builder. Using this argument produces a multi-stage builder. +.IP generator +A function that returns a list of command lines that will be executed to build +the target(s) from the source(s). The command lines must be returned as +lists, where the first element of the list is the executable, and the other +elements in the list are the arguments that will be passed to the +executable. The +.I generator +and +.I action +arguments must not both be used for the same Builder. The generator function +should take 3 arguments: +.I source +- a list of source nodes, +.I target +- a list of target nodes, +.I env +- the construction environment. Example: + +.ES +def g(env, source, target): + return [["gcc", "-c", "-o"] + target + source] + +b = Builder(name="Object", generator=g) +.EE + .SS Action Objects The Builder function will turn its diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 19ec05c..7bee082 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -163,10 +163,21 @@ def SetCommandHandler(func): global spawn spawn = func +class CommandGenerator: + """ + Wrappes a command generator function so the Action() factory + function can tell a generator function from a function action. + """ + def __init__(self, generator): + self.generator = generator + + def Action(act): """A factory for action objects.""" if isinstance(act, ActionBase): return act + elif isinstance(act, CommandGenerator): + return CommandGeneratorAction(act.generator) elif callable(act): return FunctionAction(act) elif SCons.Util.is_String(act): @@ -175,7 +186,7 @@ def Action(act): return ListAction(act) else: return None - + class ActionBase: """Base class for actions that create output objects.""" def __cmp__(self, other): @@ -301,6 +312,51 @@ class CommandAction(ActionBase): """ return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove) +class CommandGeneratorAction(ActionBase): + """Class for command-generator actions.""" + def __init__(self, generator): + self.generator = generator + + def execute(self, **kw): + # ensure that target is a list, to make it easier to write + # generator functions: + import SCons.Util + if kw.has_key("target") and not SCons.Util.is_List(kw["target"]): + kw["target"] = [kw["target"]] + + cmd_list = apply(self.generator, (), kw) + + cmd_list = map(lambda x: map(str, x), cmd_list) + for cmd_line in cmd_list: + if print_actions: + self.show(cmd_line) + if execute_actions: + try: + ENV = kw['env']['ENV'] + except: + import SCons.Defaults + ENV = SCons.Defaults.ConstructionEnvironment['ENV'] + ret = spawn(cmd_line[0], cmd_line, ENV) + if ret: + return ret + + return 0 + + def get_contents(self, **kw): + """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. + """ + kw['source'] = ["__s1__", "__s2__"] + kw['target'] = ["__t1__", "__t2__"] + cmd_list = apply(self.generator, (), kw) + cmd_list = map(lambda x: map(str, x), cmd_list) + cmd_list = map(lambda x: string.join(x, "\0"), cmd_list) + cmd_list = map(lambda x: _remove.sub('', x), cmd_list) + cmd_list = map(lambda x: filter(lambda y: y, string.split(x, "\0")), cmd_list) + return cmd_list + class FunctionAction(ActionBase): """Class for Python function actions.""" def __init__(self, function): diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index f872a1d..461da6e 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -142,6 +142,41 @@ class CommandActionTestCase(unittest.TestCase): c = a.get_contents(foo = 'FFF', bar = 'BBB') assert c == "| FFF BBB |" +class CommandGeneratorActionTestCase(unittest.TestCase): + + def test_init(self): + """Test creation of a command generator Action + """ + def f(target, source, env): + pass + a = SCons.Action.CommandGeneratorAction(f) + assert a.generator == f + + def test_execute(self): + """Test executing a command generator Action + """ + + def f(dummy, self=self): + self.dummy = dummy + return [[""]] + + a = SCons.Action.CommandGeneratorAction(f) + self.dummy = 0 + a.execute(dummy=1) + assert self.dummy == 1 + del self.dummy + + def test_get_contents(self): + """Test fetching the contents of a command generator Action + """ + def f(target, source, foo, bar): + return [["guux", foo, "$(", "ignore", "$)", bar]] + + a = SCons.Action.CommandGeneratorAction(f) + c = a.get_contents(foo = 'FFF', bar = 'BBB') + assert c == [["guux", 'FFF', 'BBB']], c + + class FunctionActionTestCase(unittest.TestCase): def test_init(self): @@ -209,6 +244,7 @@ if __name__ == "__main__": suite.addTest(ActionBaseTestCase("test_cmp")) suite.addTest(ActionBaseTestCase("test_subst_dict")) for tclass in [CommandActionTestCase, + CommandGeneratorActionTestCase, FunctionActionTestCase, ListActionTestCase]: for func in ["test_init", "test_execute", "test_get_contents"]: diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 347a652..0b8fc49 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -43,6 +43,13 @@ import SCons.Util def Builder(**kw): """A factory for builder objects.""" + + if kw.has_key('generator'): + if kw.has_key('action'): + raise UserError, "You must not specify both an action and a generator." + kw['action'] = SCons.Action.CommandGenerator(kw['generator']) + del kw['generator'] + if kw.has_key('action') and SCons.Util.is_Dict(kw['action']): return apply(CompositeBuilder, (), kw) elif kw.has_key('src_builder'): diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index a1223d7..68810b4 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -155,6 +155,15 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(name="builder", action="foo") assert builder.action.command == "foo" + def test_generator(self): + """Test Builder creation given a generator function.""" + + def generator(): + pass + + builder = SCons.Builder.Builder(name="builder", generator=generator) + assert builder.action.generator == generator + def test_cmp(self): """Test simple comparisons of Builder objects """ diff --git a/test/CommandGenerator.py b/test/CommandGenerator.py new file mode 100644 index 0000000..c1413d6 --- /dev/null +++ b/test/CommandGenerator.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002 Steven Knight +# +# 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__" + +import os.path +import sys +import TestSCons + +python = sys.executable + +test = TestSCons.TestSCons() + +test.write('build.py', r""" +import sys +contents = open(sys.argv[2], 'rb').read() +file = open(sys.argv[1], 'wb') +file.write(contents) +file.close() +sys.exit(0) +""") + +test.write('SConstruct', """ +def g(source, target, env): + import sys + python = sys.executable + return [[python, "build.py", ".temp"] + source, + [python, "build.py"] + target + [".temp"]] + +b = Builder(name = 'b', generator=g) +env = Environment(BUILDERS = [b]) +env.b(target = 'foo1.out', source = 'foo1.in') +env.b(target = 'foo2.out', source = 'foo2.in') +env.b(target = 'foo3.out', source = 'foo3.in') +""") + +test.write('foo1.in', "foo1.in\n") + +test.write('foo2.in', "foo2.in\n") + +test.write('foo3.in', "foo3.in\n") + +test.run(arguments = 'foo1.out foo2.out foo3.out') + +test.fail_test(test.read(test.workpath('foo1.out')) != "foo1.in\n") +test.fail_test(test.read(test.workpath('foo2.out')) != "foo2.in\n") +test.fail_test(test.read(test.workpath('foo3.out')) != "foo3.in\n") + +test.pass_test() |