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