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