1. SCons
  2. Core
  3. SCons

Commits

Steven Knight  committed 09c5f18

Add -k support and more

  • Participants
  • Parent commits c6d2fd3
  • Branches default

Comments (0)

Files changed (10)

File src/engine/SCons/Job.py

View file
                     self.taskmaster.failed(task)
                 else:
                     self.taskmaster.executed(task)
-                    
-                    if not self.taskmaster.is_blocked():
-                        cv.notifyAll()
+
+                # signal the cv whether the task failed or not,
+                # or otherwise the other Jobs might
+                # remain blocked:
+                if not self.taskmaster.is_blocked():
+                    cv.notifyAll()
                     
         finally:
             cv.release()

File src/engine/SCons/Node/FSTests.py

View file
 
         built_it = None
         assert not built_it
-        d1.add_source(["d"])    # XXX FAKE SUBCLASS ATTRIBUTE
+        d1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
         d1.builder_set(Builder())
         d1.env_set(Environment())
         d1.build()
         assert built_it
 
+        assert d1.get_parents() == [] 
+
         built_it = None
         assert not built_it
-        f1.add_source(["f"])    # XXX FAKE SUBCLASS ATTRIBUTE
+        f1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
         f1.builder_set(Builder())
         f1.env_set(Environment())
         f1.build()

File src/engine/SCons/Node/NodeTests.py

View file
 	"""
 	node = SCons.Node.Node()
 	assert node.depends == []
-	try:
-	    node.add_dependency('zero')
+
+        zero = SCons.Node.Node()
+        try:
+	    node.add_dependency(zero)
 	except TypeError:
 	    pass
-	node.add_dependency(['one'])
-	assert node.depends == ['one']
-	node.add_dependency(['two', 'three'])
-	assert node.depends == ['one', 'two', 'three']
-	node.add_dependency(['three', 'four', 'one'])
-	assert node.depends == ['one', 'two', 'three', 'four']
+        else:
+            assert 0
+
+        one = SCons.Node.Node()
+        two = SCons.Node.Node()
+        three = SCons.Node.Node()
+        four = SCons.Node.Node()
+
+        node.add_dependency([one])
+        assert node.depends == [one]
+        node.add_dependency([two, three])
+        assert node.depends == [one, two, three]
+        node.add_dependency([three, four, one])
+        assert node.depends == [one, two, three, four]
+
+        assert zero.get_parents() == []
+        assert one.get_parents() == [node]
+        assert two.get_parents() == [node]
+        assert three.get_parents() == [node]
+        assert four.get_parents() == [node]
+
 
     def test_add_source(self):
 	"""Test adding sources to a Node's list.
 	"""
 	node = SCons.Node.Node()
 	assert node.sources == []
+
+        zero = SCons.Node.Node()
 	try:
-	    node.add_source('zero')
+	    node.add_source(zero)
 	except TypeError:
 	    pass
-	node.add_source(['one'])
-	assert node.sources == ['one']
-	node.add_source(['two', 'three'])
-	assert node.sources == ['one', 'two', 'three']
-	node.add_source(['three', 'four', 'one'])
-	assert node.sources == ['one', 'two', 'three', 'four']
+        else:
+            assert 0
+
+        one = SCons.Node.Node()
+        two = SCons.Node.Node()
+        three = SCons.Node.Node()
+        four = SCons.Node.Node()
+
+        node.add_source([one])
+        assert node.sources == [one]
+        node.add_source([two, three])
+        assert node.sources == [one, two, three]
+        node.add_source([three, four, one])
+        assert node.sources == [one, two, three, four]
+
+        assert zero.get_parents() == []
+        assert one.get_parents() == [node]
+        assert two.get_parents() == [node]
+        assert three.get_parents() == [node]
+        assert four.get_parents() == [node]
 
     def test_children(self):
 	"""Test fetching the "children" of a Node.
 	"""
 	node = SCons.Node.Node()
-	node.add_source(['one', 'two', 'three'])
-	node.add_dependency(['four', 'five', 'six'])
-	kids = node.children()
-	kids.sort()
-	assert kids == ['five', 'four', 'one', 'six', 'three', 'two']
+        one = SCons.Node.Node()
+        two = SCons.Node.Node()
+        three = SCons.Node.Node()
+        four = SCons.Node.Node()
+        five = SCons.Node.Node()
+        six = SCons.Node.Node()
+
+        node.add_source([one, two, three])
+        node.add_dependency([four, five, six])
+        kids = node.children()
+        assert len(kids) == 6
+        assert one in kids
+        assert two in kids
+        assert three in kids
+        assert four in kids
+        assert five in kids
+        assert six in kids
+
+    def test_add_parent(self):
+        """Test adding parents to a Node."""
+        node = SCons.Node.Node()
+        parent = SCons.Node.Node()
+        node._add_parent(parent)
+        assert node.get_parents() == [parent]
+        node._add_parent(parent)
+        assert node.get_parents() == [parent]
 
     def test_state(self):
 	"""Test setting and getting the state of a node
 	assert nw.next().name ==  "n1"
 	assert nw.next() == None
 
+    def test_children_are_executed(self):
+        n1 = SCons.Node.Node()
+        n2 = SCons.Node.Node()
+        n3 = SCons.Node.Node()
+        n4 = SCons.Node.Node()
+
+        n4.add_source([n3])
+        n3.add_source([n1, n2])
+
+        assert not n4.children_are_executed()
+        assert not n3.children_are_executed()
+        assert n2.children_are_executed()
+        assert n1.children_are_executed()
+
+        n1.set_state(SCons.Node.executed)
+        assert not n4.children_are_executed()
+        assert not n3.children_are_executed()
+        assert n2.children_are_executed()
+        assert n1.children_are_executed()
+
+        n2.set_state(SCons.Node.executed)
+        assert not n4.children_are_executed()
+        assert n3.children_are_executed()
+        assert n2.children_are_executed()
+        assert n1.children_are_executed()
+
+        n3.set_state(SCons.Node.executed)
+        assert n4.children_are_executed()
+        assert n3.children_are_executed()
+        assert n2.children_are_executed()
+        assert n1.children_are_executed()
+
+
 
 
 if __name__ == "__main__":

File src/engine/SCons/Node/__init__.py

View file
 executed = 2
 up_to_date = 3
 failed = 4
-
+pending = 5
 
 class Node:
     """The base Node class, for entities that we know how to
     def __init__(self):
 	self.sources = []
 	self.depends = []
+        self.parents = []
 	self.builder = None
 	self.env = None
         self.state = None
         return self.signature
 
     def add_dependency(self, depend):
-	"""Adds dependencies. The depends argument must be a list."""
-        if type(depend) is not type([]):
-            raise TypeError("depend must be a list")
-	depend = filter(lambda x, d=self.depends: x not in d, depend)
-	if len(depend):
-	    self.depends.extend(depend)
+	"""Adds dependencies. The depend argument must be a list."""
+        self._add_child(self.depends, depend)
 
     def add_source(self, source):
 	"""Adds sources. The source argument must be a list."""
-        if type(source) is not type([]):
-            raise TypeError("source must be a list")
-	source = filter(lambda x, s=self.sources: x not in s, source)
-	if len(source):
-	    self.sources.extend(source)
+        self._add_child(self.sources, source)
+
+    def _add_child(self, collection, child):
+        """Adds 'child' to 'collection'. The 'child' argument must be a list"""
+        if type(child) is not type([]):
+            raise TypeError("child must be a list")
+	child = filter(lambda x, s=collection: x not in s, child)
+	if child:
+	    collection.extend(child)
+
+        for c in child:
+            c._add_parent(self)
+
+    def _add_parent(self, parent):
+        """Adds 'parent' to the list of parents of this node"""
+
+        if parent not in self.parents: self.parents.append(parent)
 
     def children(self):
 	return self.sources + self.depends
 
+    def get_parents(self):
+        return self.parents
+
     def set_state(self, state):
         self.state = state
 
     def current(self):
         return None
 
+    def children_are_executed(self):
+        return reduce(lambda x,y: ((y.get_state() == executed
+                                   or y.get_state() == up_to_date)
+                                   and x),
+                      self.children(),
+                      1)
+
+def get_children(node): return node.children()
+
 class Wrapper:
-    def __init__(self, node):
+    def __init__(self, node, kids_func):
         self.node = node
-        self.kids = copy.copy(node.children())
+        self.kids = copy.copy(kids_func(node))
+
         # XXX randomize kids here, if requested
 
 class Walker:
     This is depth-first, children are visited before the parent.
     The Walker object can be initialized with any node, and
     returns the next node on the descent with each next() call.
+    'kids_func' is an optional function that will be called to
+    get the children of a node instead of calling 'children'.
     """
-    def __init__(self, node):
-        self.stack = [Wrapper(node)]
+    def __init__(self, node, kids_func=get_children):
+        self.kids_func = kids_func
+        self.stack = [Wrapper(node, self.kids_func)]
 
     def next(self):
 	"""Return the next node for this walk of the tree.
 
 	while self.stack:
 	    if self.stack[-1].kids:
-	    	self.stack.append(Wrapper(self.stack[-1].kids.pop(0)))
+	    	self.stack.append(Wrapper(self.stack[-1].kids.pop(0),
+                                          self.kids_func))
             else:
                 return self.stack.pop().node
 

File src/engine/SCons/Taskmaster.py

View file
 
     def set_state(self, state):
         return self.target.set_state(state)
+
+    def get_target(self):
+        return self.target
         
 def current(node):
     """Default SCons build engine is-it-current function.
     the base class method, so this class can do it's thing.    
     """
 
-    def __init__(self, targets=[], tasker=Task, current=current):
+    def __init__(self,
+                 targets=[],
+                 tasker=Task,
+                 current=current,
+                 ignore_errors=0,
+                 keep_going_on_error=0):
         self.walkers = map(SCons.Node.Walker, targets)
         self.tasker = tasker
         self.current = current
         self.targets = targets
+        self.ready = []
+        self.pending = 0
+        self.ignore_errors = ignore_errors
+        self.keep_going_on_error = keep_going_on_error
 
+        self._find_next_ready_node()
+        
     def next_task(self):
+        if self.ready:
+            n = self.ready.pop()
+            n.set_state(SCons.Node.executing)
+            if not self.ready:
+                self._find_next_ready_node()
+
+            return self.tasker(n)
+        else:
+            return None
+        
+    def _find_next_ready_node(self):
+        """Find the next node that is ready to be built"""
         while self.walkers:
             n = self.walkers[0].next()
             if n == None:
                 self.walkers.pop(0)
             elif n.get_state() == SCons.Node.up_to_date:
                 self.up_to_date(n, self.walkers[0].is_done())
-            elif n.get_state() == SCons.Node.failed:
-                # XXX do the right thing here
-                pass
-            elif n.get_state() == SCons.Node.executing:
-                # XXX do the right thing here
-                pass
-            elif n.get_state() == SCons.Node.executed:
-                # skip this node because it has already been executed
-                pass
-            elif self.current(n):
-                n.set_state(SCons.Node.up_to_date)
-                self.up_to_date(n, self.walkers[0].is_done())
-            else:
-                n.set_state(SCons.Node.executing)
-                return self.tasker(n)
-	return None
- 
+            elif n.get_state() == None:
+                if not n.children_are_executed():
+                    n.set_state(SCons.Node.pending)
+                    self.pending = self.pending + 1
+                elif self.current(n):
+                    n.set_state(SCons.Node.up_to_date)
+                    self.up_to_date(n, self.walkers[0].is_done())
+                else:
+                    self.ready.append(n)
+                    return
+        
     def is_blocked(self):
-        return 0
+        return not self.ready and self.pending
 
     def up_to_date(self, node):
         pass
     def executed(self, task):
         task.set_state(SCons.Node.executed)
 
+        # add all the pending parents that are now executable to the 'ready'
+        # queue:
+        n = task.get_target()
+        ready = filter(lambda x: (x.get_state() == SCons.Node.pending
+                                  and x.children_are_executed()),
+                       n.get_parents())
+        self.ready.extend(ready)
+        self.pending = self.pending - len(ready)
+        
     def failed(self, task):
-        self.walkers = []
-        task.set_state(SCons.Node.failed)
+        if self.ignore_errors:
+            self.executed(task)
+        else:
+            if self.keep_going_on_error:
+                # mark all the depants of this node as failed:
+                def get_parents(node): return node.get_parents()
+                walker = SCons.Node.Walker(task.get_target(), get_parents)
+                while 1:
+                    node = walker.next()
+                    if node == None: break
+                    if node.get_state() == SCons.Node.pending:
+                        self.pending = self.pending - 1
+                    node.set_state(SCons.Node.failed)
+            else:
+                # terminate the build:
+                self.walkers = []
+                self.pending = 0
+                self.ready = []
+
+            task.set_state(SCons.Node.failed)

File src/engine/SCons/TaskmasterTests.py

View file
     def __init__(self, name, kids = []):
         self.name = name
 	self.kids = kids
-
+        self.state = None
+        self.parents = []
+        
+        for kid in kids:
+            kid.parents.append(self)
+            
     def build(self):
         global built
         built = self.name + " built"
     def children(self):
 	return self.kids
 
+    def get_parents(self):
+        return self.parents
+    
     def get_state(self):
-        pass
+        return self.state
 
     def set_state(self, state):
-        pass
+        self.state = state
 
-class Task:
-    def __init__(self, target):
-        self.target = target
-
-    def set_state(self, state):
-        pass
+    def children_are_executed(self):
+        return reduce(lambda x,y: ((y.get_state() == SCons.Node.executed
+                                   or y.get_state() == SCons.Node.up_to_date)
+                                   and x),
+                      self.children(),
+                      1)
+    
 
 
 class TaskmasterTestCase(unittest.TestCase):
 	"""
 	global built
 
+        n1 = Node("n1")
+        tm = SCons.Taskmaster.Taskmaster([n1,n1])
+        t = tm.next_task()
+        tm.executed(t)
+        t = tm.next_task()
+        assert t == None
+
+        
 	n1 = Node("n1")
 	n2 = Node("n2")
         n3 = Node("n3", [n1, n2])
         
 	tm = SCons.Taskmaster.Taskmaster([n3])
-	tm.next_task().execute()
-	assert built == "n1 built"
 
-	tm.next_task().execute()
-	assert built == "n2 built"
+        t = tm.next_task()
+        t.execute()
+        assert built == "n1 built"
+        tm.executed(t)
 
-	tm.next_task().execute()
-	assert built == "n3 built"
+        t = tm.next_task()
+        t.execute()
+        assert built == "n2 built"
+        tm.executed(t)
+
+        t = tm.next_task()
+        t.execute()
+        assert built == "n3 built"
+        tm.executed(t)
+
+        assert tm.next_task() == None
 
 	def current(node):
 	    return 1
                 global built
                 built = built + " " + node.name
 
+
+        n1.set_state(None)
+        n2.set_state(None)
+        n3.set_state(None)
 	tm = MyTM(targets = [n3], current = current)
 	assert tm.next_task() == None
         print built
 	assert built == "up to date:  n1 n2 n3"
 
+
+        n1 = Node("n1")
+	n2 = Node("n2")
+        n3 = Node("n3", [n1, n2])
         n4 = Node("n4")
-        n4.get_state = lambda: SCons.Node.executed
+        n5 = Node("n5", [n3, n4])
+        tm = SCons.Taskmaster.Taskmaster([n5])
+
+        assert not tm.is_blocked()
+        
+        t1 = tm.next_task()
+        assert t1.get_target() == n1
+        assert not tm.is_blocked()
+        
+        t2 = tm.next_task()
+        assert t2.get_target() == n2
+        assert not tm.is_blocked()
+
+        t4 = tm.next_task()
+        assert t4.get_target() == n4
+        assert tm.is_blocked()
+        tm.executed(t4)
+        assert tm.is_blocked()
+        
+        tm.executed(t1)
+        assert tm.is_blocked()
+        tm.executed(t2)
+        assert not tm.is_blocked()
+        t3 = tm.next_task()
+        assert t3.get_target() == n3
+        assert tm.is_blocked()
+
+        tm.executed(t3)
+        assert not tm.is_blocked()
+        t5 = tm.next_task()
+        assert t5.get_target() == n5
+        assert not tm.is_blocked()
+
+        assert tm.next_task() == None
+
+        
+        n4 = Node("n4")
+        n4.set_state(SCons.Node.executed)
         tm = SCons.Taskmaster.Taskmaster([n4])
         assert tm.next_task() == None
-        
+
     def test_is_blocked(self):
         """Test whether a task is blocked
 
 	Both default and overridden in a subclass.
 	"""
 	tm = SCons.Taskmaster.Taskmaster()
-	assert tm.is_blocked() == 0
+	assert not tm.is_blocked()
 
 	class MyTM(SCons.Taskmaster.Taskmaster):
 	    def is_blocked(self):
 	Both default and overridden in a subclass.
 	"""
 	tm = SCons.Taskmaster.Taskmaster()
-	tm.executed(Task('foo'))
+        foo = Node('foo')
+	tm.executed(SCons.Taskmaster.Task(foo))
 
 	class MyTM(SCons.Taskmaster.Taskmaster):
 	    def executed(self, task):
 	tm = MyTM()
 	assert tm.executed('foo') == 'xfoo'
 
+    def test_ignore_errors(self):
+        n1 = Node("n1")
+        n2 = Node("n2")
+        n3 = Node("n3", [n1])
+        
+        tm = SCons.Taskmaster.Taskmaster([n3, n2],
+                                         SCons.Taskmaster.Task,
+                                         SCons.Taskmaster.current,
+                                         1)
+
+        t = tm.next_task()
+        assert t.get_target() == n1
+        tm.failed(t)
+        t = tm.next_task()
+        assert t.get_target() == n3
+        tm.failed(t)
+        t = tm.next_task()
+        assert t.get_target() == n2
+        
+
+    def test_keep_going(self):
+        n1 = Node("n1")
+        n2 = Node("n2")
+        n3 = Node("n3", [n1])
+        
+        tm = SCons.Taskmaster.Taskmaster([n3, n2],
+                                         SCons.Taskmaster.Task,
+                                         SCons.Taskmaster.current,
+                                         0,
+                                         1)
+
+        tm.failed(tm.next_task())
+        t = tm.next_task()
+        assert t.get_target() == n2
+        tm.executed(t)
+        assert not tm.is_blocked()
+        t = tm.next_task()
+        assert t == None
+
+
     def test_failed(self):
         """Test the failed() method
 
 	Both default and overridden in a subclass.
 	"""
-	tm = SCons.Taskmaster.Taskmaster()
-	#XXX
-	tm.failed(Task('foo'))
-
+        foo = Node('foo')
+        bar = Node('bar')
+        tm = SCons.Taskmaster.Taskmaster([foo,bar])
+        tm.failed(tm.next_task())
+        assert tm.next_task() == None
+        
 	class MyTM(SCons.Taskmaster.Taskmaster):
 	    def failed(self, task):
 	        return 'y' + task

File src/script/scons.py

View file
         if top:
             print 'scons: "%s" is up to date.' % node
         SCons.Taskmaster.Taskmaster.up_to_date(self, node)
-        
-    def failed(self, task):
-        if self.ignore_errors:
-            SCons.Taskmaster.Taskmaster.executed(self, task)
-        else:
-            SCons.Taskmaster.Taskmaster.failed(self, task)
 
-    ignore_errors = 0
-    
+
 # Global variables
 
 default_targets = []
 scripts = []
 task_class = BuildTask	# default action is to build targets
 current_func = None
+ignore_errors = 0
+keep_going_on_error = 0
 
 # utility functions
 
 	help = "Print this message and exit.")
 
     def opt_i(opt, arg):
-        ScriptTaskmaster.ignore_errors = 1
+        global ignore_errors
+        ignore_errors = 1
 
     Option(func = opt_i,
 	short = 'i', long = ['ignore-errors'],
 	short = 'j', long = ['jobs'], arg = 'N',
 	help = "Allow N jobs at once.")
 
-    Option(func = opt_not_yet,
+    def opt_k(opt, arg):
+        global keep_going_on_error
+        keep_going_on_error = 1
+
+    Option(func = opt_k,
 	short = 'k', long = ['keep-going'],
 	help = "Keep going when a target can't be made.")
 
     if not current_func:
         current_func = calc.current
 
-    taskmaster = ScriptTaskmaster(nodes, task_class, current_func)
+    taskmaster = ScriptTaskmaster(nodes,
+                                  task_class,
+                                  current_func,
+                                  ignore_errors,
+                                  keep_going_on_error)
 
     jobs = SCons.Job.Jobs(num_jobs, taskmaster)
     jobs.start()

File test/Program-j.py

View file
 
 test = TestSCons.TestSCons()
 
-test.pass_test()	#XXX Short-circuit until this is supported.
-
 test.write('SConstruct', """
 env = Environment()
 env.Program(target = 'f1', source = 'f1.c')

File test/option-k.py

View file
 
 test = TestSCons.TestSCons()
 
-test.pass_test()	#XXX Short-circuit until this is supported.
-
 test.write('succeed.py', r"""
 import sys
 file = open(sys.argv[1], 'wb')
 env.Succeed(target = 'bbb.out', source = 'bbb.in')
 """ % (python, python))
 
-test.run(arguments = '.')
+test.run(arguments = 'aaa.out bbb.out',
+         stderr =
+         'scons: *** [aaa.1] Error 1\n')
 
 test.fail_test(os.path.exists(test.workpath('aaa.1')))
 test.fail_test(os.path.exists(test.workpath('aaa.out')))
 test.fail_test(os.path.exists(test.workpath('bbb.out')))
 
-test.run(arguments = '-k .')
+test.run(arguments = '-k aaa.out bbb.out',
+         stderr =
+         'scons: *** [aaa.1] Error 1\n')
 
 test.fail_test(os.path.exists(test.workpath('aaa.1')))
 test.fail_test(os.path.exists(test.workpath('aaa.out')))
-test.fail_test(test.read('bbb.out') != "bbb.out\n")
+test.fail_test(test.read('bbb.out') != "succeed.py: bbb.out\n")
 
 test.unlink("bbb.out")
 
-test.run(arguments = '--keep-going .')
+test.run(arguments = '--keep-going aaa.out bbb.out',
+         stderr =
+         'scons: *** [aaa.1] Error 1\n')
 
 test.fail_test(os.path.exists(test.workpath('aaa.1')))
 test.fail_test(os.path.exists(test.workpath('aaa.out')))
-test.fail_test(test.read('bbb.out') != "bbb.out\n")
+test.fail_test(test.read('bbb.out') != "succeed.py: bbb.out\n")
 
 test.pass_test()
  

File test/up-to-date.py

View file
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out', stdout =
 """scons: "f1.out" is up to date.
+scons: "f3.out" is up to date.
 %s build.py f2.out f2.in
-scons: "f3.out" is up to date.
 %s build.py f4.out f4.in
 """ % (python, python))