runqueue.py: Change failed tasks handling so all failed tasks are reported, not just...
[vuplus_bitbake] / lib / bb / runqueue.py
index f07902f..05027c9 100644 (file)
@@ -20,13 +20,13 @@ You should have received a copy of the GNU General Public License along with
 """
 
 from bb import msg, data, fetch, event, mkdirhier, utils
+from sets import Set 
 import bb, os, sys
 
 class TaskFailure(Exception):
     """Exception raised when a task in a runqueue fails"""
-
-    def __init__(self, fnid, taskname):
-        self.args = fnid, taskname
+    def __init__(self, x): 
+        self.args = x
 
 class RunQueue:
     """
@@ -91,10 +91,33 @@ class RunQueue:
                                 dep = taskData.fn_index[depdata]
                                 depends.append(taskData.gettask_id(dep, taskname))
 
+                def add_recursive_build(depid):
+                    """
+                    Add build depends of depid to depends
+                    (if we've not see it before)
+                    (calls itself recursively)
+                    """
+                    if str(depid) in dep_seen:
+                        return
+                    dep_seen.append(depid)
+                    if depid in taskData.build_targets:
+                        depdata = taskData.build_targets[depid][0]
+                        if depdata:
+                            dep = taskData.fn_index[depdata]
+                            taskid = taskData.gettask_id(dep, taskname)
+                            depends.append(taskid)
+                            fnid = taskData.tasks_fnid[taskid]
+                            for nextdepid in taskData.depids[fnid]:
+                                if nextdepid not in dep_seen:
+                                    add_recursive_build(nextdepid)
+                            for nextdepid in taskData.rdepids[fnid]:
+                                if nextdepid not in rdep_seen:
+                                    add_recursive_run(nextdepid)
+
                 def add_recursive_run(rdepid):
                     """
-                    Add runtime depends of rdepid to depends, if 
-                    we've not see it before
+                    Add runtime depends of rdepid to depends
+                    (if we've not see it before)
                     (calls itself recursively)
                     """
                     if str(rdepid) in rdep_seen:
@@ -107,14 +130,22 @@ class RunQueue:
                             taskid = taskData.gettask_id(dep, taskname)
                             depends.append(taskid)
                             fnid = taskData.tasks_fnid[taskid]
+                            for nextdepid in taskData.depids[fnid]:
+                                if nextdepid not in dep_seen:
+                                    add_recursive_build(nextdepid)
                             for nextdepid in taskData.rdepids[fnid]:
                                 if nextdepid not in rdep_seen:
                                     add_recursive_run(nextdepid)
 
+
                 # Resolve Recursive Runtime Depends
+                # Also includes all Build Depends (and their runtime depends)
                 if 'recrdeptask' in task_deps and taskData.tasks_name[task] in task_deps['recrdeptask']:
+                    dep_seen = []
                     rdep_seen = []
                     taskname = task_deps['recrdeptask'][taskData.tasks_name[task]]
+                    for depid in taskData.depids[fnid]:
+                        add_recursive_build(depid)
                     for rdepid in taskData.rdepids[fnid]:
                         add_recursive_run(rdepid)
 
@@ -130,8 +161,8 @@ class RunQueue:
 
             self.runq_fnid.append(taskData.tasks_fnid[task])
             self.runq_task.append(taskData.tasks_name[task])
-            self.runq_depends.append(depends)
-            self.runq_revdeps.append([])
+            self.runq_depends.append(Set(depends))
+            self.runq_revdeps.append(Set())
             self.runq_weight.append(0)
 
             runq_weight1.append(0)
@@ -160,14 +191,14 @@ class RunQueue:
             if targetid in taskData.failed_deps:
                 continue
 
+            if targetid not in taskData.build_targets:
+                continue
+
             fnid = taskData.build_targets[targetid][0]
             if fnid in taskData.failed_fnids:
                 continue
 
-            fnids = taskData.matches_in_list(self.runq_fnid, fnid)
-            tasks = taskData.matches_in_list(self.runq_task, target[1])
-
-            listid = taskData.both_contain(fnids, tasks)
+            listid = taskData.tasks_lookup[fnid][target[1]]
 
             mark_active(listid, 1)
 
@@ -189,6 +220,12 @@ class RunQueue:
                 delcount = delcount + 1
                 maps.append(-1)
 
+        if len(self.runq_fnid) == 0:
+            if not taskData.abort:
+                bb.msg.note(1, bb.msg.domain.RunQueue, "All possible tasks have been run but build incomplete (--continue mode). See errors above for incomplete tasks.")
+                return
+            bb.msg.fatal(bb.msg.domain.RunQueue, "No active tasks and not in --continue mode?! Please report this bug.")
+
         bb.msg.note(2, bb.msg.domain.RunQueue, "Pruned %s inactive tasks, %s left" % (delcount, len(self.runq_fnid)))
 
         for listid in range(len(self.runq_fnid)):
@@ -198,14 +235,13 @@ class RunQueue:
                 if maps[origdep] == -1:
                     bb.msg.fatal(bb.msg.domain.RunQueue, "Invalid mapping - Should never happen!")
                 newdeps.append(maps[origdep])
-            self.runq_depends[listid] = newdeps
+            self.runq_depends[listid] = Set(newdeps)
 
         bb.msg.note(2, bb.msg.domain.RunQueue, "Assign Weightings")
 
         for listid in range(len(self.runq_fnid)):
             for dep in self.runq_depends[listid]:
-                if dep not in self.runq_revdeps[dep]:
-                    self.runq_revdeps[dep].append(listid)
+                self.runq_revdeps[dep].add(listid)
 
         endpoints = []
         for listid in range(len(self.runq_fnid)):
@@ -238,7 +274,22 @@ class RunQueue:
         # Sanity Checks
         for task in range(len(self.runq_fnid)):
             if runq_done[task] == 0:
-                bb.msg.fatal(bb.msg.domain.RunQueue, "Task %s (%s) not processed!" % (task, self.get_user_idstring(task, taskData)))
+                seen = []
+                deps_seen = []
+                def print_chain(taskid, finish):
+                    seen.append(taskid)
+                    for revdep in self.runq_revdeps[taskid]:
+                        if runq_done[revdep] == 0 and revdep not in seen and not finish:
+                            bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) (depends: %s)" % (revdep, self.get_user_idstring(revdep, taskData), self.runq_depends[revdep]))
+                            if revdep in deps_seen:
+                                bb.msg.error(bb.msg.domain.RunQueue, "Chain ends at Task %s (%s)" % (revdep, self.get_user_idstring(revdep, taskData)))
+                                finish = True
+                                return
+                            for dep in self.runq_depends[revdep]:
+                                deps_seen.append(dep)
+                            print_chain(revdep, finish)
+                print_chain(task, False)
+                bb.msg.fatal(bb.msg.domain.RunQueue, "Task %s (%s) not processed!\nThis is probably a circular dependency (the chain might be printed above)." % (task, self.get_user_idstring(task, taskData)))
             if runq_weight1[task] != 0:
                 bb.msg.fatal(bb.msg.domain.RunQueue, "Task %s (%s) count not zero!" % (task, self.get_user_idstring(task, taskData)))
 
@@ -256,6 +307,8 @@ class RunQueue:
             copyweight[idx] = -1
         self.prio_map.reverse()
 
+        #self.dump_data(taskData)
+
     def execute_runqueue(self, cooker, cfgData, dataCache, taskData, runlist):
         """
         Run the tasks in a queue prepared by prepare_runqueue
@@ -265,21 +318,23 @@ class RunQueue:
 
         failures = 0
         while 1:
-            try:
-                self.execute_runqueue_internal(cooker, cfgData, dataCache, taskData)
+            failed_fnids = self.execute_runqueue_internal(cooker, cfgData, dataCache, taskData)
+            if len(failed_fnids) == 0:
                 return failures
-            except bb.runqueue.TaskFailure, (fnid, taskname):
-                if cooker.configuration.abort:
-                    raise
+            if taskData.abort:
+                raise bb.runqueue.TaskFailure(failed_fnids)
+            for fnid in failed_fnids:
+                #print "Failure: %s %s %s" % (fnid, taskData.fn_index[fnid],  self.runq_task[fnid])
                 taskData.fail_fnid(fnid)
-                self.reset_runqueue()
-                self.prepare_runqueue(cfgData, dataCache, taskData, runlist)
                 failures = failures + 1
+            self.reset_runqueue()
+            self.prepare_runqueue(cfgData, dataCache, taskData, runlist)
 
     def execute_runqueue_internal(self, cooker, cfgData, dataCache, taskData):
         """
         Run the tasks in a queue prepared by prepare_runqueue
         """
+        import signal
 
         bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
 
@@ -288,6 +343,14 @@ class RunQueue:
         runq_complete = []
         active_builds = 0
         build_pids = {}
+        failed_fnids = []
+
+        if len(self.runq_fnid) == 0:
+            # nothing to do
+            return
+
+        def sigint_handler(signum, frame):
+            raise KeyboardInterrupt
 
         def get_next_task(data):
             """
@@ -343,10 +406,12 @@ class RunQueue:
                     taskname = self.runq_task[task]
 
                     if bb.build.stamp_is_current_cache(dataCache, fn, taskname):
-                        bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task, taskData)))
-                        runq_running[task] = 1
-                        task_complete(self, task)
-                        continue
+                        targetid = taskData.gettask_id(fn, taskname)
+                        if not (targetid in taskData.external_targets and cooker.configuration.force):
+                            bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task, taskData)))
+                            runq_running[task] = 1
+                            task_complete(self, task)
+                            continue
 
                     bb.msg.debug(1, bb.msg.domain.RunQueue, "Running task %s (%s)" % (task, self.get_user_idstring(task, taskData)))
                     try: 
@@ -354,14 +419,20 @@ class RunQueue:
                     except OSError, e: 
                         bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror))
                     if pid == 0:
+                        # Bypass finally below
+                        active_builds = 0 
+                        # Stop Ctrl+C being sent to children
+                        signal.signal(signal.SIGINT, signal.SIG_IGN)
+                        sys.stdin = open('/dev/null', 'r')
                         cooker.configuration.cmd = taskname[3:]
                         try: 
                             cooker.tryBuild(fn, False)
-                        except bb.build.EventException, e:
+                        except bb.build.EventException:
                             bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
                             sys.exit(1)
                         except:
-                            sys.exit(1)
+                            bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
+                            raise
                         sys.exit(0)
                     build_pids[pid] = task
                     runq_running[task] = 1
@@ -371,28 +442,39 @@ class RunQueue:
                 if active_builds > 0:
                     result = os.waitpid(-1, 0)
                     active_builds = active_builds - 1
+                    task = build_pids[result[0]]
                     if result[1] != 0:
-                        bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (build_pids[result[0]], self.get_user_idstring(build_pids[result[0]], taskData)))
-                        raise bb.runqueue.TaskFailure(self.runq_fnid[build_pids[result[0]]], self.runq_task[build_pids[result[0]]])
-                    task_complete(self, build_pids[result[0]])
+                        del build_pids[result[0]]
+                        bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task, taskData)))
+                        failed_fnids.append(self.runq_fnid[task])
+                        break
+                    task_complete(self, task)
                     del build_pids[result[0]]
                     continue
                 break
-        except SystemExit:
-            raise
-        except:
-            bb.msg.error(bb.msg.domain.RunQueue, "Exception received")
-            if active_builds > 0:
+        finally:
+            try:
+                orig_builds = active_builds
                 while active_builds > 0:
                     bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % active_builds)
                     tasknum = 1
                     for k, v in build_pids.iteritems():
-                        bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v, taskData), k))
-                        tasknum = tasknum + 1
+                         bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v, taskData), k))
+                         tasknum = tasknum + 1
                     result = os.waitpid(-1, 0)
-                    del build_pids[result[0]]              
+                    task = build_pids[result[0]]
+                    if result[1] != 0:
+                         bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task, taskData)))
+                         failed_fnids.append(self.runq_fnid[task])
+                    del build_pids[result[0]]
                     active_builds = active_builds - 1
-            raise
+                if orig_builds > 0:
+                    return failed_fnids
+            except:
+                bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGTERM to remaining %s tasks" % active_builds)
+                for k, v in build_pids.iteritems():
+                     os.kill(k, signal.SIGTERM)
+                raise
 
         # Sanity Checks
         for task in range(len(self.runq_fnid)):
@@ -403,7 +485,7 @@ class RunQueue:
             if runq_complete[task] == 0:
                 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
 
-        return 0
+        return failed_fnids
 
     def dump_data(self, taskQueue):
         """