blob: 5ffe89eae3eeb563732ed558a3ee73f78506f051 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# 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
27try:
28 import cPickle as pickle
29except ImportError:
30 import pickle
31import logging
32import atexit
33import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050034import ast
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035import bb.utils
36import bb.compat
37import bb.exceptions
38
39# This is the pid for which we should generate the event. This is set when
40# the runqueue forks off.
41worker_pid = 0
42worker_fire = None
43
44logger = logging.getLogger('BitBake.Event')
45
46class Event(object):
47 """Base class for events"""
48
49 def __init__(self):
50 self.pid = worker_pid
51
52Registered = 10
53AlreadyRegistered = 14
54
55def get_class_handlers():
56 return _handlers
57
58def set_class_handlers(h):
59 global _handlers
60 _handlers = h
61
62def clean_class_handlers():
63 return bb.compat.OrderedDict()
64
65# Internal
66_handlers = clean_class_handlers()
67_ui_handlers = {}
68_ui_logfilters = {}
69_ui_handler_seq = 0
70_event_handler_map = {}
71_catchall_handlers = {}
72_eventfilter = None
73_uiready = False
74
75def execute_handler(name, handler, event, d):
76 event.data = d
77 addedd = False
78 if 'd' not in __builtins__:
79 __builtins__['d'] = d
80 addedd = True
81 try:
82 ret = handler(event)
83 except (bb.parse.SkipRecipe, bb.BBHandledException):
84 raise
85 except Exception:
86 etype, value, tb = sys.exc_info()
87 logger.error("Execution of event handler '%s' failed" % name,
88 exc_info=(etype, value, tb.tb_next))
89 raise
90 except SystemExit as exc:
91 if exc.code != 0:
92 logger.error("Execution of event handler '%s' failed" % name)
93 raise
94 finally:
95 del event.data
96 if addedd:
97 del __builtins__['d']
98
99def fire_class_handlers(event, d):
100 if isinstance(event, logging.LogRecord):
101 return
102
103 eid = str(event.__class__)[8:-2]
104 evt_hmap = _event_handler_map.get(eid, {})
105 for name, handler in _handlers.iteritems():
106 if name in _catchall_handlers or name in evt_hmap:
107 if _eventfilter:
108 if not _eventfilter(name, handler, event, d):
109 continue
110 execute_handler(name, handler, event, d)
111
112ui_queue = []
113@atexit.register
114def print_ui_queue():
115 """If we're exiting before a UI has been spawned, display any queued
116 LogRecords to the console."""
117 logger = logging.getLogger("BitBake")
118 if not _uiready:
119 from bb.msg import BBLogFormatter
120 console = logging.StreamHandler(sys.stdout)
121 console.setFormatter(BBLogFormatter("%(levelname)s: %(message)s"))
122 logger.handlers = [console]
123
124 # First check to see if we have any proper messages
125 msgprint = False
126 for event in ui_queue:
127 if isinstance(event, logging.LogRecord):
128 if event.levelno > logging.DEBUG:
129 logger.handle(event)
130 msgprint = True
131 if msgprint:
132 return
133
134 # Nope, so just print all of the messages we have (including debug messages)
135 for event in ui_queue:
136 if isinstance(event, logging.LogRecord):
137 logger.handle(event)
138
139def fire_ui_handlers(event, d):
140 if not _uiready:
141 # No UI handlers registered yet, queue up the messages
142 ui_queue.append(event)
143 return
144
145 errors = []
146 for h in _ui_handlers:
147 #print "Sending event %s" % event
148 try:
149 if not _ui_logfilters[h].filter(event):
150 continue
151 # We use pickle here since it better handles object instances
152 # which xmlrpc's marshaller does not. Events *must* be serializable
153 # by pickle.
154 if hasattr(_ui_handlers[h].event, "sendpickle"):
155 _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
156 else:
157 _ui_handlers[h].event.send(event)
158 except:
159 errors.append(h)
160 for h in errors:
161 del _ui_handlers[h]
162
163def fire(event, d):
164 """Fire off an Event"""
165
166 # We can fire class handlers in the worker process context and this is
167 # desired so they get the task based datastore.
168 # UI handlers need to be fired in the server context so we defer this. They
169 # don't have a datastore so the datastore context isn't a problem.
170
171 fire_class_handlers(event, d)
172 if worker_fire:
173 worker_fire(event, d)
174 else:
175 fire_ui_handlers(event, d)
176
177def fire_from_worker(event, d):
178 fire_ui_handlers(event, d)
179
180noop = lambda _: None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500181def register(name, handler, mask=None, filename=None, lineno=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182 """Register an Event handler"""
183
184 # already registered
185 if name in _handlers:
186 return AlreadyRegistered
187
188 if handler is not None:
189 # handle string containing python code
190 if isinstance(handler, basestring):
191 tmp = "def %s(e):\n%s" % (name, handler)
192 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500193 code = bb.methodpool.compile_cache(tmp)
194 if not code:
195 if filename is None:
196 filename = "%s(e)" % name
197 code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
198 if lineno is not None:
199 ast.increment_lineno(code, lineno-1)
200 code = compile(code, filename, "exec")
201 bb.methodpool.compile_cache_add(tmp, code)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500202 except SyntaxError:
203 logger.error("Unable to register event handler '%s':\n%s", name,
204 ''.join(traceback.format_exc(limit=0)))
205 _handlers[name] = noop
206 return
207 env = {}
208 bb.utils.better_exec(code, env)
209 func = bb.utils.better_eval(name, env)
210 _handlers[name] = func
211 else:
212 _handlers[name] = handler
213
214 if not mask or '*' in mask:
215 _catchall_handlers[name] = True
216 else:
217 for m in mask:
218 if _event_handler_map.get(m, None) is None:
219 _event_handler_map[m] = {}
220 _event_handler_map[m][name] = True
221
222 return Registered
223
224def remove(name, handler):
225 """Remove an Event handler"""
226 _handlers.pop(name)
227
228def set_eventfilter(func):
229 global _eventfilter
230 _eventfilter = func
231
232def register_UIHhandler(handler, mainui=False):
233 if mainui:
234 global _uiready
235 _uiready = True
236 bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
237 _ui_handlers[_ui_handler_seq] = handler
238 level, debug_domains = bb.msg.constructLogOptions()
239 _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
240 return _ui_handler_seq
241
242def unregister_UIHhandler(handlerNum):
243 if handlerNum in _ui_handlers:
244 del _ui_handlers[handlerNum]
245 return
246
247# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC
248class UIEventFilter(object):
249 def __init__(self, level, debug_domains):
250 self.update(None, level, debug_domains)
251
252 def update(self, eventmask, level, debug_domains):
253 self.eventmask = eventmask
254 self.stdlevel = level
255 self.debug_domains = debug_domains
256
257 def filter(self, event):
258 if isinstance(event, logging.LogRecord):
259 if event.levelno >= self.stdlevel:
260 return True
261 if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]:
262 return True
263 return False
264 eid = str(event.__class__)[8:-2]
265 if self.eventmask and eid not in self.eventmask:
266 return False
267 return True
268
269def set_UIHmask(handlerNum, level, debug_domains, mask):
270 if not handlerNum in _ui_handlers:
271 return False
272 if '*' in mask:
273 _ui_logfilters[handlerNum].update(None, level, debug_domains)
274 else:
275 _ui_logfilters[handlerNum].update(mask, level, debug_domains)
276 return True
277
278def getName(e):
279 """Returns the name of a class or class instance"""
280 if getattr(e, "__name__", None) == None:
281 return e.__class__.__name__
282 else:
283 return e.__name__
284
285class OperationStarted(Event):
286 """An operation has begun"""
287 def __init__(self, msg = "Operation Started"):
288 Event.__init__(self)
289 self.msg = msg
290
291class OperationCompleted(Event):
292 """An operation has completed"""
293 def __init__(self, total, msg = "Operation Completed"):
294 Event.__init__(self)
295 self.total = total
296 self.msg = msg
297
298class OperationProgress(Event):
299 """An operation is in progress"""
300 def __init__(self, current, total, msg = "Operation in Progress"):
301 Event.__init__(self)
302 self.current = current
303 self.total = total
304 self.msg = msg + ": %s/%s" % (current, total);
305
306class ConfigParsed(Event):
307 """Configuration Parsing Complete"""
308
309class RecipeEvent(Event):
310 def __init__(self, fn):
311 self.fn = fn
312 Event.__init__(self)
313
314class RecipePreFinalise(RecipeEvent):
315 """ Recipe Parsing Complete but not yet finialised"""
316
317class RecipeParsed(RecipeEvent):
318 """ Recipe Parsing Complete """
319
320class StampUpdate(Event):
321 """Trigger for any adjustment of the stamp files to happen"""
322
323 def __init__(self, targets, stampfns):
324 self._targets = targets
325 self._stampfns = stampfns
326 Event.__init__(self)
327
328 def getStampPrefix(self):
329 return self._stampfns
330
331 def getTargets(self):
332 return self._targets
333
334 stampPrefix = property(getStampPrefix)
335 targets = property(getTargets)
336
337class BuildBase(Event):
338 """Base class for bbmake run events"""
339
340 def __init__(self, n, p, failures = 0):
341 self._name = n
342 self._pkgs = p
343 Event.__init__(self)
344 self._failures = failures
345
346 def getPkgs(self):
347 return self._pkgs
348
349 def setPkgs(self, pkgs):
350 self._pkgs = pkgs
351
352 def getName(self):
353 return self._name
354
355 def setName(self, name):
356 self._name = name
357
358 def getCfg(self):
359 return self.data
360
361 def setCfg(self, cfg):
362 self.data = cfg
363
364 def getFailures(self):
365 """
366 Return the number of failed packages
367 """
368 return self._failures
369
370 pkgs = property(getPkgs, setPkgs, None, "pkgs property")
371 name = property(getName, setName, None, "name property")
372 cfg = property(getCfg, setCfg, None, "cfg property")
373
374
375
376
377
378class BuildStarted(BuildBase, OperationStarted):
379 """bbmake build run started"""
380 def __init__(self, n, p, failures = 0):
381 OperationStarted.__init__(self, "Building Started")
382 BuildBase.__init__(self, n, p, failures)
383
384class BuildCompleted(BuildBase, OperationCompleted):
385 """bbmake build run completed"""
386 def __init__(self, total, n, p, failures=0, interrupted=0):
387 if not failures:
388 OperationCompleted.__init__(self, total, "Building Succeeded")
389 else:
390 OperationCompleted.__init__(self, total, "Building Failed")
391 self._interrupted = interrupted
392 BuildBase.__init__(self, n, p, failures)
393
394class DiskFull(Event):
395 """Disk full case build aborted"""
396 def __init__(self, dev, type, freespace, mountpoint):
397 Event.__init__(self)
398 self._dev = dev
399 self._type = type
400 self._free = freespace
401 self._mountpoint = mountpoint
402
403class NoProvider(Event):
404 """No Provider for an Event"""
405
406 def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None):
407 Event.__init__(self)
408 self._item = item
409 self._runtime = runtime
410 self._dependees = dependees
411 self._reasons = reasons
412 self._close_matches = close_matches
413
414 def getItem(self):
415 return self._item
416
417 def isRuntime(self):
418 return self._runtime
419
420class MultipleProviders(Event):
421 """Multiple Providers"""
422
423 def __init__(self, item, candidates, runtime = False):
424 Event.__init__(self)
425 self._item = item
426 self._candidates = candidates
427 self._is_runtime = runtime
428
429 def isRuntime(self):
430 """
431 Is this a runtime issue?
432 """
433 return self._is_runtime
434
435 def getItem(self):
436 """
437 The name for the to be build item
438 """
439 return self._item
440
441 def getCandidates(self):
442 """
443 Get the possible Candidates for a PROVIDER.
444 """
445 return self._candidates
446
447class ParseStarted(OperationStarted):
448 """Recipe parsing for the runqueue has begun"""
449 def __init__(self, total):
450 OperationStarted.__init__(self, "Recipe parsing Started")
451 self.total = total
452
453class ParseCompleted(OperationCompleted):
454 """Recipe parsing for the runqueue has completed"""
455 def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
456 OperationCompleted.__init__(self, total, "Recipe parsing Completed")
457 self.cached = cached
458 self.parsed = parsed
459 self.skipped = skipped
460 self.virtuals = virtuals
461 self.masked = masked
462 self.errors = errors
463 self.sofar = cached + parsed
464
465class ParseProgress(OperationProgress):
466 """Recipe parsing progress"""
467 def __init__(self, current, total):
468 OperationProgress.__init__(self, current, total, "Recipe parsing")
469
470
471class CacheLoadStarted(OperationStarted):
472 """Loading of the dependency cache has begun"""
473 def __init__(self, total):
474 OperationStarted.__init__(self, "Loading cache Started")
475 self.total = total
476
477class CacheLoadProgress(OperationProgress):
478 """Cache loading progress"""
479 def __init__(self, current, total):
480 OperationProgress.__init__(self, current, total, "Loading cache")
481
482class CacheLoadCompleted(OperationCompleted):
483 """Cache loading is complete"""
484 def __init__(self, total, num_entries):
485 OperationCompleted.__init__(self, total, "Loading cache Completed")
486 self.num_entries = num_entries
487
488class TreeDataPreparationStarted(OperationStarted):
489 """Tree data preparation started"""
490 def __init__(self):
491 OperationStarted.__init__(self, "Preparing tree data Started")
492
493class TreeDataPreparationProgress(OperationProgress):
494 """Tree data preparation is in progress"""
495 def __init__(self, current, total):
496 OperationProgress.__init__(self, current, total, "Preparing tree data")
497
498class TreeDataPreparationCompleted(OperationCompleted):
499 """Tree data preparation completed"""
500 def __init__(self, total):
501 OperationCompleted.__init__(self, total, "Preparing tree data Completed")
502
503class DepTreeGenerated(Event):
504 """
505 Event when a dependency tree has been generated
506 """
507
508 def __init__(self, depgraph):
509 Event.__init__(self)
510 self._depgraph = depgraph
511
512class TargetsTreeGenerated(Event):
513 """
514 Event when a set of buildable targets has been generated
515 """
516 def __init__(self, model):
517 Event.__init__(self)
518 self._model = model
519
520class ReachableStamps(Event):
521 """
522 An event listing all stamps reachable after parsing
523 which the metadata may use to clean up stale data
524 """
525
526 def __init__(self, stamps):
527 Event.__init__(self)
528 self.stamps = stamps
529
530class FilesMatchingFound(Event):
531 """
532 Event when a list of files matching the supplied pattern has
533 been generated
534 """
535 def __init__(self, pattern, matches):
536 Event.__init__(self)
537 self._pattern = pattern
538 self._matches = matches
539
540class CoreBaseFilesFound(Event):
541 """
542 Event when a list of appropriate config files has been generated
543 """
544 def __init__(self, paths):
545 Event.__init__(self)
546 self._paths = paths
547
548class ConfigFilesFound(Event):
549 """
550 Event when a list of appropriate config files has been generated
551 """
552 def __init__(self, variable, values):
553 Event.__init__(self)
554 self._variable = variable
555 self._values = values
556
557class ConfigFilePathFound(Event):
558 """
559 Event when a path for a config file has been found
560 """
561 def __init__(self, path):
562 Event.__init__(self)
563 self._path = path
564
565class MsgBase(Event):
566 """Base class for messages"""
567
568 def __init__(self, msg):
569 self._message = msg
570 Event.__init__(self)
571
572class MsgDebug(MsgBase):
573 """Debug Message"""
574
575class MsgNote(MsgBase):
576 """Note Message"""
577
578class MsgWarn(MsgBase):
579 """Warning Message"""
580
581class MsgError(MsgBase):
582 """Error Message"""
583
584class MsgFatal(MsgBase):
585 """Fatal Message"""
586
587class MsgPlain(MsgBase):
588 """General output"""
589
590class LogExecTTY(Event):
591 """Send event containing program to spawn on tty of the logger"""
592 def __init__(self, msg, prog, sleep_delay, retries):
593 Event.__init__(self)
594 self.msg = msg
595 self.prog = prog
596 self.sleep_delay = sleep_delay
597 self.retries = retries
598
599class LogHandler(logging.Handler):
600 """Dispatch logging messages as bitbake events"""
601
602 def emit(self, record):
603 if record.exc_info:
604 etype, value, tb = record.exc_info
605 if hasattr(tb, 'tb_next'):
606 tb = list(bb.exceptions.extract_traceback(tb, context=3))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500607 # Need to turn the value into something the logging system can pickle
608 value = str(value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500609 record.bb_exc_info = (etype, value, tb)
610 record.exc_info = None
611 fire(record, None)
612
613 def filter(self, record):
614 record.taskpid = worker_pid
615 return True
616
617class RequestPackageInfo(Event):
618 """
619 Event to request package information
620 """
621
622class PackageInfo(Event):
623 """
624 Package information for GUI
625 """
626 def __init__(self, pkginfolist):
627 Event.__init__(self)
628 self._pkginfolist = pkginfolist
629
630class MetadataEvent(Event):
631 """
632 Generic event that target for OE-Core classes
633 to report information during asynchrous execution
634 """
635 def __init__(self, eventtype, eventdata):
636 Event.__init__(self)
637 self.type = eventtype
638 self._localdata = eventdata
639
640class SanityCheck(Event):
641 """
642 Event to run sanity checks, either raise errors or generate events as return status.
643 """
644 def __init__(self, generateevents = True):
645 Event.__init__(self)
646 self.generateevents = generateevents
647
648class SanityCheckPassed(Event):
649 """
650 Event to indicate sanity check has passed
651 """
652
653class SanityCheckFailed(Event):
654 """
655 Event to indicate sanity check has failed
656 """
657 def __init__(self, msg, network_error=False):
658 Event.__init__(self)
659 self._msg = msg
660 self._network_error = network_error
661
662class NetworkTest(Event):
663 """
664 Event to run network connectivity tests, either raise errors or generate events as return status.
665 """
666 def __init__(self, generateevents = True):
667 Event.__init__(self)
668 self.generateevents = generateevents
669
670class NetworkTestPassed(Event):
671 """
672 Event to indicate network test has passed
673 """
674
675class NetworkTestFailed(Event):
676 """
677 Event to indicate network test has failed
678 """
679