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