of Python function actions, so that changing the location of an
otherwise unmodified Python function doesn't cause rebuilds.
+ - Fix AddPreAction() and AddPostAction() when an action has more than
+ one target file: attach the actions to the Executor, not the Node.
+
From Wayne Lee:
- Avoid "maximum recursion limit" errors when removing $(-$) pairs
def AddPreAction(self, files, action):
nodes = self.arg2nodes(files, self.fs.Entry)
action = SCons.Action.Action(action)
- for n in nodes:
- n.add_pre_action(action)
+ uniq = {}
+ for executor in map(lambda n: n.get_executor(), nodes):
+ uniq[executor] = 1
+ for executor in uniq.keys():
+ executor.add_pre_action(action)
return nodes
-
+
def AddPostAction(self, files, action):
nodes = self.arg2nodes(files, self.fs.Entry)
action = SCons.Action.Action(action)
- for n in nodes:
- n.add_post_action(action)
+ uniq = {}
+ for executor in map(lambda n: n.get_executor(), nodes):
+ uniq[executor] = 1
+ for executor in uniq.keys():
+ executor.add_post_action(action)
return nodes
def Alias(self, target, source=[], action=None, **kw):
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import string
from SCons.Debug import logInstanceCreation
import SCons.Util
def __init__(self, action, env=None, overridelist=[{}],
targets=[], sources=[], builder_kw={}):
if __debug__: logInstanceCreation(self, 'Executor.Executor')
- if not action:
- raise SCons.Errors.UserError, "Executor must have an action."
- self.action = action
+ self.set_action_list(action)
+ self.pre_actions = []
+ self.post_actions = []
self.env = env
self.overridelist = overridelist
self.targets = targets
self.sources = sources[:]
self.builder_kw = builder_kw
+ def set_action_list(self, action):
+ if not SCons.Util.is_List(action):
+ if not action:
+ raise SCons.Errors.UserError, "Executor must have an action."
+ action = [action]
+ self.action_list = action
+
+ def get_action_list(self):
+ return self.pre_actions + self.action_list + self.post_actions
+
def get_build_env(self):
"""Fetch or create the appropriate build Environment
for this Executor.
def do_execute(self, target, exitstatfunc, kw):
"""Actually execute the action list."""
- apply(self.action,
- (self.targets, self.sources, self.get_build_env(), exitstatfunc),
- self.get_kw(kw))
+ env = self.get_build_env()
+ kw = self.get_kw(kw)
+ for act in self.get_action_list():
+ apply(act,
+ (self.targets, self.sources, env, exitstatfunc),
+ kw)
# use extra indirection because with new-style objects (Python 2.2
# and above) we can't override special methods, and nullify() needs
slist = filter(lambda x, s=self.sources: x not in s, sources)
self.sources.extend(slist)
+ def add_pre_action(self, action):
+ self.pre_actions.append(action)
+
+ def add_post_action(self, action):
+ self.post_actions.append(action)
+
# another extra indirection for new-style objects and nullify...
def my_str(self):
- return self.action.genstring(self.targets,
- self.sources,
- self.get_build_env())
+ env = self.get_build_env()
+ get = lambda action, t=self.targets, s=self.sources, e=env: \
+ action.genstring(t, s, e)
+ return string.join(map(get, self.get_action_list()), "\n")
+
def __str__(self):
"__cacheable__"
or source Nodes there are.
__cacheable__
"""
- return self.action.get_contents(self.targets,
- self.sources,
- self.get_build_env())
+ env = self.get_build_env()
+ get = lambda action, t=self.targets, s=self.sources, e=env: \
+ action.get_contents(t, s, e)
+ return string.join(map(get, self.get_action_list()), "")
def get_timestamp(self):
"""Fetch a time stamp for this Executor. We don't have one, of
return map(func, self.get_unignored_sources(ignore))
+_Executor = Executor
-class Null:
+class Null(_Executor):
"""A null Executor, with a null build Environment, that does
nothing when the rest of the methods call it.
disassociate Builders from Nodes entirely, so we're not
going to worry about unit tests for this--at least for now.
"""
- def __init__(self):
+ def __init__(self, *args, **kw):
if __debug__: logInstanceCreation(self, 'Executor.Null')
+ kw['action'] = []
+ apply(_Executor.__init__, (self,), kw)
def get_build_env(self):
class NullEnvironment:
def get_scanner(self, key):
return NullEnvironment()
def get_build_scanner_path(self):
return None
- def __call__(self, *args, **kw):
- pass
def cleanup(self):
pass
- def get_missing_sources(self):
- return []
- def get_unignored_sources(self, ignore=[]):
- return []
- def process_sources(self, func, ignore=[]):
- return []
"""Test creating an Executor"""
source_list = ['s1', 's2']
x = SCons.Executor.Executor('a', 'e', ['o'], 't', source_list)
- assert x.action == 'a', x.builder
+ assert x.action_list == ['a'], x.action_list
assert x.env == 'e', x.env
assert x.overridelist == ['o'], x.overridelist
assert x.targets == 't', x.targets
else:
raise "Did not catch expected UserError"
+ def test__action_list(self):
+ """Test the {get,set}_action_list() methods"""
+ x = SCons.Executor.Executor('a', 'e', 'o', 't', ['s1', 's2'])
+
+ l = x.get_action_list()
+ assert l == ['a'], l
+
+ x.add_pre_action('pre')
+ x.add_post_action('post')
+ l = x.get_action_list()
+ assert l == ['pre', 'a', 'post'], l
+
+ x.set_action_list('b')
+ l = x.get_action_list()
+ assert l == ['pre', 'b', 'post'], l
+
+ x.set_action_list(['c'])
+ l = x.get_action_list()
+ assert l == ['pre', 'c', 'post'], l
+
def test_get_build_env(self):
"""Test fetching and generating a build environment"""
x = SCons.Executor.Executor(MyAction(), MyEnvironment(e=1), [],
env = MyEnvironment()
a = MyAction([action1, action2])
- b = MyBuilder(env, {})
- b.action = a
- n = MyNode('n', [pre], [post])
- n.builder = b
- n.build()
+ t = MyNode('t')
+
+ x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
+ x.add_pre_action(pre)
+ x.add_post_action(post)
+ x(t, lambda x: x)
assert result == ['pre', 'action1', 'action2', 'post'], result
del result[:]
errfunc(1)
return 1
- n = MyNode('n', [pre_err], [post])
- n.builder = b
- n.build()
+ x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
+ x.add_pre_action(pre_err)
+ x.add_post_action(post)
+ x(t, lambda x: x)
assert result == ['pre_err', 'action1', 'action2', 'post'], result
del result[:]
raise "errfunc %s" % stat
try:
- n.build(errfunc)
+ x(t, errfunc)
except:
assert sys.exc_type == "errfunc 1", sys.exc_type
else:
x.add_sources(['s3', 's1', 's4'])
assert x.sources == ['s1', 's2', 's3', 's4'], x.sources
+ def test_add_pre_action(self):
+ """Test adding pre-actions to an Executor"""
+ x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+ x.add_pre_action('a1')
+ assert x.pre_actions == ['a1']
+ x.add_pre_action('a2')
+ assert x.pre_actions == ['a1', 'a2']
+
+ def test_add_post_action(self):
+ """Test adding post-actions to an Executor"""
+ x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+ x.add_post_action('a1')
+ assert x.post_actions == ['a1']
+ x.add_post_action('a2')
+ assert x.post_actions == ['a1', 'a2']
+
def test___str__(self):
"""Test the __str__() method"""
env = MyEnvironment(S='string')
c = str(x)
assert c == 'GENSTRING action1 action2 t s', c
+ x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s'])
+ x.add_pre_action(MyAction(['pre']))
+ x.add_post_action(MyAction(['post']))
+ c = str(x)
+ expect = 'GENSTRING pre t s\n' + \
+ 'GENSTRING action1 action2 t s\n' + \
+ 'GENSTRING post t s'
+ assert c == expect, c
+
def test_nullify(self):
"""Test the nullify() method"""
env = MyEnvironment(S='string')
x = SCons.Executor.Executor(MyAction(actions=['grow']), env, [],
['t'], ['s'])
+ x.add_pre_action(MyAction(['pre']))
+ x.add_post_action(MyAction(['post']))
c = x.get_contents()
- assert c == 'grow t s', c
+ assert c == 'pre t sgrow t spost t s', c
def test_get_timestamp(self):
"""Test fetching the "timestamp" """
self.entries['.'] = self
self.entries['..'] = self.dir
self.cwd = self
- self.builder = get_MkdirBuilder()
self.searched = 0
self._sconsign = None
self.build_dirs = []
+ # Don't just reset the executor, replace its action list,
+ # because it might have some pre-or post-actions that need to
+ # be preserved.
+ self.builder = get_MkdirBuilder()
+ self.get_executor().set_action_list(self.builder.action)
+
def disambiguate(self):
return self
try:
dir_made = []
d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
+ d9.reset_executor()
f9.exists()
expect = os.path.join('build', 'var2', 'new_dir')
assert dir_made[0].path == expect, dir_made[0].path
assert not built_it
d1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
d1.builder_set(Builder(fs.File))
+ d1.reset_executor()
d1.env_set(Environment())
d1.build()
assert built_it
assert not built_it
f1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE
f1.builder_set(Builder(fs.File))
+ f1.reset_executor()
f1.env_set(Environment())
f1.build()
assert built_it
class DirTestCase(_tempdirTestCase):
+ def test__morph(self):
+ """Test handling of actions when morphing an Entry into a Dir"""
+ test = self.test
+ e = self.fs.Entry('eee')
+ x = e.get_executor()
+ x.add_pre_action('pre')
+ x.add_post_action('post')
+ e.must_be_a_Dir()
+ a = x.get_action_list()
+ assert a[0] == 'pre', a
+ assert a[2] == 'post', a
+
def test_entry_exists_on_disk(self):
"""Test the Dir.entry_exists_on_disk() method
"""
dir_made = []
new_dir = fs.Dir("new_dir")
new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
+ new_dir.reset_executor()
xyz = fs.File(os.path.join("new_dir", "xyz"))
xyz.set_state(SCons.Node.up_to_date)
def __call__(self, target, source, env, errfunc):
for A in self.list:
A(target, source, env, errfunc)
-
-class MyNonGlobalAction(MyActionBase):
- def __init__(self):
- self.order = 0
- self.built_it = None
- self.built_target = None
- self.built_source = None
-
- def __call__(self, target, source, env, errfunc):
- # 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 Environment:
def __init__(self, **kw):
assert built_args["on"] == 3, built_args
assert built_args["off"] == 4, built_args
- 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_get_build_scanner_path(self):
"""Test the get_build_scanner_path() method"""
n = SCons.Node.Node()
try:
act = self.builder.action
except AttributeError:
- executor = SCons.Executor.Null()
+ executor = SCons.Executor.Null(targets=[self])
else:
- if self.pre_actions:
- act = self.pre_actions + act
- if self.post_actions:
- act = act + self.post_actions
executor = SCons.Executor.Executor(act,
self.env or self.builder.env,
[self.builder.overrides],
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)
- # executor must be recomputed to include new pre-actions
- self.reset_executor()
-
- def add_post_action(self, act):
- """Adds and Action performed on this Node only after
- building it."""
- self.post_actions.append(act)
- # executor must be recomputed to include new pre-actions
- self.reset_executor()
-
def render_include_tree(self):
"""
Return a text representation, suitable for displaying to the
import TestSCons
_exe = TestSCons._exe
+python = TestSCons.python
test = TestSCons.TestSCons()
-test.subdir('work1', 'work2', 'work3')
+test.subdir('work1', 'work2', 'work3', 'work4')
+test.write(['work4', 'build.py'], """\
+import sys
+outfp = open(sys.argv[1], 'wb')
+for f in sys.argv[2:]:
+ outfp.write(open(f, 'rb').read())
+outfp.close()
+""")
+
+test.write(['work4', 'SConstruct'], """\
+def pre_action(target, source, env):
+ open(str(target[0]), 'ab').write('pre %%s\\n' %% source[0])
+def post_action(target, source, env):
+ open(str(target[0]), 'ab').write('post %%s\\n' %% source[0])
+env = Environment()
+o = env.Command(['pre-post', 'file.out'],
+ 'file.in',
+ "%(python)s build.py ${TARGETS[1]} $SOURCE")
+env.AddPreAction(o, pre_action)
+env.AddPostAction(o, post_action)
+""" % locals())
+
+test.write(['work4', 'file.in'], "file.in\n")
+
+test.run(chdir='work4', arguments='.')
+
+test.must_match(['work4', 'file.out'], "file.in\n")
+test.must_match(['work4', 'pre-post'], "pre file.in\npost file.in\n")
+
+test.pass_test()
+
+
+
test.pass_test()