summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/man/scons.125
-rw-r--r--src/engine/SCons/Action.py58
-rw-r--r--src/engine/SCons/ActionTests.py36
-rw-r--r--src/engine/SCons/Builder.py7
-rw-r--r--src/engine/SCons/BuilderTests.py9
-rw-r--r--test/CommandGenerator.py70
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()