Add support for side effect targets. (Anthony Roach)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 4 Jul 2002 21:44:00 +0000 (21:44 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 4 Jul 2002 21:44:00 +0000 (21:44 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@402 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Node/__init__.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
test/SideEffect.py [new file with mode: 0644]

index 22d607255873a977cb17d8de0005e7a49985e25b..1e231989df977747a0acf12a0490af88c5290b2f 100644 (file)
@@ -85,6 +85,8 @@ class Node:
         self.includes = None
         self.build_args = {}
         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
 
     def generate_build_args(self):
         dict = copy.copy(self.env.Dictionary())
index c43f1ab309c76779a73c7d61030941dadc6890fc..3040447ada3675d1e4ee73b598e8976ec1c7e745 100644 (file)
@@ -129,11 +129,11 @@ class BuildTask(SCons.Taskmaster.Task):
 class CleanTask(SCons.Taskmaster.Task):
     """An SCons clean task."""
     def show(self):
-        if self.targets[0].builder:
+        if self.targets[0].builder or self.targets[0].side_effect:
             print "Removed " + self.targets[0].path
 
     def remove(self):
-        if self.targets[0].builder:
+        if self.targets[0].builder or self.targets[0].side_effect:
             try:
                 os.unlink(self.targets[0].path)
             except OSError:
index 3fe96dae45f7c8da0a9134534e4466cd96956ce7..3e4dbbc5a5ddd339cf0ccbec0de09c50d5c2d3e6 100644 (file)
@@ -39,7 +39,7 @@ import copy
 
 class Task:
     """Default SCons build engine task.
-    
+
     This controls the interaction of the actual building of node
     and the rest of the engine.
 
@@ -50,7 +50,7 @@ class Task:
     needs to customze something by sub-classing Taskmaster (or
     some other build engine class), we should first try to migrate
     that functionality into this class.
-    
+
     Note that it's generally a good idea for sub-classes to call
     these methods explicitly to update state, etc., rather than
     roll their own interaction with Taskmaster from scratch."""
@@ -73,11 +73,6 @@ class Task:
         """
         return self.node
 
-    def set_tstates(self, state):
-        """Set all of the target nodes's states."""
-        for t in self.targets:
-            t.set_state(state)
-
     def executed(self):
         """Called when the task has been successfully executed.
 
@@ -88,8 +83,10 @@ class Task:
         back on the pending list."""
 
         if self.targets[0].get_state() == SCons.Node.executing:
-            self.set_tstates(SCons.Node.executed)
             for t in self.targets:
+                for side_effect in t.side_effects:
+                    side_effect.set_state(None)
+                t.set_state(SCons.Node.executed)
                 t.built()
 
         self.tm.executed(self.node)
@@ -100,7 +97,8 @@ class Task:
 
     def fail_stop(self):
         """Explicit stop-the-build failure."""
-        self.set_tstates(SCons.Node.failed)
+        for t in self.targets:
+            t.set_state(SCons.Node.failed)
         self.tm.stop()
 
     def fail_continue(self):
@@ -116,7 +114,7 @@ class Task:
             n = walker.next()
             while n:
                 n = walker.next()
-        
+
         self.tm.executed(self.node)
 
     def make_ready(self):
@@ -126,7 +124,11 @@ class Task:
             bsig = self.tm.calc.bsig(t)
             if not self.tm.calc.current(t, bsig):
                 state = SCons.Node.executing
-        self.set_tstates(state)
+        for t in self.targets:
+            if state == SCons.Node.executing:
+                for side_effect in t.side_effects:
+                    side_effect.set_state(state)
+            t.set_state(state)
 
 class Calc:
     def bsig(self, node):
@@ -136,7 +138,7 @@ class Calc:
 
     def current(self, node, sig):
         """Default SCons build engine is-it-current function.
-    
+
         This returns "always out of date," so every node is always
         built/visited.
         """
@@ -146,7 +148,7 @@ class Taskmaster:
     """A generic Taskmaster for handling a bunch of targets.
 
     Classes that override methods of this class should call
-    the base class method, so this class can do its thing.    
+    the base class method, so this class can do its thing.
     """
 
     def __init__(self, targets=[], tasker=Task, calc=Calc()):
@@ -164,11 +166,11 @@ class Taskmaster:
 
         if self.ready:
             return
-        
+
         while self.candidates:
             node = self.candidates[-1]
             state = node.get_state()
-            
+
             # Skip nodes that have already been executed:
             if state != None and state != SCons.Node.stack:
                 self.candidates.pop()
@@ -191,13 +193,24 @@ class Taskmaster:
             # Add non-derived files that have not been built
             # to the candidates list:
             def derived(node):
-                return node.builder and node.get_state() == None
+                return (node.builder or node.side_effect) and node.get_state() == None
             derived = filter(derived, children)
             if derived:
                 derived.reverse()
                 self.candidates.extend(derived)
                 continue
 
+            # Skip nodes whose side-effects are currently being built:
+            cont = 0
+            for side_effect in node.side_effects:
+                if side_effect.get_state() == SCons.Node.executing:
+                    self.pending.append(node)
+                    node.set_state(SCons.Node.pending)
+                    self.candidates.pop()
+                    cont = 1
+                    break
+            if cont: continue
+
             # Skip nodes that are pending on a currently executing node:
             if node.depends_on(self.executing) or node.depends_on(self.pending):
                 self.pending.append(node)
@@ -211,25 +224,26 @@ class Taskmaster:
 
     def next_task(self):
         """Return the next task to be executed."""
-        
+
         self._find_next_ready_node()
 
         node = self.ready
-        
+
         if node is None:
             return None
-        
+
         self.executing.append(node)
+        self.executing.extend(node.side_effects)
         try:
             tlist = node.builder.targets(node)
         except AttributeError:
             tlist = [node]
-        task = self.tasker(self, tlist, node in self.targets, node) 
+        task = self.tasker(self, tlist, node in self.targets, node)
         task.make_ready()
         self.ready = None
-        
+
         return task
-            
+
     def is_blocked(self):
         self._find_next_ready_node()
 
@@ -243,7 +257,9 @@ class Taskmaster:
 
     def executed(self, node):
         self.executing.remove(node)
-        
+        for side_effect in node.side_effects:
+            self.executing.remove(side_effect)
+
         # move the current pending nodes to the candidates list:
         # (they may not all be ready to build, but _find_next_ready_node()
         #  will figure out which ones are really ready)
index 3a67aa2f1bf118b9ba49f7e262e1f2a61d17b174..a4a2f479c95af2173a30732f8255b426a963a37e 100644 (file)
@@ -46,6 +46,8 @@ class Node:
         self.csig = None
         self.state = None
         self.parents = []
+        self.side_effect = 0
+        self.side_effects = []
 
         for kid in kids:
             kid.parents.append(self)
@@ -292,7 +294,40 @@ class TaskmasterTestCase(unittest.TestCase):
         t.executed()
         assert tm.next_task() == None
         assert scan_called == 5, scan_called
-    
+
+        n1 = Node("n1")
+        n2 = Node("n2")
+        n3 = Node("n3")
+        n4 = Node("n4", [n1,n2,n3])
+        n5 = Node("n5", [n4])
+        n3.side_effect = 1
+        n1.side_effects = n2.side_effects = n3.side_effects = [n4]
+        tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
+        t = tm.next_task()
+        assert t.get_target() == n1
+        assert n4.state == SCons.Node.executing
+        assert tm.is_blocked()
+        t.executed()
+        assert not tm.is_blocked()
+        t = tm.next_task()
+        assert t.get_target() == n2
+        assert tm.is_blocked()
+        t.executed()
+        t = tm.next_task()
+        assert t.get_target() == n3
+        assert tm.is_blocked()
+        t.executed()
+        t = tm.next_task()
+        assert t.get_target() == n4
+        assert tm.is_blocked()
+        t.executed()
+        t = tm.next_task()
+        assert t.get_target() == n5
+        assert not tm.is_blocked()
+        assert not tm.next_task()
+        t.executed()
+
+
     def test_cycle_detection(self):
         n1 = Node("n1")
         n2 = Node("n2", [n1])
diff --git a/test/SideEffect.py b/test/SideEffect.py
new file mode 100644 (file)
index 0000000..7d37947
--- /dev/null
@@ -0,0 +1,121 @@
+#!/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 TestSCons
+import os.path
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', 
+"""
+def copy(source, target):
+    print 'copy() < %s > %s' % (source, target)
+    open(target, "wb").write(open(source, "rb").read())
+
+def build(env, source, target):
+    copy(str(source[0]), str(target[0]))
+    if target[0].side_effects:
+        side_effect = open(str(target[0].side_effects[0]), "ab")
+        side_effect.write('%s -> %s\\n'%(str(source[0]), str(target[0])))
+
+Build = Builder(action=build)
+env = Environment(BUILDERS={'Build':Build})
+env.Build('foo.out', 'foo.in')
+env.Build('bar.out', 'bar.in')
+env.Build('blat.out', 'blat.in')
+env.SideEffect('log.txt', ['foo.out', 'bar.out', 'blat.out'])
+env.Build('log.out', 'log.txt')
+""")
+
+test.write('foo.in', 'foo.in\n')
+test.write('bar.in', 'bar.in\n')
+test.write('blat.in', 'blat.in\n')
+
+test.run(arguments = 'foo.out bar.out', stdout="""\
+copy() < foo.in > foo.out
+copy() < bar.in > bar.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+"""
+assert test.read('log.txt') == expect
+
+test.write('bar.in', 'bar.in 2 \n')
+
+test.run(arguments = 'log.txt', stdout="""\
+copy() < bar.in > bar.out
+copy() < blat.in > blat.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+bar.in -> bar.out
+blat.in -> blat.out
+"""
+assert test.read('log.txt') == expect
+
+test.write('foo.in', 'foo.in 2 \n')
+
+test.run(arguments = ".", stdout="""\
+copy() < foo.in > foo.out
+copy() < log.txt > log.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+bar.in -> bar.out
+blat.in -> blat.out
+foo.in -> foo.out
+"""
+assert test.read('log.txt') == expect
+
+test.run(arguments = "-c .")
+
+test.fail_test(os.path.exists(test.workpath('foo.out')))
+test.fail_test(os.path.exists(test.workpath('bar.out')))
+test.fail_test(os.path.exists(test.workpath('blat.out')))
+test.fail_test(os.path.exists(test.workpath('log.txt')))
+
+test.run(arguments = "-j 4 .", stdout="""\
+copy() < bar.in > bar.out
+copy() < blat.in > blat.out
+copy() < foo.in > foo.out
+copy() < log.txt > log.out
+""")
+
+expect = """\
+bar.in -> bar.out
+blat.in -> blat.out
+foo.in -> foo.out
+"""
+assert test.read('log.txt') == expect
+
+
+test.pass_test()