diff options
author | Steven Knight <knight@baldmt.com> | 2003-01-20 06:14:54 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2003-01-20 06:14:54 (GMT) |
commit | 624493645fb835655b25e92cf5e48f3990829789 (patch) | |
tree | cfd91caee8189c36e039d0bb1182869b72b69e77 | |
parent | a5f3e5e1a386486767bca62126835486e7f722fe (diff) | |
download | SCons-624493645fb835655b25e92cf5e48f3990829789.zip SCons-624493645fb835655b25e92cf5e48f3990829789.tar.gz SCons-624493645fb835655b25e92cf5e48f3990829789.tar.bz2 |
Add AddPreAction() and AddPostAction() methods. (Charles Crain)
-rw-r--r-- | doc/man/scons.1 | 28 | ||||
-rw-r--r-- | src/CHANGES.txt | 5 | ||||
-rw-r--r-- | src/engine/SCons/Action.py | 31 | ||||
-rw-r--r-- | src/engine/SCons/ActionTests.py | 77 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 60 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 15 | ||||
-rw-r--r-- | src/engine/SCons/Script/SConscript.py | 13 | ||||
-rw-r--r-- | test/append-action.py | 84 | ||||
-rw-r--r-- | test/pre-post-actions.py | 83 |
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() |