summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/runqueue.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/runqueue.py')
-rw-r--r--bitbake/lib/bb/runqueue.py355
1 files changed, 201 insertions, 154 deletions
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
index ec94b0f8ba..059f800b65 100644
--- a/bitbake/lib/bb/runqueue.py
+++ b/bitbake/lib/bb/runqueue.py
@@ -25,20 +25,47 @@ Handles preparation and execution of a queue of tasks
25from bb import msg, data, fetch, event, mkdirhier, utils 25from bb import msg, data, fetch, event, mkdirhier, utils
26from sets import Set 26from sets import Set
27import bb, os, sys 27import bb, os, sys
28import signal
28 29
29class TaskFailure(Exception): 30class TaskFailure(Exception):
30 """Exception raised when a task in a runqueue fails""" 31 """Exception raised when a task in a runqueue fails"""
31 def __init__(self, x): 32 def __init__(self, x):
32 self.args = x 33 self.args = x
33 34
35
36class RunQueueStats:
37 """
38 Holds statistics on the tasks handled by the associated runQueue
39 """
40 def __init__(self):
41 self.completed = 0
42 self.skipped = 0
43 self.failed = 0
44
45 def taskFailed(self):
46 self.failed = self.failed + 1
47
48 def taskCompleted(self):
49 self.completed = self.completed + 1
50
51 def taskSkipped(self):
52 self.skipped = self.skipped + 1
53
34class RunQueue: 54class RunQueue:
35 """ 55 """
36 BitBake Run Queue implementation 56 BitBake Run Queue implementation
37 """ 57 """
38 def __init__(self): 58 def __init__(self, cooker, cfgData, dataCache, taskData, targets):
39 self.reset_runqueue() 59 self.reset_runqueue()
60 self.cooker = cooker
61 self.dataCache = dataCache
62 self.taskData = taskData
63 self.targets = targets
64
65 self.number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData) or 1)
40 66
41 def reset_runqueue(self): 67 def reset_runqueue(self):
68
42 self.runq_fnid = [] 69 self.runq_fnid = []
43 self.runq_task = [] 70 self.runq_task = []
44 self.runq_depends = [] 71 self.runq_depends = []
@@ -46,16 +73,15 @@ class RunQueue:
46 self.runq_weight = [] 73 self.runq_weight = []
47 self.prio_map = [] 74 self.prio_map = []
48 75
49 def get_user_idstring(self, task, taskData): 76 def get_user_idstring(self, task):
50 fn = taskData.fn_index[self.runq_fnid[task]] 77 fn = self.taskData.fn_index[self.runq_fnid[task]]
51 taskname = self.runq_task[task] 78 taskname = self.runq_task[task]
52 return "%s, %s" % (fn, taskname) 79 return "%s, %s" % (fn, taskname)
53 80
54 def prepare_runqueue(self, cooker, cfgData, dataCache, taskData, targets): 81 def prepare_runqueue(self):
55 """ 82 """
56 Turn a set of taskData into a RunQueue and compute data needed 83 Turn a set of taskData into a RunQueue and compute data needed
57 to optimise the execution order. 84 to optimise the execution order.
58 targets is list of paired values - a provider name and the task to run
59 """ 85 """
60 86
61 depends = [] 87 depends = []
@@ -63,12 +89,14 @@ class RunQueue:
63 runq_build = [] 89 runq_build = []
64 runq_done = [] 90 runq_done = []
65 91
92 taskData = self.taskData
93
66 bb.msg.note(1, bb.msg.domain.RunQueue, "Preparing Runqueue") 94 bb.msg.note(1, bb.msg.domain.RunQueue, "Preparing Runqueue")
67 95
68 for task in range(len(taskData.tasks_name)): 96 for task in range(len(taskData.tasks_name)):
69 fnid = taskData.tasks_fnid[task] 97 fnid = taskData.tasks_fnid[task]
70 fn = taskData.fn_index[fnid] 98 fn = taskData.fn_index[fnid]
71 task_deps = dataCache.task_deps[fn] 99 task_deps = self.dataCache.task_deps[fn]
72 100
73 if fnid not in taskData.failed_fnids: 101 if fnid not in taskData.failed_fnids:
74 102
@@ -94,6 +122,15 @@ class RunQueue:
94 dep = taskData.fn_index[depdata] 122 dep = taskData.fn_index[depdata]
95 depends.append(taskData.gettask_id(dep, taskname)) 123 depends.append(taskData.gettask_id(dep, taskname))
96 124
125 idepends = taskData.tasks_idepends[task]
126 for idepend in idepends:
127 depid = int(idepend.split(":")[0])
128 if depid in taskData.build_targets:
129 depdata = taskData.build_targets[depid][0]
130 if depdata:
131 dep = taskData.fn_index[depdata]
132 depends.append(taskData.gettask_id(dep, idepend.split(":")[1]))
133
97 def add_recursive_build(depid): 134 def add_recursive_build(depid):
98 """ 135 """
99 Add build depends of depid to depends 136 Add build depends of depid to depends
@@ -197,7 +234,7 @@ class RunQueue:
197 for depend in depends: 234 for depend in depends:
198 mark_active(depend, depth+1) 235 mark_active(depend, depth+1)
199 236
200 for target in targets: 237 for target in self.targets:
201 targetid = taskData.getbuild_id(target[0]) 238 targetid = taskData.getbuild_id(target[0])
202 239
203 if targetid not in taskData.build_targets: 240 if targetid not in taskData.build_targets:
@@ -209,10 +246,10 @@ class RunQueue:
209 fnid = taskData.build_targets[targetid][0] 246 fnid = taskData.build_targets[targetid][0]
210 247
211 # Remove stamps for targets if force mode active 248 # Remove stamps for targets if force mode active
212 if cooker.configuration.force: 249 if self.cooker.configuration.force:
213 fn = taskData.fn_index[fnid] 250 fn = taskData.fn_index[fnid]
214 bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (target[1], fn)) 251 bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (target[1], fn))
215 bb.build.del_stamp(target[1], dataCache, fn) 252 bb.build.del_stamp(target[1], self.dataCache, fn)
216 253
217 if fnid in taskData.failed_fnids: 254 if fnid in taskData.failed_fnids:
218 continue 255 continue
@@ -299,18 +336,18 @@ class RunQueue:
299 seen.append(taskid) 336 seen.append(taskid)
300 for revdep in self.runq_revdeps[taskid]: 337 for revdep in self.runq_revdeps[taskid]:
301 if runq_done[revdep] == 0 and revdep not in seen and not finish: 338 if runq_done[revdep] == 0 and revdep not in seen and not finish:
302 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) (depends: %s)" % (revdep, self.get_user_idstring(revdep, taskData), self.runq_depends[revdep])) 339 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) (depends: %s)" % (revdep, self.get_user_idstring(revdep), self.runq_depends[revdep]))
303 if revdep in deps_seen: 340 if revdep in deps_seen:
304 bb.msg.error(bb.msg.domain.RunQueue, "Chain ends at Task %s (%s)" % (revdep, self.get_user_idstring(revdep, taskData))) 341 bb.msg.error(bb.msg.domain.RunQueue, "Chain ends at Task %s (%s)" % (revdep, self.get_user_idstring(revdep)))
305 finish = True 342 finish = True
306 return 343 return
307 for dep in self.runq_depends[revdep]: 344 for dep in self.runq_depends[revdep]:
308 deps_seen.append(dep) 345 deps_seen.append(dep)
309 print_chain(revdep, finish) 346 print_chain(revdep, finish)
310 print_chain(task, False) 347 print_chain(task, False)
311 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))) 348 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)))
312 if runq_weight1[task] != 0: 349 if runq_weight1[task] != 0:
313 bb.msg.fatal(bb.msg.domain.RunQueue, "Task %s (%s) count not zero!" % (task, self.get_user_idstring(task, taskData))) 350 bb.msg.fatal(bb.msg.domain.RunQueue, "Task %s (%s) count not zero!" % (task, self.get_user_idstring(task)))
314 351
315 # Make a weight sorted map 352 # Make a weight sorted map
316 from copy import deepcopy 353 from copy import deepcopy
@@ -328,7 +365,7 @@ class RunQueue:
328 365
329 #self.dump_data(taskData) 366 #self.dump_data(taskData)
330 367
331 def execute_runqueue(self, cooker, cfgData, dataCache, taskData, runlist): 368 def execute_runqueue(self):
332 """ 369 """
333 Run the tasks in a queue prepared by prepare_runqueue 370 Run the tasks in a queue prepared by prepare_runqueue
334 Upon failure, optionally try to recover the build using any alternate providers 371 Upon failure, optionally try to recover the build using any alternate providers
@@ -337,35 +374,86 @@ class RunQueue:
337 374
338 failures = 0 375 failures = 0
339 while 1: 376 while 1:
340 failed_fnids = self.execute_runqueue_internal(cooker, cfgData, dataCache, taskData) 377 failed_fnids = []
378 try:
379 self.execute_runqueue_internal()
380 finally:
381 if self.master_process:
382 failed_fnids = self.finish_runqueue()
341 if len(failed_fnids) == 0: 383 if len(failed_fnids) == 0:
342 return failures 384 return failures
343 if taskData.abort: 385 if self.taskData.abort:
344 raise bb.runqueue.TaskFailure(failed_fnids) 386 raise bb.runqueue.TaskFailure(failed_fnids)
345 for fnid in failed_fnids: 387 for fnid in failed_fnids:
346 #print "Failure: %s %s %s" % (fnid, taskData.fn_index[fnid], self.runq_task[fnid]) 388 #print "Failure: %s %s %s" % (fnid, self.taskData.fn_index[fnid], self.runq_task[fnid])
347 taskData.fail_fnid(fnid) 389 self.taskData.fail_fnid(fnid)
348 failures = failures + 1 390 failures = failures + 1
349 self.reset_runqueue() 391 self.reset_runqueue()
350 self.prepare_runqueue(cooker, cfgData, dataCache, taskData, runlist) 392 self.prepare_runqueue()
393
394 def execute_runqueue_initVars(self):
395
396 self.stats = RunQueueStats()
397
398 self.active_builds = 0
399 self.runq_buildable = []
400 self.runq_running = []
401 self.runq_complete = []
402 self.build_pids = {}
403 self.failed_fnids = []
404 self.master_process = True
351 405
352 def execute_runqueue_internal(self, cooker, cfgData, dataCache, taskData): 406 # Mark initial buildable tasks
407 for task in range(len(self.runq_fnid)):
408 self.runq_running.append(0)
409 self.runq_complete.append(0)
410 if len(self.runq_depends[task]) == 0:
411 self.runq_buildable.append(1)
412 else:
413 self.runq_buildable.append(0)
414
415 def task_complete(self, task):
416 """
417 Mark a task as completed
418 Look at the reverse dependencies and mark any task with
419 completed dependencies as buildable
420 """
421 self.runq_complete[task] = 1
422 for revdep in self.runq_revdeps[task]:
423 if self.runq_running[revdep] == 1:
424 continue
425 if self.runq_buildable[revdep] == 1:
426 continue
427 alldeps = 1
428 for dep in self.runq_depends[revdep]:
429 if self.runq_complete[dep] != 1:
430 alldeps = 0
431 if alldeps == 1:
432 self.runq_buildable[revdep] = 1
433 fn = self.taskData.fn_index[self.runq_fnid[revdep]]
434 taskname = self.runq_task[revdep]
435 bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname))
436
437 def get_next_task(self):
438 """
439 Return the id of the highest priority task that is buildable
440 """
441 for task1 in range(len(self.runq_fnid)):
442 task = self.prio_map[task1]
443 if self.runq_running[task] == 1:
444 continue
445 if self.runq_buildable[task] == 1:
446 return task
447 return None
448
449 def execute_runqueue_internal(self):
353 """ 450 """
354 Run the tasks in a queue prepared by prepare_runqueue 451 Run the tasks in a queue prepared by prepare_runqueue
355 """ 452 """
356 import signal
357 453
358 bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue") 454 bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
359 455
360 active_builds = 0 456 self.execute_runqueue_initVars()
361 tasks_completed = 0
362 tasks_skipped = 0
363
364 runq_buildable = []
365 runq_running = []
366 runq_complete = []
367 build_pids = {}
368 failed_fnids = []
369 457
370 if len(self.runq_fnid) == 0: 458 if len(self.runq_fnid) == 0:
371 # nothing to do 459 # nothing to do
@@ -374,144 +462,103 @@ class RunQueue:
374 def sigint_handler(signum, frame): 462 def sigint_handler(signum, frame):
375 raise KeyboardInterrupt 463 raise KeyboardInterrupt
376 464
377 def get_next_task(data): 465 while True:
378 """ 466 task = self.get_next_task()
379 Return the id of the highest priority task that is buildable 467 if task is not None:
380 """ 468 fn = self.taskData.fn_index[self.runq_fnid[task]]
381 for task1 in range(len(data.runq_fnid)): 469
382 task = data.prio_map[task1] 470 taskname = self.runq_task[task]
383 if runq_running[task] == 1: 471 if bb.build.stamp_is_current(taskname, self.dataCache, fn):
472 bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task)))
473 self.runq_running[task] = 1
474 self.task_complete(task)
475 self.stats.taskCompleted()
476 self.stats.taskSkipped()
384 continue 477 continue
385 if runq_buildable[task] == 1:
386 return task
387 return None
388 478
389 def task_complete(data, task): 479 bb.msg.note(1, bb.msg.domain.RunQueue, "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.active_builds + 1, len(self.runq_fnid), task, self.get_user_idstring(task)))
390 """ 480 try:
391 Mark a task as completed 481 pid = os.fork()
392 Look at the reverse dependencies and mark any task with 482 except OSError, e:
393 completed dependencies as buildable 483 bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror))
394 """ 484 if pid == 0:
395 runq_complete[task] = 1 485 # Bypass master process' handling
396 for revdep in data.runq_revdeps[task]: 486 self.master_process = False
397 if runq_running[revdep] == 1: 487 # Stop Ctrl+C being sent to children
398 continue 488 # signal.signal(signal.SIGINT, signal.SIG_IGN)
399 if runq_buildable[revdep] == 1: 489 # Make the child the process group leader
490 os.setpgid(0, 0)
491 sys.stdin = open('/dev/null', 'r')
492 self.cooker.configuration.cmd = taskname[3:]
493 try:
494 self.cooker.tryBuild(fn, False)
495 except bb.build.EventException:
496 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
497 sys.exit(1)
498 except:
499 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
500 raise
501 sys.exit(0)
502 self.build_pids[pid] = task
503 self.runq_running[task] = 1
504 self.active_builds = self.active_builds + 1
505 if self.active_builds < self.number_tasks:
400 continue 506 continue
401 alldeps = 1 507 if self.active_builds > 0:
402 for dep in data.runq_depends[revdep]: 508 result = os.waitpid(-1, 0)
403 if runq_complete[dep] != 1: 509 self.active_builds = self.active_builds - 1
404 alldeps = 0 510 task = self.build_pids[result[0]]
405 if alldeps == 1: 511 if result[1] != 0:
406 runq_buildable[revdep] = 1 512 del self.build_pids[result[0]]
407 fn = taskData.fn_index[self.runq_fnid[revdep]] 513 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
408 taskname = self.runq_task[revdep] 514 self.failed_fnids.append(self.runq_fnid[task])
409 bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname)) 515 self.stats.taskFailed()
410 516 break
411 # Mark initial buildable tasks 517 self.task_complete(task)
412 for task in range(len(self.runq_fnid)): 518 self.stats.taskCompleted()
413 runq_running.append(0) 519 del self.build_pids[result[0]]
414 runq_complete.append(0) 520 continue
415 if len(self.runq_depends[task]) == 0: 521 return
416 runq_buildable.append(1)
417 else:
418 runq_buildable.append(0)
419
420
421 number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData) or 1)
422 522
523 def finish_runqueue(self):
423 try: 524 try:
424 while 1: 525 while self.active_builds > 0:
425 task = get_next_task(self) 526 bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.active_builds)
426 if task is not None: 527 tasknum = 1
427 fn = taskData.fn_index[self.runq_fnid[task]] 528 for k, v in self.build_pids.iteritems():
428 taskname = self.runq_task[task] 529 bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
429 530 tasknum = tasknum + 1
430 if bb.build.stamp_is_current(taskname, dataCache, fn): 531 result = os.waitpid(-1, 0)
431 bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task, taskData))) 532 task = self.build_pids[result[0]]
432 runq_running[task] = 1 533 if result[1] != 0:
433 task_complete(self, task) 534 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
434 tasks_completed = tasks_completed + 1 535 self.failed_fnids.append(self.runq_fnid[task])
435 tasks_skipped = tasks_skipped + 1 536 self.stats.taskFailed()
436 continue 537 del self.build_pids[result[0]]
437 538 self.active_builds = self.active_builds - 1
438 bb.msg.note(1, bb.msg.domain.RunQueue, "Running task %d of %d (ID: %s, %s)" % (tasks_completed + active_builds + 1, len(self.runq_fnid), task, self.get_user_idstring(task, taskData))) 539 if len(self.failed_fnids) > 0:
439 try: 540 return self.failed_fnids
440 pid = os.fork() 541 except KeyboardInterrupt:
441 except OSError, e: 542 bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.active_builds)
442 bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror)) 543 for k, v in self.build_pids.iteritems():
443 if pid == 0: 544 try:
444 # Bypass finally below
445 active_builds = 0
446 # Stop Ctrl+C being sent to children
447 # signal.signal(signal.SIGINT, signal.SIG_IGN)
448 # Make the child the process group leader
449 os.setpgid(0, 0)
450 sys.stdin = open('/dev/null', 'r')
451 cooker.configuration.cmd = taskname[3:]
452 try:
453 cooker.tryBuild(fn, False)
454 except bb.build.EventException:
455 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
456 sys.exit(1)
457 except:
458 bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
459 raise
460 sys.exit(0)
461 build_pids[pid] = task
462 runq_running[task] = 1
463 active_builds = active_builds + 1
464 if active_builds < number_tasks:
465 continue
466 if active_builds > 0:
467 result = os.waitpid(-1, 0)
468 active_builds = active_builds - 1
469 task = build_pids[result[0]]
470 if result[1] != 0:
471 del build_pids[result[0]]
472 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task, taskData)))
473 failed_fnids.append(self.runq_fnid[task])
474 break
475 task_complete(self, task)
476 tasks_completed = tasks_completed + 1
477 del build_pids[result[0]]
478 continue
479 break
480 finally:
481 try:
482 while active_builds > 0:
483 bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % active_builds)
484 tasknum = 1
485 for k, v in build_pids.iteritems():
486 bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v, taskData), k))
487 tasknum = tasknum + 1
488 result = os.waitpid(-1, 0)
489 task = build_pids[result[0]]
490 if result[1] != 0:
491 bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task, taskData)))
492 failed_fnids.append(self.runq_fnid[task])
493 del build_pids[result[0]]
494 active_builds = active_builds - 1
495 if len(failed_fnids) > 0:
496 return failed_fnids
497 except:
498 bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % active_builds)
499 for k, v in build_pids.iteritems():
500 os.kill(-k, signal.SIGINT) 545 os.kill(-k, signal.SIGINT)
501 raise 546 except:
547 pass
548 raise
502 549
503 # Sanity Checks 550 # Sanity Checks
504 for task in range(len(self.runq_fnid)): 551 for task in range(len(self.runq_fnid)):
505 if runq_buildable[task] == 0: 552 if self.runq_buildable[task] == 0:
506 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task) 553 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
507 if runq_running[task] == 0: 554 if self.runq_running[task] == 0:
508 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task) 555 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
509 if runq_complete[task] == 0: 556 if self.runq_complete[task] == 0:
510 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task) 557 bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
511 558
512 bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (tasks_completed, tasks_skipped, len(failed_fnids))) 559 bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
513 560
514 return failed_fnids 561 return self.failed_fnids
515 562
516 def dump_data(self, taskQueue): 563 def dump_data(self, taskQueue):
517 """ 564 """