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