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