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