1# ex:ts=4:sw=4:sts=4:et 2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- 3""" 4BitBake 'Event' implementation 5 6Classes and functions for manipulating 'events' in the 7BitBake build tools. 8""" 9 10# Copyright (C) 2003, 2004 Chris Larson 11# 12# This program is free software; you can redistribute it and/or modify 13# it under the terms of the GNU General Public License version 2 as 14# published by the Free Software Foundation. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License along 22# with this program; if not, write to the Free Software Foundation, Inc., 23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24 25import os, sys 26import warnings 27import pickle 28import logging 29import atexit 30import traceback 31import ast 32import threading 33 34import bb.utils 35import bb.compat 36import bb.exceptions 37 38# This is the pid for which we should generate the event. This is set when 39# the runqueue forks off. 40worker_pid = 0 41worker_fire = None 42 43logger = logging.getLogger('BitBake.Event') 44 45class Event(object): 46 """Base class for events""" 47 48 def __init__(self): 49 self.pid = worker_pid 50 51 52class HeartbeatEvent(Event): 53 """Triggered at regular time intervals of 10 seconds. Other events can fire much more often 54 (runQueueTaskStarted when there are many short tasks) or not at all for long periods 55 of time (again runQueueTaskStarted, when there is just one long-running task), so this 56 event is more suitable for doing some task-independent work occassionally.""" 57 def __init__(self, time): 58 Event.__init__(self) 59 self.time = time 60 61Registered = 10 62AlreadyRegistered = 14 63 64def get_class_handlers(): 65 return _handlers 66 67def set_class_handlers(h): 68 global _handlers 69 _handlers = h 70 71def clean_class_handlers(): 72 return bb.compat.OrderedDict() 73 74# Internal 75_handlers = clean_class_handlers() 76_ui_handlers = {} 77_ui_logfilters = {} 78_ui_handler_seq = 0 79_event_handler_map = {} 80_catchall_handlers = {} 81_eventfilter = None 82_uiready = False 83_thread_lock = threading.Lock() 84_thread_lock_enabled = False 85 86if hasattr(__builtins__, '__setitem__'): 87 builtins = __builtins__ 88else: 89 builtins = __builtins__.__dict__ 90 91def enable_threadlock(): 92 global _thread_lock_enabled 93 _thread_lock_enabled = True 94 95def disable_threadlock(): 96 global _thread_lock_enabled 97 _thread_lock_enabled = False 98 99def execute_handler(name, handler, event, d): 100 event.data = d 101 addedd = False 102 if 'd' not in builtins: 103 builtins['d'] = d 104 addedd = True 105 try: 106 ret = handler(event) 107 except (bb.parse.SkipRecipe, bb.BBHandledException): 108 raise 109 except Exception: 110 etype, value, tb = sys.exc_info() 111 logger.error("Execution of event handler '%s' failed" % name, 112 exc_info=(etype, value, tb.tb_next)) 113 raise 114 except SystemExit as exc: 115 if exc.code != 0: 116 logger.error("Execution of event handler '%s' failed" % name) 117 raise 118 finally: 119 del event.data 120 if addedd: 121 del builtins['d'] 122 123def fire_class_handlers(event, d): 124 if isinstance(event, logging.LogRecord): 125 return 126 127 eid = str(event.__class__)[8:-2] 128 evt_hmap = _event_handler_map.get(eid, {}) 129 for name, handler in list(_handlers.items()): 130 if name in _catchall_handlers or name in evt_hmap: 131 if _eventfilter: 132 if not _eventfilter(name, handler, event, d): 133 continue 134 execute_handler(name, handler, event, d) 135 136ui_queue = [] 137@atexit.register 138def print_ui_queue(): 139 """If we're exiting before a UI has been spawned, display any queued 140 LogRecords to the console.""" 141 logger = logging.getLogger("BitBake") 142 if not _uiready: 143 from bb.msg import BBLogFormatter 144 stdout = logging.StreamHandler(sys.stdout) 145 stderr = logging.StreamHandler(sys.stderr) 146 formatter = BBLogFormatter("%(levelname)s: %(message)s") 147 stdout.setFormatter(formatter) 148 stderr.setFormatter(formatter) 149 150 # First check to see if we have any proper messages 151 msgprint = False 152 msgerrs = False 153 154 # Should we print to stderr? 155 for event in ui_queue[:]: 156 if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING: 157 msgerrs = True 158 break 159 160 if msgerrs: 161 logger.addHandler(stderr) 162 else: 163 logger.addHandler(stdout) 164 165 for event in ui_queue[:]: 166 if isinstance(event, logging.LogRecord): 167 if event.levelno > logging.DEBUG: 168 logger.handle(event) 169 msgprint = True 170 171 # Nope, so just print all of the messages we have (including debug messages) 172 if not msgprint: 173 for event in ui_queue[:]: 174 if isinstance(event, logging.LogRecord): 175 logger.handle(event) 176 if msgerrs: 177 logger.removeHandler(stderr) 178 else: 179 logger.removeHandler(stdout) 180 181def fire_ui_handlers(event, d): 182 global _thread_lock 183 global _thread_lock_enabled 184 185 if not _uiready: 186 # No UI handlers registered yet, queue up the messages 187 ui_queue.append(event) 188 return 189 190 if _thread_lock_enabled: 191 _thread_lock.acquire() 192 193 errors = [] 194 for h in _ui_handlers: 195 #print "Sending event %s" % event 196 try: 197 if not _ui_logfilters[h].filter(event): 198 continue 199 # We use pickle here since it better handles object instances 200 # which xmlrpc's marshaller does not. Events *must* be serializable 201 # by pickle. 202 if hasattr(_ui_handlers[h].event, "sendpickle"): 203 _ui_handlers[h].event.sendpickle((pickle.dumps(event))) 204 else: 205 _ui_handlers[h].event.send(event) 206 except: 207 errors.append(h) 208 for h in errors: 209 del _ui_handlers[h] 210 211 if _thread_lock_enabled: 212 _thread_lock.release() 213 214def fire(event, d): 215 """Fire off an Event""" 216 217 # We can fire class handlers in the worker process context and this is 218 # desired so they get the task based datastore. 219 # UI handlers need to be fired in the server context so we defer this. They 220 # don't have a datastore so the datastore context isn't a problem. 221 222 fire_class_handlers(event, d) 223 if worker_fire: 224 worker_fire(event, d) 225 else: 226 # If messages have been queued up, clear the queue 227 global _uiready, ui_queue 228 if _uiready and ui_queue: 229 for queue_event in ui_queue: 230 fire_ui_handlers(queue_event, d) 231 ui_queue = [] 232 fire_ui_handlers(event, d) 233 234def fire_from_worker(event, d): 235 fire_ui_handlers(event, d) 236 237noop = lambda _: None 238def register(name, handler, mask=None, filename=None, lineno=None): 239 """Register an Event handler""" 240 241 # already registered 242 if name in _handlers: 243 return AlreadyRegistered 244 245 if handler is not None: 246 # handle string containing python code 247 if isinstance(handler, str): 248 tmp = "def %s(e):\n%s" % (name, handler) 249 try: 250 code = bb.methodpool.compile_cache(tmp) 251 if not code: 252 if filename is None: 253 filename = "%s(e)" % name 254 code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST) 255 if lineno is not None: 256 ast.increment_lineno(code, lineno-1) 257 code = compile(code, filename, "exec") 258 bb.methodpool.compile_cache_add(tmp, code) 259 except SyntaxError: 260 logger.error("Unable to register event handler '%s':\n%s", name, 261 ''.join(traceback.format_exc(limit=0))) 262 _handlers[name] = noop 263 return 264 env = {} 265 bb.utils.better_exec(code, env) 266 func = bb.utils.better_eval(name, env) 267 _handlers[name] = func 268 else: 269 _handlers[name] = handler 270 271 if not mask or '*' in mask: 272 _catchall_handlers[name] = True 273 else: 274 for m in mask: 275 if _event_handler_map.get(m, None) is None: 276 _event_handler_map[m] = {} 277 _event_handler_map[m][name] = True 278 279 return Registered 280 281def remove(name, handler): 282 """Remove an Event handler""" 283 _handlers.pop(name) 284 if name in _catchall_handlers: 285 _catchall_handlers.pop(name) 286 for event in _event_handler_map.keys(): 287 if name in _event_handler_map[event]: 288 _event_handler_map[event].pop(name) 289 290def get_handlers(): 291 return _handlers 292 293def set_handlers(handlers): 294 global _handlers 295 _handlers = handlers 296 297def set_eventfilter(func): 298 global _eventfilter 299 _eventfilter = func 300 301def register_UIHhandler(handler, mainui=False): 302 bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1 303 _ui_handlers[_ui_handler_seq] = handler 304 level, debug_domains = bb.msg.constructLogOptions() 305 _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains) 306 if mainui: 307 global _uiready 308 _uiready = _ui_handler_seq 309 return _ui_handler_seq 310 311def unregister_UIHhandler(handlerNum, mainui=False): 312 if mainui: 313 global _uiready 314 _uiready = False 315 if handlerNum in _ui_handlers: 316 del _ui_handlers[handlerNum] 317 return 318 319def get_uihandler(): 320 if _uiready is False: 321 return None 322 return _uiready 323 324# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC 325class UIEventFilter(object): 326 def __init__(self, level, debug_domains): 327 self.update(None, level, debug_domains) 328 329 def update(self, eventmask, level, debug_domains): 330 self.eventmask = eventmask 331 self.stdlevel = level 332 self.debug_domains = debug_domains 333 334 def filter(self, event): 335 if isinstance(event, logging.LogRecord): 336 if event.levelno >= self.stdlevel: 337 return True 338 if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]: 339 return True 340 return False 341 eid = str(event.__class__)[8:-2] 342 if self.eventmask and eid not in self.eventmask: 343 return False 344 return True 345 346def set_UIHmask(handlerNum, level, debug_domains, mask): 347 if not handlerNum in _ui_handlers: 348 return False 349 if '*' in mask: 350 _ui_logfilters[handlerNum].update(None, level, debug_domains) 351 else: 352 _ui_logfilters[handlerNum].update(mask, level, debug_domains) 353 return True 354 355def getName(e): 356 """Returns the name of a class or class instance""" 357 if getattr(e, "__name__", None) == None: 358 return e.__class__.__name__ 359 else: 360 return e.__name__ 361 362class OperationStarted(Event): 363 """An operation has begun""" 364 def __init__(self, msg = "Operation Started"): 365 Event.__init__(self) 366 self.msg = msg 367 368class OperationCompleted(Event): 369 """An operation has completed""" 370 def __init__(self, total, msg = "Operation Completed"): 371 Event.__init__(self) 372 self.total = total 373 self.msg = msg 374 375class OperationProgress(Event): 376 """An operation is in progress""" 377 def __init__(self, current, total, msg = "Operation in Progress"): 378 Event.__init__(self) 379 self.current = current 380 self.total = total 381 self.msg = msg + ": %s/%s" % (current, total); 382 383class ConfigParsed(Event): 384 """Configuration Parsing Complete""" 385 386class MultiConfigParsed(Event): 387 """Multi-Config Parsing Complete""" 388 def __init__(self, mcdata): 389 self.mcdata = mcdata 390 Event.__init__(self) 391 392class RecipeEvent(Event): 393 def __init__(self, fn): 394 self.fn = fn 395 Event.__init__(self) 396 397class RecipePreFinalise(RecipeEvent): 398 """ Recipe Parsing Complete but not yet finialised""" 399 400class RecipeTaskPreProcess(RecipeEvent): 401 """ 402 Recipe Tasks about to be finalised 403 The list of tasks should be final at this point and handlers 404 are only able to change interdependencies 405 """ 406 def __init__(self, fn, tasklist): 407 self.fn = fn 408 self.tasklist = tasklist 409 Event.__init__(self) 410 411class RecipeParsed(RecipeEvent): 412 """ Recipe Parsing Complete """ 413 414class StampUpdate(Event): 415 """Trigger for any adjustment of the stamp files to happen""" 416 417 def __init__(self, targets, stampfns): 418 self._targets = targets 419 self._stampfns = stampfns 420 Event.__init__(self) 421 422 def getStampPrefix(self): 423 return self._stampfns 424 425 def getTargets(self): 426 return self._targets 427 428 stampPrefix = property(getStampPrefix) 429 targets = property(getTargets) 430 431class BuildBase(Event): 432 """Base class for bitbake build events""" 433 434 def __init__(self, n, p, failures = 0): 435 self._name = n 436 self._pkgs = p 437 Event.__init__(self) 438 self._failures = failures 439 440 def getPkgs(self): 441 return self._pkgs 442 443 def setPkgs(self, pkgs): 444 self._pkgs = pkgs 445 446 def getName(self): 447 return self._name 448 449 def setName(self, name): 450 self._name = name 451 452 def getFailures(self): 453 """ 454 Return the number of failed packages 455 """ 456 return self._failures 457 458 pkgs = property(getPkgs, setPkgs, None, "pkgs property") 459 name = property(getName, setName, None, "name property") 460 461class BuildInit(BuildBase): 462 """buildFile or buildTargets was invoked""" 463 def __init__(self, p=[]): 464 name = None 465 BuildBase.__init__(self, name, p) 466 467class BuildStarted(BuildBase, OperationStarted): 468 """Event when builds start""" 469 def __init__(self, n, p, failures = 0): 470 OperationStarted.__init__(self, "Building Started") 471 BuildBase.__init__(self, n, p, failures) 472 473class BuildCompleted(BuildBase, OperationCompleted): 474 """Event when builds have completed""" 475 def __init__(self, total, n, p, failures=0, interrupted=0): 476 if not failures: 477 OperationCompleted.__init__(self, total, "Building Succeeded") 478 else: 479 OperationCompleted.__init__(self, total, "Building Failed") 480 self._interrupted = interrupted 481 BuildBase.__init__(self, n, p, failures) 482 483class DiskFull(Event): 484 """Disk full case build aborted""" 485 def __init__(self, dev, type, freespace, mountpoint): 486 Event.__init__(self) 487 self._dev = dev 488 self._type = type 489 self._free = freespace 490 self._mountpoint = mountpoint 491 492class DiskUsageSample: 493 def __init__(self, available_bytes, free_bytes, total_bytes): 494 # Number of bytes available to non-root processes. 495 self.available_bytes = available_bytes 496 # Number of bytes available to root processes. 497 self.free_bytes = free_bytes 498 # Total capacity of the volume. 499 self.total_bytes = total_bytes 500 501class MonitorDiskEvent(Event): 502 """If BB_DISKMON_DIRS is set, then this event gets triggered each time disk space is checked. 503 Provides information about devices that are getting monitored.""" 504 def __init__(self, disk_usage): 505 Event.__init__(self) 506 # hash of device root path -> DiskUsageSample 507 self.disk_usage = disk_usage 508 509class NoProvider(Event): 510 """No Provider for an Event""" 511 512 def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None): 513 Event.__init__(self) 514 self._item = item 515 self._runtime = runtime 516 self._dependees = dependees 517 self._reasons = reasons 518 self._close_matches = close_matches 519 520 def getItem(self): 521 return self._item 522 523 def isRuntime(self): 524 return self._runtime 525 526 def __str__(self): 527 msg = '' 528 if self._runtime: 529 r = "R" 530 else: 531 r = "" 532 533 extra = '' 534 if not self._reasons: 535 if self._close_matches: 536 extra = ". Close matches:\n %s" % '\n '.join(self._close_matches) 537 538 if self._dependees: 539 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, self._item, ", ".join(self._dependees), r, extra) 540 else: 541 msg = "Nothing %sPROVIDES '%s'%s" % (r, self._item, extra) 542 if self._reasons: 543 for reason in self._reasons: 544 msg += '\n' + reason 545 return msg 546 547 548class MultipleProviders(Event): 549 """Multiple Providers""" 550 551 def __init__(self, item, candidates, runtime = False): 552 Event.__init__(self) 553 self._item = item 554 self._candidates = candidates 555 self._is_runtime = runtime 556 557 def isRuntime(self): 558 """ 559 Is this a runtime issue? 560 """ 561 return self._is_runtime 562 563 def getItem(self): 564 """ 565 The name for the to be build item 566 """ 567 return self._item 568 569 def getCandidates(self): 570 """ 571 Get the possible Candidates for a PROVIDER. 572 """ 573 return self._candidates 574 575 def __str__(self): 576 msg = "Multiple providers are available for %s%s (%s)" % (self._is_runtime and "runtime " or "", 577 self._item, 578 ", ".join(self._candidates)) 579 rtime = "" 580 if self._is_runtime: 581 rtime = "R" 582 msg += "\nConsider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, self._item) 583 return msg 584 585class ParseStarted(OperationStarted): 586 """Recipe parsing for the runqueue has begun""" 587 def __init__(self, total): 588 OperationStarted.__init__(self, "Recipe parsing Started") 589 self.total = total 590 591class ParseCompleted(OperationCompleted): 592 """Recipe parsing for the runqueue has completed""" 593 def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total): 594 OperationCompleted.__init__(self, total, "Recipe parsing Completed") 595 self.cached = cached 596 self.parsed = parsed 597 self.skipped = skipped 598 self.virtuals = virtuals 599 self.masked = masked 600 self.errors = errors 601 self.sofar = cached + parsed 602 603class ParseProgress(OperationProgress): 604 """Recipe parsing progress""" 605 def __init__(self, current, total): 606 OperationProgress.__init__(self, current, total, "Recipe parsing") 607 608 609class CacheLoadStarted(OperationStarted): 610 """Loading of the dependency cache has begun""" 611 def __init__(self, total): 612 OperationStarted.__init__(self, "Loading cache Started") 613 self.total = total 614 615class CacheLoadProgress(OperationProgress): 616 """Cache loading progress""" 617 def __init__(self, current, total): 618 OperationProgress.__init__(self, current, total, "Loading cache") 619 620class CacheLoadCompleted(OperationCompleted): 621 """Cache loading is complete""" 622 def __init__(self, total, num_entries): 623 OperationCompleted.__init__(self, total, "Loading cache Completed") 624 self.num_entries = num_entries 625 626class TreeDataPreparationStarted(OperationStarted): 627 """Tree data preparation started""" 628 def __init__(self): 629 OperationStarted.__init__(self, "Preparing tree data Started") 630 631class TreeDataPreparationProgress(OperationProgress): 632 """Tree data preparation is in progress""" 633 def __init__(self, current, total): 634 OperationProgress.__init__(self, current, total, "Preparing tree data") 635 636class TreeDataPreparationCompleted(OperationCompleted): 637 """Tree data preparation completed""" 638 def __init__(self, total): 639 OperationCompleted.__init__(self, total, "Preparing tree data Completed") 640 641class DepTreeGenerated(Event): 642 """ 643 Event when a dependency tree has been generated 644 """ 645 646 def __init__(self, depgraph): 647 Event.__init__(self) 648 self._depgraph = depgraph 649 650class TargetsTreeGenerated(Event): 651 """ 652 Event when a set of buildable targets has been generated 653 """ 654 def __init__(self, model): 655 Event.__init__(self) 656 self._model = model 657 658class ReachableStamps(Event): 659 """ 660 An event listing all stamps reachable after parsing 661 which the metadata may use to clean up stale data 662 """ 663 664 def __init__(self, stamps): 665 Event.__init__(self) 666 self.stamps = stamps 667 668class FilesMatchingFound(Event): 669 """ 670 Event when a list of files matching the supplied pattern has 671 been generated 672 """ 673 def __init__(self, pattern, matches): 674 Event.__init__(self) 675 self._pattern = pattern 676 self._matches = matches 677 678class ConfigFilesFound(Event): 679 """ 680 Event when a list of appropriate config files has been generated 681 """ 682 def __init__(self, variable, values): 683 Event.__init__(self) 684 self._variable = variable 685 self._values = values 686 687class ConfigFilePathFound(Event): 688 """ 689 Event when a path for a config file has been found 690 """ 691 def __init__(self, path): 692 Event.__init__(self) 693 self._path = path 694 695class MsgBase(Event): 696 """Base class for messages""" 697 698 def __init__(self, msg): 699 self._message = msg 700 Event.__init__(self) 701 702class MsgDebug(MsgBase): 703 """Debug Message""" 704 705class MsgNote(MsgBase): 706 """Note Message""" 707 708class MsgWarn(MsgBase): 709 """Warning Message""" 710 711class MsgError(MsgBase): 712 """Error Message""" 713 714class MsgFatal(MsgBase): 715 """Fatal Message""" 716 717class MsgPlain(MsgBase): 718 """General output""" 719 720class LogExecTTY(Event): 721 """Send event containing program to spawn on tty of the logger""" 722 def __init__(self, msg, prog, sleep_delay, retries): 723 Event.__init__(self) 724 self.msg = msg 725 self.prog = prog 726 self.sleep_delay = sleep_delay 727 self.retries = retries 728 729class LogHandler(logging.Handler): 730 """Dispatch logging messages as bitbake events""" 731 732 def emit(self, record): 733 if record.exc_info: 734 etype, value, tb = record.exc_info 735 if hasattr(tb, 'tb_next'): 736 tb = list(bb.exceptions.extract_traceback(tb, context=3)) 737 # Need to turn the value into something the logging system can pickle 738 record.bb_exc_info = (etype, value, tb) 739 record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) 740 value = str(value) 741 record.exc_info = None 742 fire(record, None) 743 744 def filter(self, record): 745 record.taskpid = worker_pid 746 return True 747 748class MetadataEvent(Event): 749 """ 750 Generic event that target for OE-Core classes 751 to report information during asynchrous execution 752 """ 753 def __init__(self, eventtype, eventdata): 754 Event.__init__(self) 755 self.type = eventtype 756 self._localdata = eventdata 757 758class ProcessStarted(Event): 759 """ 760 Generic process started event (usually part of the initial startup) 761 where further progress events will be delivered 762 """ 763 def __init__(self, processname, total): 764 Event.__init__(self) 765 self.processname = processname 766 self.total = total 767 768class ProcessProgress(Event): 769 """ 770 Generic process progress event (usually part of the initial startup) 771 """ 772 def __init__(self, processname, progress): 773 Event.__init__(self) 774 self.processname = processname 775 self.progress = progress 776 777class ProcessFinished(Event): 778 """ 779 Generic process finished event (usually part of the initial startup) 780 """ 781 def __init__(self, processname): 782 Event.__init__(self) 783 self.processname = processname 784 785class SanityCheck(Event): 786 """ 787 Event to run sanity checks, either raise errors or generate events as return status. 788 """ 789 def __init__(self, generateevents = True): 790 Event.__init__(self) 791 self.generateevents = generateevents 792 793class SanityCheckPassed(Event): 794 """ 795 Event to indicate sanity check has passed 796 """ 797 798class SanityCheckFailed(Event): 799 """ 800 Event to indicate sanity check has failed 801 """ 802 def __init__(self, msg, network_error=False): 803 Event.__init__(self) 804 self._msg = msg 805 self._network_error = network_error 806 807class NetworkTest(Event): 808 """ 809 Event to run network connectivity tests, either raise errors or generate events as return status. 810 """ 811 def __init__(self, generateevents = True): 812 Event.__init__(self) 813 self.generateevents = generateevents 814 815class NetworkTestPassed(Event): 816 """ 817 Event to indicate network test has passed 818 """ 819 820class NetworkTestFailed(Event): 821 """ 822 Event to indicate network test has failed 823 """ 824 825class FindSigInfoResult(Event): 826 """ 827 Event to return results from findSigInfo command 828 """ 829 def __init__(self, result): 830 Event.__init__(self) 831 self.result = result 832