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