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