xref: /openbmc/openbmc/poky/bitbake/lib/bb/event.py (revision 7f40b716)
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