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