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