summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-01-20 06:14:54 (GMT)
committerSteven Knight <knight@baldmt.com>2003-01-20 06:14:54 (GMT)
commit624493645fb835655b25e92cf5e48f3990829789 (patch)
treecfd91caee8189c36e039d0bb1182869b72b69e77
parenta5f3e5e1a386486767bca62126835486e7f722fe (diff)
downloadSCons-624493645fb835655b25e92cf5e48f3990829789.zip
SCons-624493645fb835655b25e92cf5e48f3990829789.tar.gz
SCons-624493645fb835655b25e92cf5e48f3990829789.tar.bz2
Add AddPreAction() and AddPostAction() methods. (Charles Crain)
-rw-r--r--doc/man/scons.128
-rw-r--r--src/CHANGES.txt5
-rw-r--r--src/engine/SCons/Action.py31
-rw-r--r--src/engine/SCons/ActionTests.py77
-rw-r--r--src/engine/SCons/Node/NodeTests.py60
-rw-r--r--src/engine/SCons/Node/__init__.py15
-rw-r--r--src/engine/SCons/Script/SConscript.py13
-rw-r--r--test/append-action.py84
-rw-r--r--test/pre-post-actions.py83
9 files changed, 392 insertions, 4 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index 7ad2857..00db84e 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -31,7 +31,7 @@
.RE
.fi
..
-.TH SCONS 1 "November 2002"
+.TH SCONS 1 "February 2003"
.SH NAME
scons \- a software construction tool
.SH SYNOPSIS
@@ -2324,6 +2324,32 @@ is usually safe, and is always more efficient than
.IR duplicate =1.
.TP
+.RI AddPostAction ( target, action )
+Arranges for the specified
+.I action
+to be performed
+after the specified
+.I target
+has been built.
+The specified action(s) may be
+an Action object, or anything that
+can be converted into an Action object
+(see below).
+
+.TP
+.RI AddPreAction ( target, action )
+Arranges for the specified
+.I action
+to be performed
+before the specified
+.I target
+is built.
+The specified action(s) may be
+an Action object, or anything that
+can be converted into an Action object
+(see below).
+
+.TP
.RI Clean ( target, files_or_dirs )
This specifies a list of files or directories which should be removed
whenever the target is specified with the
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 50f58d3..be0a4c9 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -10,6 +10,11 @@
RELEASE 0.11 - XXX
+ From Charles Crain:
+
+ - Added new AddPreAction() and AddPostAction() functions that support
+ taking additional actions before or after building specific targets.
+
From Steven Knight:
- Allow Python function Actions to specify a list of construction
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index f185641..646ee8d 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -64,6 +64,25 @@ def SetCommandHandler(func, escape = lambda x: x):
def GetCommandHandler():
raise SCons.Errors.UserError("GetCommandHandler() is no longer supported, use the SPAWN construction variable.")
+def _actionAppend(act1, act2):
+ # This function knows how to slap two actions together.
+ # Mainly, it handles ListActions by concatenating into
+ # a single ListAction.
+ a1 = Action(act1)
+ a2 = Action(act2)
+ if a1 is None or a2 is None:
+ raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
+ if isinstance(a1, ListAction):
+ if isinstance(a2, ListAction):
+ return ListAction(a1.list + a2.list)
+ else:
+ return ListAction(a1.list + [ a2 ])
+ else:
+ if isinstance(a2, ListAction):
+ return ListAction([ a1 ] + a2.list)
+ else:
+ return ListAction([ a1, a2 ])
+
class CommandGenerator:
"""
Wraps a command generator function so the Action() factory
@@ -72,6 +91,12 @@ class CommandGenerator:
def __init__(self, generator):
self.generator = generator
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
def _do_create_action(act, strfunction=_null, varlist=[]):
"""This is the actual "implementation" for the
Action factory method, below. This handles the
@@ -178,6 +203,12 @@ class ActionBase:
return dict
+ def __add__(self, other):
+ return _actionAppend(self, other)
+
+ def __radd__(self, other):
+ return _actionAppend(other, self)
+
def _string_from_cmd_list(cmd_list):
"""Takes a list of command line arguments and returns a pretty
representation for printing."""
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index ba6c91d..0f96899 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -234,6 +234,83 @@ class ActionBaseTestCase(unittest.TestCase):
SOURCES.sort()
assert SOURCES == ['rstr-s4', 's3'], d['SOURCES']
+ def test_add(self):
+ """Test adding Actions to stuff."""
+ # Adding actions to other Actions or to stuff that can
+ # be converted into an Action should produce a ListAction
+ # containing all the Actions.
+ def bar():
+ return None
+ baz = SCons.Action.CommandGenerator(bar)
+ act1 = SCons.Action.Action('foo bar')
+ act2 = SCons.Action.Action([ 'foo', bar ])
+
+ sum = act1 + act2
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 3, len(sum.list)
+ assert map(lambda x: isinstance(x, SCons.Action.ActionBase),
+ sum.list) == [ 1, 1, 1 ]
+
+ sum = act1 + act1
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 2, len(sum.list)
+
+ sum = act2 + act2
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 4, len(sum.list)
+
+ # Should also be able to add command generators to each other
+ # or to actions
+ sum = baz + baz
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 2, len(sum.list)
+
+ sum = baz + act1
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 2, len(sum.list)
+
+ sum = act2 + baz
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 3, len(sum.list)
+
+ # Also should be able to add Actions to anything that can
+ # be converted into an action.
+ sum = act1 + bar
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 2, len(sum.list)
+ assert isinstance(sum.list[1], SCons.Action.FunctionAction)
+
+ sum = 'foo bar' + act2
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 3, len(sum.list)
+ assert isinstance(sum.list[0], SCons.Action.CommandAction)
+
+ sum = [ 'foo', 'bar' ] + act1
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 3, sum.list
+ assert isinstance(sum.list[0], SCons.Action.CommandAction)
+ assert isinstance(sum.list[1], SCons.Action.CommandAction)
+
+ sum = act2 + [ baz, bar ]
+ assert isinstance(sum, SCons.Action.ListAction), str(sum)
+ assert len(sum.list) == 4, len(sum.list)
+ assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction)
+ assert isinstance(sum.list[3], SCons.Action.FunctionAction)
+
+ try:
+ sum = act2 + 1
+ except TypeError:
+ pass
+ else:
+ assert 0, "Should have thrown a TypeError adding to an int."
+
+ try:
+ sum = 1 + act2
+ except TypeError:
+ pass
+ else:
+ assert 0, "Should have thrown a TypeError adding to an int."
+
class CommandActionTestCase(unittest.TestCase):
def test_init(self):
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index 2374026..3b66f43 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -37,14 +37,38 @@ built_it = None
built_target = None
built_source = None
cycle_detected = None
+built_order = 0
class MyAction:
+ def __init__(self):
+ self.order = 0
+
def __call__(self, target, source, env):
- global built_it, built_target, built_source, built_args
+ global built_it, built_target, built_source, built_args, built_order
built_it = 1
built_target = target
built_source = source
built_args = env
+ built_order = built_order + 1
+ self.order = built_order
+ return 0
+
+class MyNonGlobalAction:
+ def __init__(self):
+ self.order = 0
+ self.built_it = None
+ self.built_target = None
+ self.built_source = None
+
+ def __call__(self, target, source, env):
+ # Okay, so not ENTIRELY non-global...
+ global built_order
+ self.built_it = 1
+ self.built_target = target
+ self.built_source = source
+ self.built_args = env
+ built_order = built_order + 1
+ self.order = built_order
return 0
class Builder:
@@ -116,7 +140,7 @@ class NodeTestCase(unittest.TestCase):
def test_build(self):
"""Test building a node
"""
- global built_it
+ global built_it, built_order
# Make sure it doesn't blow up if no builder is set.
node = MyNode("www")
@@ -159,7 +183,39 @@ class NodeTestCase(unittest.TestCase):
ggg.path = "ggg"
fff.sources = ["hhh", "iii"]
ggg.sources = ["hhh", "iii"]
+ # [Charles C. 1/7/2002] Uhhh, why are there no asserts here?
+ built_it = None
+ built_order = 0
+ node = MyNode("xxx")
+ node.builder_set(Builder())
+ node.env_set(Environment())
+ node.sources = ["yyy", "zzz"]
+ pre1 = MyNonGlobalAction()
+ pre2 = MyNonGlobalAction()
+ post1 = MyNonGlobalAction()
+ post2 = MyNonGlobalAction()
+ node.add_pre_action(pre1)
+ node.add_pre_action(pre2)
+ node.add_post_action(post1)
+ node.add_post_action(post2)
+ node.build()
+ assert built_it
+ assert pre1.built_it
+ assert pre2.built_it
+ assert post1.built_it
+ assert post2.built_it
+ assert pre1.order == 1, pre1.order
+ assert pre2.order == 2, pre1.order
+ # The action of the builder itself is order 3...
+ assert post1.order == 4, pre1.order
+ assert post2.order == 5, pre1.order
+
+ for act in [ pre1, pre2, post1, post2 ]:
+ assert type(act.built_target[0]) == type(MyNode("bar")), type(act.built_target[0])
+ assert str(act.built_target[0]) == "xxx", str(act.built_target[0])
+ assert act.built_source == ["yyy", "zzz"], act.built_source
+
def test_depends_on(self):
"""Test the depends_on() method
"""
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 618a8fb..9641ea7 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -102,6 +102,8 @@ class Node:
self.attributes = self.Attrs() # Generic place to stick information about the Node.
self.side_effect = 0 # true iff this node is a side effect
self.side_effects = [] # the side effects of building this target
+ self.pre_actions = []
+ self.post_actions = []
def generate_build_env(self):
return self.env.Override(self.overrides)
@@ -115,7 +117,8 @@ class Node:
"""
if not self.has_builder():
return None
- action_list = self.builder.get_actions()
+ action_list = self.pre_actions + self.builder.get_actions() + \
+ self.post_actions
if not action_list:
return
targets = self.builder.targets(self)
@@ -470,6 +473,16 @@ class Node:
the command interpreter literally."""
return 1
+ def add_pre_action(self, act):
+ """Adds an Action performed on this Node only before
+ building it."""
+ self.pre_actions.append(act)
+
+ def add_post_action(self, act):
+ """Adds and Action performed on this Node only after
+ building it."""
+ self.post_actions.append(act)
+
def render_include_tree(self):
"""
Return a text representation, suitable for displaying to the
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index d6bba7d..c5cccb6 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -30,6 +30,7 @@ files.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Environment
@@ -336,6 +337,16 @@ def Clean(target, files):
else:
clean_targets[s] = nodes
+def AddPreAction(files, action):
+ nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
+ for n in nodes:
+ n.add_pre_action(SCons.Action.Action(action))
+
+def AddPostAction(files, action):
+ nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
+ for n in nodes:
+ n.add_post_action(SCons.Action.Action(action))
+
def BuildDefaultGlobals():
"""
Create a dictionary containing all the default globals for
@@ -344,6 +355,8 @@ def BuildDefaultGlobals():
globals = {}
globals['Action'] = SCons.Action.Action
+ globals['AddPostAction'] = AddPostAction
+ globals['AddPreAction'] = AddPreAction
globals['ARGUMENTS'] = arguments
globals['BuildDir'] = BuildDir
globals['Builder'] = SCons.Builder.Builder
diff --git a/test/append-action.py b/test/append-action.py
new file mode 100644
index 0000000..c8de281
--- /dev/null
+++ b/test/append-action.py
@@ -0,0 +1,84 @@
+#!/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.
+#
+# This test exercises the addition operator of Action objects.
+# Using Environment.Prepend() and Environment.Append(), you should be
+# able to add new actions to existing ones, effectively adding steps
+# to a build process.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import stat
+import sys
+import TestSCons
+
+if sys.platform == 'win32':
+ _exe = '.exe'
+else:
+ _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write('foo.c', r"""
+#include <stdio.h>
+
+int main(void)
+{
+ printf("Foo\n");
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os.path
+
+env=Environment()
+
+def before(env, target, source):
+ f=open(str(target[0]), "wb")
+ f.write("Foo\\n")
+ f.close()
+ f=open("before.txt", "wb")
+ f.write("Bar\\n")
+ f.close()
+
+def after(env, target, source):
+ fin = open(str(target[0]), "rb")
+ fout = open("after%s", "wb")
+ fout.write(fin.read())
+ fout.close()
+ fin.close()
+
+env.Prepend(LINKCOM=Action(before))
+env.Append(LINKCOM=Action(after))
+env.Program(source='foo.c', target='foo')
+""" % _exe)
+
+after_exe = test.workpath('after' + _exe)
+
+test.run(arguments='.')
+test.fail_test(open('before.txt', 'rb').read() != "Bar\n")
+os.chmod(after_exe, os.stat(after_exe)[stat.ST_MODE] | stat.S_IXUSR)
+test.run(program=after_exe, stdout="Foo\n")
+test.pass_test()
diff --git a/test/pre-post-actions.py b/test/pre-post-actions.py
new file mode 100644
index 0000000..d3139bb
--- /dev/null
+++ b/test/pre-post-actions.py
@@ -0,0 +1,83 @@
+#!/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.
+#
+# This test exercises the AddPreAction() and AddPostAction() API
+# functions, which add pre-build and post-build actions to nodes.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import stat
+import sys
+import TestSCons
+
+if sys.platform == 'win32':
+ _exe = '.exe'
+else:
+ _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write('foo.c', r"""
+#include <stdio.h>
+
+int main(void)
+{
+ printf("Foo\n");
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os.path
+
+env=Environment()
+
+def before(env, target, source):
+ f=open(str(target[0]), "wb")
+ f.write("Foo\\n")
+ f.close()
+ f=open("before.txt", "wb")
+ f.write("Bar\\n")
+ f.close()
+
+def after(env, target, source):
+ fin = open(str(target[0]), "rb")
+ fout = open("after%s", "wb")
+ fout.write(fin.read())
+ fout.close()
+ fin.close()
+
+foo = env.Program(source='foo.c', target='foo')
+AddPreAction(foo, before)
+AddPostAction('foo%s', after)
+""" % (_exe, _exe))
+
+after_exe = test.workpath('after' + _exe)
+
+test.run(arguments='.')
+test.fail_test(open('before.txt', 'rb').read() != "Bar\n")
+os.chmod(after_exe, os.stat(after_exe)[stat.ST_MODE] | stat.S_IXUSR)
+test.run(program=test.workpath(after_exe), stdout="Foo\n")
+test.pass_test()