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