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 ast 14import atexit 15import collections 16import logging 17import pickle 18import sys 19import threading 20import traceback 21 22import bb.exceptions 23import bb.utils 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 collections.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 RecipePostKeyExpansion(RecipeEvent): 393 """ Recipe Parsing Complete but not yet finalised""" 394 395 396class RecipeTaskPreProcess(RecipeEvent): 397 """ 398 Recipe Tasks about to be finalised 399 The list of tasks should be final at this point and handlers 400 are only able to change interdependencies 401 """ 402 def __init__(self, fn, tasklist): 403 self.fn = fn 404 self.tasklist = tasklist 405 Event.__init__(self) 406 407class RecipeParsed(RecipeEvent): 408 """ Recipe Parsing Complete """ 409 410class BuildBase(Event): 411 """Base class for bitbake build events""" 412 413 def __init__(self, n, p, failures = 0): 414 self._name = n 415 self._pkgs = p 416 Event.__init__(self) 417 self._failures = failures 418 419 def getPkgs(self): 420 return self._pkgs 421 422 def setPkgs(self, pkgs): 423 self._pkgs = pkgs 424 425 def getName(self): 426 return self._name 427 428 def setName(self, name): 429 self._name = name 430 431 def getFailures(self): 432 """ 433 Return the number of failed packages 434 """ 435 return self._failures 436 437 pkgs = property(getPkgs, setPkgs, None, "pkgs property") 438 name = property(getName, setName, None, "name property") 439 440class BuildInit(BuildBase): 441 """buildFile or buildTargets was invoked""" 442 def __init__(self, p=[]): 443 name = None 444 BuildBase.__init__(self, name, p) 445 446class BuildStarted(BuildBase, OperationStarted): 447 """Event when builds start""" 448 def __init__(self, n, p, failures = 0): 449 OperationStarted.__init__(self, "Building Started") 450 BuildBase.__init__(self, n, p, failures) 451 452class BuildCompleted(BuildBase, OperationCompleted): 453 """Event when builds have completed""" 454 def __init__(self, total, n, p, failures=0, interrupted=0): 455 if not failures: 456 OperationCompleted.__init__(self, total, "Building Succeeded") 457 else: 458 OperationCompleted.__init__(self, total, "Building Failed") 459 self._interrupted = interrupted 460 BuildBase.__init__(self, n, p, failures) 461 462class DiskFull(Event): 463 """Disk full case build aborted""" 464 def __init__(self, dev, type, freespace, mountpoint): 465 Event.__init__(self) 466 self._dev = dev 467 self._type = type 468 self._free = freespace 469 self._mountpoint = mountpoint 470 471class DiskUsageSample: 472 def __init__(self, available_bytes, free_bytes, total_bytes): 473 # Number of bytes available to non-root processes. 474 self.available_bytes = available_bytes 475 # Number of bytes available to root processes. 476 self.free_bytes = free_bytes 477 # Total capacity of the volume. 478 self.total_bytes = total_bytes 479 480class MonitorDiskEvent(Event): 481 """If BB_DISKMON_DIRS is set, then this event gets triggered each time disk space is checked. 482 Provides information about devices that are getting monitored.""" 483 def __init__(self, disk_usage): 484 Event.__init__(self) 485 # hash of device root path -> DiskUsageSample 486 self.disk_usage = disk_usage 487 488class NoProvider(Event): 489 """No Provider for an Event""" 490 491 def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None): 492 Event.__init__(self) 493 self._item = item 494 self._runtime = runtime 495 self._dependees = dependees 496 self._reasons = reasons 497 self._close_matches = close_matches 498 499 def getItem(self): 500 return self._item 501 502 def isRuntime(self): 503 return self._runtime 504 505 def __str__(self): 506 msg = '' 507 if self._runtime: 508 r = "R" 509 else: 510 r = "" 511 512 extra = '' 513 if not self._reasons: 514 if self._close_matches: 515 extra = ". Close matches:\n %s" % '\n '.join(sorted(set(self._close_matches))) 516 517 if self._dependees: 518 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, self._item, ", ".join(self._dependees), r, extra) 519 else: 520 msg = "Nothing %sPROVIDES '%s'%s" % (r, self._item, extra) 521 if self._reasons: 522 for reason in self._reasons: 523 msg += '\n' + reason 524 return msg 525 526 527class MultipleProviders(Event): 528 """Multiple Providers""" 529 530 def __init__(self, item, candidates, runtime = False): 531 Event.__init__(self) 532 self._item = item 533 self._candidates = candidates 534 self._is_runtime = runtime 535 536 def isRuntime(self): 537 """ 538 Is this a runtime issue? 539 """ 540 return self._is_runtime 541 542 def getItem(self): 543 """ 544 The name for the to be build item 545 """ 546 return self._item 547 548 def getCandidates(self): 549 """ 550 Get the possible Candidates for a PROVIDER. 551 """ 552 return self._candidates 553 554 def __str__(self): 555 msg = "Multiple providers are available for %s%s (%s)" % (self._is_runtime and "runtime " or "", 556 self._item, 557 ", ".join(self._candidates)) 558 rtime = "" 559 if self._is_runtime: 560 rtime = "R" 561 msg += "\nConsider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, self._item) 562 return msg 563 564class ParseStarted(OperationStarted): 565 """Recipe parsing for the runqueue has begun""" 566 def __init__(self, total): 567 OperationStarted.__init__(self, "Recipe parsing Started") 568 self.total = total 569 570class ParseCompleted(OperationCompleted): 571 """Recipe parsing for the runqueue has completed""" 572 def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total): 573 OperationCompleted.__init__(self, total, "Recipe parsing Completed") 574 self.cached = cached 575 self.parsed = parsed 576 self.skipped = skipped 577 self.virtuals = virtuals 578 self.masked = masked 579 self.errors = errors 580 self.sofar = cached + parsed 581 582class ParseProgress(OperationProgress): 583 """Recipe parsing progress""" 584 def __init__(self, current, total): 585 OperationProgress.__init__(self, current, total, "Recipe parsing") 586 587 588class CacheLoadStarted(OperationStarted): 589 """Loading of the dependency cache has begun""" 590 def __init__(self, total): 591 OperationStarted.__init__(self, "Loading cache Started") 592 self.total = total 593 594class CacheLoadProgress(OperationProgress): 595 """Cache loading progress""" 596 def __init__(self, current, total): 597 OperationProgress.__init__(self, current, total, "Loading cache") 598 599class CacheLoadCompleted(OperationCompleted): 600 """Cache loading is complete""" 601 def __init__(self, total, num_entries): 602 OperationCompleted.__init__(self, total, "Loading cache Completed") 603 self.num_entries = num_entries 604 605class TreeDataPreparationStarted(OperationStarted): 606 """Tree data preparation started""" 607 def __init__(self): 608 OperationStarted.__init__(self, "Preparing tree data Started") 609 610class TreeDataPreparationProgress(OperationProgress): 611 """Tree data preparation is in progress""" 612 def __init__(self, current, total): 613 OperationProgress.__init__(self, current, total, "Preparing tree data") 614 615class TreeDataPreparationCompleted(OperationCompleted): 616 """Tree data preparation completed""" 617 def __init__(self, total): 618 OperationCompleted.__init__(self, total, "Preparing tree data Completed") 619 620class DepTreeGenerated(Event): 621 """ 622 Event when a dependency tree has been generated 623 """ 624 625 def __init__(self, depgraph): 626 Event.__init__(self) 627 self._depgraph = depgraph 628 629class TargetsTreeGenerated(Event): 630 """ 631 Event when a set of buildable targets has been generated 632 """ 633 def __init__(self, model): 634 Event.__init__(self) 635 self._model = model 636 637class ReachableStamps(Event): 638 """ 639 An event listing all stamps reachable after parsing 640 which the metadata may use to clean up stale data 641 """ 642 643 def __init__(self, stamps): 644 Event.__init__(self) 645 self.stamps = stamps 646 647class FilesMatchingFound(Event): 648 """ 649 Event when a list of files matching the supplied pattern has 650 been generated 651 """ 652 def __init__(self, pattern, matches): 653 Event.__init__(self) 654 self._pattern = pattern 655 self._matches = matches 656 657class ConfigFilesFound(Event): 658 """ 659 Event when a list of appropriate config files has been generated 660 """ 661 def __init__(self, variable, values): 662 Event.__init__(self) 663 self._variable = variable 664 self._values = values 665 666class ConfigFilePathFound(Event): 667 """ 668 Event when a path for a config file has been found 669 """ 670 def __init__(self, path): 671 Event.__init__(self) 672 self._path = path 673 674class MsgBase(Event): 675 """Base class for messages""" 676 677 def __init__(self, msg): 678 self._message = msg 679 Event.__init__(self) 680 681class MsgDebug(MsgBase): 682 """Debug Message""" 683 684class MsgNote(MsgBase): 685 """Note Message""" 686 687class MsgWarn(MsgBase): 688 """Warning Message""" 689 690class MsgError(MsgBase): 691 """Error Message""" 692 693class MsgFatal(MsgBase): 694 """Fatal Message""" 695 696class MsgPlain(MsgBase): 697 """General output""" 698 699class LogExecTTY(Event): 700 """Send event containing program to spawn on tty of the logger""" 701 def __init__(self, msg, prog, sleep_delay, retries): 702 Event.__init__(self) 703 self.msg = msg 704 self.prog = prog 705 self.sleep_delay = sleep_delay 706 self.retries = retries 707 708class LogHandler(logging.Handler): 709 """Dispatch logging messages as bitbake events""" 710 711 def emit(self, record): 712 if record.exc_info: 713 etype, value, tb = record.exc_info 714 if hasattr(tb, 'tb_next'): 715 tb = list(bb.exceptions.extract_traceback(tb, context=3)) 716 # Need to turn the value into something the logging system can pickle 717 record.bb_exc_info = (etype, value, tb) 718 record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) 719 value = str(value) 720 record.exc_info = None 721 fire(record, None) 722 723 def filter(self, record): 724 record.taskpid = worker_pid 725 return True 726 727class MetadataEvent(Event): 728 """ 729 Generic event that target for OE-Core classes 730 to report information during asynchrous execution 731 """ 732 def __init__(self, eventtype, eventdata): 733 Event.__init__(self) 734 self.type = eventtype 735 self._localdata = eventdata 736 737class ProcessStarted(Event): 738 """ 739 Generic process started event (usually part of the initial startup) 740 where further progress events will be delivered 741 """ 742 def __init__(self, processname, total): 743 Event.__init__(self) 744 self.processname = processname 745 self.total = total 746 747class ProcessProgress(Event): 748 """ 749 Generic process progress event (usually part of the initial startup) 750 """ 751 def __init__(self, processname, progress): 752 Event.__init__(self) 753 self.processname = processname 754 self.progress = progress 755 756class ProcessFinished(Event): 757 """ 758 Generic process finished event (usually part of the initial startup) 759 """ 760 def __init__(self, processname): 761 Event.__init__(self) 762 self.processname = processname 763 764class SanityCheck(Event): 765 """ 766 Event to run sanity checks, either raise errors or generate events as return status. 767 """ 768 def __init__(self, generateevents = True): 769 Event.__init__(self) 770 self.generateevents = generateevents 771 772class SanityCheckPassed(Event): 773 """ 774 Event to indicate sanity check has passed 775 """ 776 777class SanityCheckFailed(Event): 778 """ 779 Event to indicate sanity check has failed 780 """ 781 def __init__(self, msg, network_error=False): 782 Event.__init__(self) 783 self._msg = msg 784 self._network_error = network_error 785 786class NetworkTest(Event): 787 """ 788 Event to run network connectivity tests, either raise errors or generate events as return status. 789 """ 790 def __init__(self, generateevents = True): 791 Event.__init__(self) 792 self.generateevents = generateevents 793 794class NetworkTestPassed(Event): 795 """ 796 Event to indicate network test has passed 797 """ 798 799class NetworkTestFailed(Event): 800 """ 801 Event to indicate network test has failed 802 """ 803 804class FindSigInfoResult(Event): 805 """ 806 Event to return results from findSigInfo command 807 """ 808 def __init__(self, result): 809 Event.__init__(self) 810 self.result = result 811