event.py, knotty.py, ncurses.py, runningbuild.py: Add support for LogExecTTY event
[bitbake.git] / lib / bb / event.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 BitBake 'Event' implementation
5
6 Classes and functions for manipulating 'events' in the
7 BitBake 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
25 import os, sys
26 import warnings
27 try:
28     import cPickle as pickle
29 except ImportError:
30     import pickle
31 import logging
32 import atexit
33 import traceback
34 import bb.utils
35 import bb.compat
36
37 # This is the pid for which we should generate the event. This is set when
38 # the runqueue forks off.
39 worker_pid = 0
40 worker_pipe = None
41
42 logger = logging.getLogger('BitBake.Event')
43
44 class Event(object):
45     """Base class for events"""
46
47     def __init__(self):
48         self.pid = worker_pid
49
50 NotHandled = 0
51 Handled    = 1
52
53 Registered        = 10
54 AlreadyRegistered = 14
55
56 # Internal
57 _handlers = bb.compat.OrderedDict()
58 _ui_handlers = {}
59 _ui_handler_seq = 0
60
61 # For compatibility
62 bb.utils._context["NotHandled"] = NotHandled
63 bb.utils._context["Handled"] = Handled
64
65 def execute_handler(name, handler, event, d):
66     event.data = d
67     try:
68         ret = handler(event)
69     except bb.parse.SkipPackage:
70         raise
71     except Exception:
72         etype, value, tb = sys.exc_info()
73         logger.error("Execution of event handler '%s' failed" % name,
74                         exc_info=(etype, value, tb.tb_next))
75         raise
76     except SystemExit as exc:
77         if exc.code != 0:
78             logger.error("Execution of event handler '%s' failed" % name)
79         raise
80     finally:
81         del event.data
82
83     if ret is not None:
84         warnings.warn("Using Handled/NotHandled in event handlers is deprecated",
85                         DeprecationWarning, stacklevel = 2)
86
87 def fire_class_handlers(event, d):
88     if isinstance(event, logging.LogRecord):
89         return
90
91     for name, handler in _handlers.iteritems():
92         try:
93             execute_handler(name, handler, event, d)
94         except Exception:
95             continue
96
97 ui_queue = []
98 @atexit.register
99 def print_ui_queue():
100     """If we're exiting before a UI has been spawned, display any queued
101     LogRecords to the console."""
102     logger = logging.getLogger("BitBake")
103     if not _ui_handlers:
104         from bb.msg import BBLogFormatter
105         console = logging.StreamHandler(sys.stdout)
106         console.setFormatter(BBLogFormatter("%(levelname)s: %(message)s"))
107         logger.handlers = [console]
108
109         # First check to see if we have any proper messages
110         msgprint = False
111         for event in ui_queue:
112             if isinstance(event, logging.LogRecord):
113                 if event.levelno > logging.DEBUG:
114                     logger.handle(event)
115                     msgprint = True
116         if msgprint:
117             return
118
119         # Nope, so just print all of the messages we have (including debug messages)
120         for event in ui_queue:
121             if isinstance(event, logging.LogRecord):
122                 logger.handle(event)
123
124 def fire_ui_handlers(event, d):
125     if not _ui_handlers:
126         # No UI handlers registered yet, queue up the messages
127         ui_queue.append(event)
128         return
129
130     errors = []
131     for h in _ui_handlers:
132         #print "Sending event %s" % event
133         try:
134              # We use pickle here since it better handles object instances
135              # which xmlrpc's marshaller does not. Events *must* be serializable
136              # by pickle.
137              if hasattr(_ui_handlers[h].event, "sendpickle"):
138                 _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
139              else:
140                 _ui_handlers[h].event.send(event)
141         except:
142             errors.append(h)
143     for h in errors:
144         del _ui_handlers[h]
145
146 def fire(event, d):
147     """Fire off an Event"""
148
149     # We can fire class handlers in the worker process context and this is
150     # desired so they get the task based datastore.
151     # UI handlers need to be fired in the server context so we defer this. They
152     # don't have a datastore so the datastore context isn't a problem.
153
154     fire_class_handlers(event, d)
155     if worker_pid != 0:
156         worker_fire(event, d)
157     else:
158         fire_ui_handlers(event, d)
159
160 def worker_fire(event, d):
161     data = "<event>" + pickle.dumps(event) + "</event>"
162     worker_pipe.write(data)
163
164 def fire_from_worker(event, d):
165     if not event.startswith("<event>") or not event.endswith("</event>"):
166         print("Error, not an event %s" % event)
167         return
168     event = pickle.loads(event[7:-8])
169     fire_ui_handlers(event, d)
170
171 noop = lambda _: None
172 def register(name, handler):
173     """Register an Event handler"""
174
175     # already registered
176     if name in _handlers:
177         return AlreadyRegistered
178
179     if handler is not None:
180         # handle string containing python code
181         if isinstance(handler, basestring):
182             tmp = "def %s(e):\n%s" % (name, handler)
183             try:
184                 code = compile(tmp, "%s(e)" % name, "exec")
185             except SyntaxError:
186                 logger.error("Unable to register event handler '%s':\n%s", name,
187                              ''.join(traceback.format_exc(limit=0)))
188                 _handlers[name] = noop
189                 return
190             env = {}
191             bb.utils.better_exec(code, env)
192             func = bb.utils.better_eval(name, env)
193             _handlers[name] = func
194         else:
195             _handlers[name] = handler
196
197         return Registered
198
199 def remove(name, handler):
200     """Remove an Event handler"""
201     _handlers.pop(name)
202
203 def register_UIHhandler(handler):
204     bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
205     _ui_handlers[_ui_handler_seq] = handler
206     return _ui_handler_seq
207
208 def unregister_UIHhandler(handlerNum):
209     if handlerNum in _ui_handlers:
210         del _ui_handlers[handlerNum]
211     return
212
213 def getName(e):
214     """Returns the name of a class or class instance"""
215     if getattr(e, "__name__", None) == None:
216         return e.__class__.__name__
217     else:
218         return e.__name__
219
220 class OperationStarted(Event):
221     """An operation has begun"""
222     def __init__(self, msg = "Operation Started"):
223         Event.__init__(self)
224         self.msg = msg
225
226 class OperationCompleted(Event):
227     """An operation has completed"""
228     def __init__(self, total, msg = "Operation Completed"):
229         Event.__init__(self)
230         self.total = total
231         self.msg = msg
232
233 class OperationProgress(Event):
234     """An operation is in progress"""
235     def __init__(self, current, total, msg = "Operation in Progress"):
236         Event.__init__(self)
237         self.current = current
238         self.total = total
239         self.msg = msg + ": %s/%s" % (current, total);
240
241 class ConfigParsed(Event):
242     """Configuration Parsing Complete"""
243
244 class RecipeEvent(Event):
245     def __init__(self, fn):
246         self.fn = fn
247         Event.__init__(self)
248
249 class RecipePreFinalise(RecipeEvent):
250     """ Recipe Parsing Complete but not yet finialised"""
251
252 class RecipeParsed(RecipeEvent):
253     """ Recipe Parsing Complete """
254
255 class StampUpdate(Event):
256     """Trigger for any adjustment of the stamp files to happen"""
257
258     def __init__(self, targets, stampfns):
259         self._targets = targets
260         self._stampfns = stampfns
261         Event.__init__(self)
262
263     def getStampPrefix(self):
264         return self._stampfns
265
266     def getTargets(self):
267         return self._targets
268
269     stampPrefix = property(getStampPrefix)
270     targets = property(getTargets)
271
272 class BuildBase(Event):
273     """Base class for bbmake run events"""
274
275     def __init__(self, n, p, failures = 0):
276         self._name = n
277         self._pkgs = p
278         Event.__init__(self)
279         self._failures = failures
280
281     def getPkgs(self):
282         return self._pkgs
283
284     def setPkgs(self, pkgs):
285         self._pkgs = pkgs
286
287     def getName(self):
288         return self._name
289
290     def setName(self, name):
291         self._name = name
292
293     def getCfg(self):
294         return self.data
295
296     def setCfg(self, cfg):
297         self.data = cfg
298
299     def getFailures(self):
300         """
301         Return the number of failed packages
302         """
303         return self._failures
304
305     pkgs = property(getPkgs, setPkgs, None, "pkgs property")
306     name = property(getName, setName, None, "name property")
307     cfg = property(getCfg, setCfg, None, "cfg property")
308
309
310
311
312
313 class BuildStarted(BuildBase, OperationStarted):
314     """bbmake build run started"""
315     def __init__(self, n, p, failures = 0):
316         OperationStarted.__init__(self, "Building Started")
317         BuildBase.__init__(self, n, p, failures)
318
319 class BuildCompleted(BuildBase, OperationCompleted):
320     """bbmake build run completed"""
321     def __init__(self, total, n, p, failures = 0):
322         if not failures:
323             OperationCompleted.__init__(self, total, "Building Succeeded")
324         else:
325             OperationCompleted.__init__(self, total, "Building Failed")
326         BuildBase.__init__(self, n, p, failures)
327
328 class DiskFull(Event):
329     """Disk full case build aborted"""
330     def __init__(self, dev, type, freespace, mountpoint):
331         Event.__init__(self)
332         self._dev = dev
333         self._type = type
334         self._free = freespace
335         self._mountpoint = mountpoint
336
337 class NoProvider(Event):
338     """No Provider for an Event"""
339
340     def __init__(self, item, runtime=False, dependees=None, reasons=[]):
341         Event.__init__(self)
342         self._item = item
343         self._runtime = runtime
344         self._dependees = dependees
345         self._reasons = reasons
346
347     def getItem(self):
348         return self._item
349
350     def isRuntime(self):
351         return self._runtime
352
353 class MultipleProviders(Event):
354     """Multiple Providers"""
355
356     def  __init__(self, item, candidates, runtime = False):
357         Event.__init__(self)
358         self._item = item
359         self._candidates = candidates
360         self._is_runtime = runtime
361
362     def isRuntime(self):
363         """
364         Is this a runtime issue?
365         """
366         return self._is_runtime
367
368     def getItem(self):
369         """
370         The name for the to be build item
371         """
372         return self._item
373
374     def getCandidates(self):
375         """
376         Get the possible Candidates for a PROVIDER.
377         """
378         return self._candidates
379
380 class ParseStarted(OperationStarted):
381     """Recipe parsing for the runqueue has begun"""
382     def __init__(self, total):
383         OperationStarted.__init__(self, "Recipe parsing Started")
384         self.total = total
385
386 class ParseCompleted(OperationCompleted):
387     """Recipe parsing for the runqueue has completed"""
388     def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
389         OperationCompleted.__init__(self, total, "Recipe parsing Completed")
390         self.cached = cached
391         self.parsed = parsed
392         self.skipped = skipped
393         self.virtuals = virtuals
394         self.masked = masked
395         self.errors = errors
396         self.sofar = cached + parsed
397
398 class ParseProgress(OperationProgress):
399     """Recipe parsing progress"""
400     def __init__(self, current, total):
401         OperationProgress.__init__(self, current, total, "Recipe parsing")
402
403
404 class CacheLoadStarted(OperationStarted):
405     """Loading of the dependency cache has begun"""
406     def __init__(self, total):
407         OperationStarted.__init__(self, "Loading cache Started")
408         self.total = total
409
410 class CacheLoadProgress(OperationProgress):
411     """Cache loading progress"""
412     def __init__(self, current, total):
413         OperationProgress.__init__(self, current, total, "Loading cache")
414
415 class CacheLoadCompleted(OperationCompleted):
416     """Cache loading is complete"""
417     def __init__(self, total, num_entries):
418         OperationCompleted.__init__(self, total, "Loading cache Completed")
419         self.num_entries = num_entries
420
421 class TreeDataPreparationStarted(OperationStarted):
422     """Tree data preparation started"""
423     def __init__(self):
424         OperationStarted.__init__(self, "Preparing tree data Started")
425
426 class TreeDataPreparationProgress(OperationProgress):
427     """Tree data preparation is in progress"""
428     def __init__(self, current, total):
429         OperationProgress.__init__(self, current, total, "Preparing tree data")
430
431 class TreeDataPreparationCompleted(OperationCompleted):
432     """Tree data preparation completed"""
433     def __init__(self, total):
434         OperationCompleted.__init__(self, total, "Preparing tree data Completed")
435
436 class DepTreeGenerated(Event):
437     """
438     Event when a dependency tree has been generated
439     """
440
441     def __init__(self, depgraph):
442         Event.__init__(self)
443         self._depgraph = depgraph
444
445 class TargetsTreeGenerated(Event):
446     """
447     Event when a set of buildable targets has been generated
448     """
449     def __init__(self, model):
450         Event.__init__(self)
451         self._model = model
452
453 class FilesMatchingFound(Event):
454     """
455     Event when a list of files matching the supplied pattern has
456     been generated
457     """
458     def __init__(self, pattern, matches):
459         Event.__init__(self)
460         self._pattern = pattern
461         self._matches = matches
462
463 class CoreBaseFilesFound(Event):
464     """
465     Event when a list of appropriate config files has been generated
466     """
467     def __init__(self, paths):
468         Event.__init__(self)
469         self._paths = paths
470
471 class ConfigFilesFound(Event):
472     """
473     Event when a list of appropriate config files has been generated
474     """
475     def __init__(self, variable, values):
476         Event.__init__(self)
477         self._variable = variable
478         self._values = values
479
480 class ConfigFilePathFound(Event):
481     """
482     Event when a path for a config file has been found
483     """
484     def __init__(self, path):
485         Event.__init__(self)
486         self._path = path
487
488 class MsgBase(Event):
489     """Base class for messages"""
490
491     def __init__(self, msg):
492         self._message = msg
493         Event.__init__(self)
494
495 class MsgDebug(MsgBase):
496     """Debug Message"""
497
498 class MsgNote(MsgBase):
499     """Note Message"""
500
501 class MsgWarn(MsgBase):
502     """Warning Message"""
503
504 class MsgError(MsgBase):
505     """Error Message"""
506
507 class MsgFatal(MsgBase):
508     """Fatal Message"""
509
510 class MsgPlain(MsgBase):
511     """General output"""
512
513 class LogExecTTY(Event):
514     """Send event containing program to spawn on tty of the logger"""
515     def __init__(self, msg, prog, sleep_delay, retries):
516         Event.__init__(self)
517         self.msg = msg
518         self.prog = prog
519         self.sleep_delay = sleep_delay
520         self.retries = retries
521
522 class LogHandler(logging.Handler):
523     """Dispatch logging messages as bitbake events"""
524
525     def emit(self, record):
526         if record.exc_info:
527             etype, value, tb = record.exc_info
528             if hasattr(tb, 'tb_next'):
529                 tb = list(bb.exceptions.extract_traceback(tb, context=3))
530             record.bb_exc_info = (etype, value, tb)
531             record.exc_info = None
532         fire(record, None)
533
534     def filter(self, record):
535         record.taskpid = worker_pid
536         return True
537
538 class RequestPackageInfo(Event):
539     """
540     Event to request package information
541     """
542
543 class PackageInfo(Event):
544     """
545     Package information for GUI
546     """
547     def __init__(self, pkginfolist):
548         Event.__init__(self)
549         self._pkginfolist = pkginfolist
550
551 class SanityCheck(Event):
552     """
553     Event to issue sanity check
554     """
555
556 class SanityCheckPassed(Event):
557     """
558     Event to indicate sanity check is passed
559     """
560
561 class SanityCheckFailed(Event):
562     """
563     Event to indicate sanity check has failed
564     """
565     def __init__(self, msg):
566         Event.__init__(self)
567         self._msg = msg