summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/tinfoil.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/tinfoil.py')
-rw-r--r--bitbake/lib/bb/tinfoil.py215
1 files changed, 195 insertions, 20 deletions
diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py
index 763c329810..e7fbcbca0a 100644
--- a/bitbake/lib/bb/tinfoil.py
+++ b/bitbake/lib/bb/tinfoil.py
@@ -10,10 +10,12 @@
10import logging 10import logging
11import os 11import os
12import sys 12import sys
13import time
13import atexit 14import atexit
14import re 15import re
15from collections import OrderedDict, defaultdict 16from collections import OrderedDict, defaultdict
16from functools import partial 17from functools import partial, wraps
18from contextlib import contextmanager
17 19
18import bb.cache 20import bb.cache
19import bb.cooker 21import bb.cooker
@@ -25,6 +27,135 @@ import bb.remotedata
25from bb.main import setup_bitbake, BitBakeConfigParameters 27from bb.main import setup_bitbake, BitBakeConfigParameters
26import bb.fetch2 28import bb.fetch2
27 29
30def wait_for(f):
31 """
32 Wrap a function that makes an asynchronous tinfoil call using
33 self.run_command() and wait for events to say that the call has been
34 successful, or an error has occurred.
35 """
36 @wraps(f)
37 def wrapper(self, *args, handle_events=True, extra_events=None, event_callback=None, **kwargs):
38 if handle_events:
39 # A reasonable set of default events matching up with those we handle below
40 eventmask = [
41 'bb.event.BuildStarted',
42 'bb.event.BuildCompleted',
43 'logging.LogRecord',
44 'bb.event.NoProvider',
45 'bb.command.CommandCompleted',
46 'bb.command.CommandFailed',
47 'bb.build.TaskStarted',
48 'bb.build.TaskFailed',
49 'bb.build.TaskSucceeded',
50 'bb.build.TaskFailedSilent',
51 'bb.build.TaskProgress',
52 'bb.runqueue.runQueueTaskStarted',
53 'bb.runqueue.sceneQueueTaskStarted',
54 'bb.event.ProcessStarted',
55 'bb.event.ProcessProgress',
56 'bb.event.ProcessFinished',
57 ]
58 if extra_events:
59 eventmask.extend(extra_events)
60 ret = self.set_event_mask(eventmask)
61
62 includelogs = self.config_data.getVar('BBINCLUDELOGS')
63 loglines = self.config_data.getVar('BBINCLUDELOGS_LINES')
64
65 # Call actual function
66 ret = f(self, *args, **kwargs)
67
68 if handle_events:
69 lastevent = time.time()
70 result = False
71 # Borrowed from knotty, instead somewhat hackily we use the helper
72 # as the object to store "shutdown" on
73 helper = bb.ui.uihelper.BBUIHelper()
74 helper.shutdown = 0
75 parseprogress = None
76 termfilter = bb.ui.knotty.TerminalFilter(helper, helper, self.logger.handlers, quiet=self.quiet)
77 try:
78 while True:
79 try:
80 event = self.wait_event(0.25)
81 if event:
82 lastevent = time.time()
83 if event_callback and event_callback(event):
84 continue
85 if helper.eventHandler(event):
86 if isinstance(event, bb.build.TaskFailedSilent):
87 self.logger.warning("Logfile for failed setscene task is %s" % event.logfile)
88 elif isinstance(event, bb.build.TaskFailed):
89 bb.ui.knotty.print_event_log(event, includelogs, loglines, termfilter)
90 continue
91 if isinstance(event, bb.event.ProcessStarted):
92 if self.quiet > 1:
93 continue
94 parseprogress = bb.ui.knotty.new_progress(event.processname, event.total)
95 parseprogress.start(False)
96 continue
97 if isinstance(event, bb.event.ProcessProgress):
98 if self.quiet > 1:
99 continue
100 if parseprogress:
101 parseprogress.update(event.progress)
102 else:
103 bb.warn("Got ProcessProgress event for something that never started?")
104 continue
105 if isinstance(event, bb.event.ProcessFinished):
106 if self.quiet > 1:
107 continue
108 if parseprogress:
109 parseprogress.finish()
110 parseprogress = None
111 continue
112 if isinstance(event, bb.command.CommandCompleted):
113 result = True
114 break
115 if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit)):
116 self.logger.error(str(event))
117 result = False
118 break
119 if isinstance(event, logging.LogRecord):
120 if event.taskpid == 0 or event.levelno > logging.INFO:
121 self.logger.handle(event)
122 continue
123 if isinstance(event, bb.event.NoProvider):
124 self.logger.error(str(event))
125 result = False
126 break
127 elif helper.shutdown > 1:
128 break
129 termfilter.updateFooter()
130 if time.time() > (lastevent + (3*60)):
131 if not self.run_command('ping', handle_events=False):
132 print("\nUnable to ping server and no events, closing down...\n")
133 return False
134 except KeyboardInterrupt:
135 termfilter.clearFooter()
136 if helper.shutdown == 1:
137 print("\nSecond Keyboard Interrupt, stopping...\n")
138 ret = self.run_command("stateForceShutdown")
139 if ret and ret[2]:
140 self.logger.error("Unable to cleanly stop: %s" % ret[2])
141 elif helper.shutdown == 0:
142 print("\nKeyboard Interrupt, closing down...\n")
143 interrupted = True
144 ret = self.run_command("stateShutdown")
145 if ret and ret[2]:
146 self.logger.error("Unable to cleanly shutdown: %s" % ret[2])
147 helper.shutdown = helper.shutdown + 1
148 termfilter.clearFooter()
149 finally:
150 termfilter.finish()
151 if helper.failed_tasks:
152 result = False
153 return result
154 else:
155 return ret
156
157 return wrapper
158
28 159
29# We need this in order to shut down the connection to the bitbake server, 160# We need this in order to shut down the connection to the bitbake server,
30# otherwise the process will never properly exit 161# otherwise the process will never properly exit
@@ -52,6 +183,10 @@ class TinfoilDataStoreConnectorVarHistory:
52 def remoteCommand(self, cmd, *args, **kwargs): 183 def remoteCommand(self, cmd, *args, **kwargs):
53 return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs) 184 return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs)
54 185
186 def emit(self, var, oval, val, o, d):
187 ret = self.tinfoil.run_command('dataStoreConnectorVarHistCmdEmit', self.dsindex, var, oval, val, d.dsindex)
188 o.write(ret)
189
55 def __getattr__(self, name): 190 def __getattr__(self, name):
56 if not hasattr(bb.data_smart.VariableHistory, name): 191 if not hasattr(bb.data_smart.VariableHistory, name):
57 raise AttributeError("VariableHistory has no such method %s" % name) 192 raise AttributeError("VariableHistory has no such method %s" % name)
@@ -183,11 +318,19 @@ class TinfoilCookerAdapter:
183 self._cache[name] = attrvalue 318 self._cache[name] = attrvalue
184 return attrvalue 319 return attrvalue
185 320
321 class TinfoilSkiplistByMcAdapter:
322 def __init__(self, tinfoil):
323 self.tinfoil = tinfoil
324
325 def __getitem__(self, mc):
326 return self.tinfoil.get_skipped_recipes(mc)
327
186 def __init__(self, tinfoil): 328 def __init__(self, tinfoil):
187 self.tinfoil = tinfoil 329 self.tinfoil = tinfoil
188 self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split() 330 self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split()
189 self.collections = {} 331 self.collections = {}
190 self.recipecaches = {} 332 self.recipecaches = {}
333 self.skiplist_by_mc = self.TinfoilSkiplistByMcAdapter(tinfoil)
191 for mc in self.multiconfigs: 334 for mc in self.multiconfigs:
192 self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc) 335 self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc)
193 self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc) 336 self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc)
@@ -196,8 +339,6 @@ class TinfoilCookerAdapter:
196 # Grab these only when they are requested since they aren't always used 339 # Grab these only when they are requested since they aren't always used
197 if name in self._cache: 340 if name in self._cache:
198 return self._cache[name] 341 return self._cache[name]
199 elif name == 'skiplist':
200 attrvalue = self.tinfoil.get_skipped_recipes()
201 elif name == 'bbfile_config_priorities': 342 elif name == 'bbfile_config_priorities':
202 ret = self.tinfoil.run_command('getLayerPriorities') 343 ret = self.tinfoil.run_command('getLayerPriorities')
203 bbfile_config_priorities = [] 344 bbfile_config_priorities = []
@@ -320,11 +461,11 @@ class Tinfoil:
320 self.recipes_parsed = False 461 self.recipes_parsed = False
321 self.quiet = 0 462 self.quiet = 0
322 self.oldhandlers = self.logger.handlers[:] 463 self.oldhandlers = self.logger.handlers[:]
464 self.localhandlers = []
323 if setup_logging: 465 if setup_logging:
324 # This is the *client-side* logger, nothing to do with 466 # This is the *client-side* logger, nothing to do with
325 # logging messages from the server 467 # logging messages from the server
326 bb.msg.logger_create('BitBake', output) 468 bb.msg.logger_create('BitBake', output)
327 self.localhandlers = []
328 for handler in self.logger.handlers: 469 for handler in self.logger.handlers:
329 if handler not in self.oldhandlers: 470 if handler not in self.oldhandlers:
330 self.localhandlers.append(handler) 471 self.localhandlers.append(handler)
@@ -440,11 +581,17 @@ class Tinfoil:
440 to initialise Tinfoil and use it with config_only=True first and 581 to initialise Tinfoil and use it with config_only=True first and
441 then conditionally call this function to parse recipes later. 582 then conditionally call this function to parse recipes later.
442 """ 583 """
443 config_params = TinfoilConfigParameters(config_only=False) 584 config_params = TinfoilConfigParameters(config_only=False, quiet=self.quiet)
444 self.run_actions(config_params) 585 self.run_actions(config_params)
445 self.recipes_parsed = True 586 self.recipes_parsed = True
446 587
447 def run_command(self, command, *params): 588 def modified_files(self):
589 """
590 Notify the server it needs to revalidate it's caches since the client has modified files
591 """
592 self.run_command("revalidateCaches")
593
594 def run_command(self, command, *params, handle_events=True):
448 """ 595 """
449 Run a command on the server (as implemented in bb.command). 596 Run a command on the server (as implemented in bb.command).
450 Note that there are two types of command - synchronous and 597 Note that there are two types of command - synchronous and
@@ -464,7 +611,7 @@ class Tinfoil:
464 try: 611 try:
465 result = self.server_connection.connection.runCommand(commandline) 612 result = self.server_connection.connection.runCommand(commandline)
466 finally: 613 finally:
467 while True: 614 while handle_events:
468 event = self.wait_event() 615 event = self.wait_event()
469 if not event: 616 if not event:
470 break 617 break
@@ -489,7 +636,7 @@ class Tinfoil:
489 Wait for an event from the server for the specified time. 636 Wait for an event from the server for the specified time.
490 A timeout of 0 means don't wait if there are no events in the queue. 637 A timeout of 0 means don't wait if there are no events in the queue.
491 Returns the next event in the queue or None if the timeout was 638 Returns the next event in the queue or None if the timeout was
492 reached. Note that in order to recieve any events you will 639 reached. Note that in order to receive any events you will
493 first need to set the internal event mask using set_event_mask() 640 first need to set the internal event mask using set_event_mask()
494 (otherwise whatever event mask the UI set up will be in effect). 641 (otherwise whatever event mask the UI set up will be in effect).
495 """ 642 """
@@ -503,12 +650,12 @@ class Tinfoil:
503 """ 650 """
504 return defaultdict(list, self.run_command('getOverlayedRecipes', mc)) 651 return defaultdict(list, self.run_command('getOverlayedRecipes', mc))
505 652
506 def get_skipped_recipes(self): 653 def get_skipped_recipes(self, mc=''):
507 """ 654 """
508 Find recipes which were skipped (i.e. SkipRecipe was raised 655 Find recipes which were skipped (i.e. SkipRecipe was raised
509 during parsing). 656 during parsing).
510 """ 657 """
511 return OrderedDict(self.run_command('getSkippedRecipes')) 658 return OrderedDict(self.run_command('getSkippedRecipes', mc))
512 659
513 def get_all_providers(self, mc=''): 660 def get_all_providers(self, mc=''):
514 return defaultdict(list, self.run_command('allProviders', mc)) 661 return defaultdict(list, self.run_command('allProviders', mc))
@@ -522,6 +669,7 @@ class Tinfoil:
522 def get_runtime_providers(self, rdep): 669 def get_runtime_providers(self, rdep):
523 return self.run_command('getRuntimeProviders', rdep) 670 return self.run_command('getRuntimeProviders', rdep)
524 671
672 # TODO: teach this method about mc
525 def get_recipe_file(self, pn): 673 def get_recipe_file(self, pn):
526 """ 674 """
527 Get the file name for the specified recipe/target. Raises 675 Get the file name for the specified recipe/target. Raises
@@ -530,6 +678,7 @@ class Tinfoil:
530 """ 678 """
531 best = self.find_best_provider(pn) 679 best = self.find_best_provider(pn)
532 if not best or (len(best) > 3 and not best[3]): 680 if not best or (len(best) > 3 and not best[3]):
681 # TODO: pass down mc
533 skiplist = self.get_skipped_recipes() 682 skiplist = self.get_skipped_recipes()
534 taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) 683 taskdata = bb.taskdata.TaskData(None, skiplist=skiplist)
535 skipreasons = taskdata.get_reasons(pn) 684 skipreasons = taskdata.get_reasons(pn)
@@ -622,6 +771,29 @@ class Tinfoil:
622 fn = self.get_recipe_file(pn) 771 fn = self.get_recipe_file(pn)
623 return self.parse_recipe_file(fn) 772 return self.parse_recipe_file(fn)
624 773
774 @contextmanager
775 def _data_tracked_if_enabled(self):
776 """
777 A context manager to enable data tracking for a code segment if data
778 tracking was enabled for this tinfoil instance.
779 """
780 if self.tracking:
781 # Enable history tracking just for the operation
782 self.run_command('enableDataTracking')
783
784 # Here goes the operation with the optional data tracking
785 yield
786
787 if self.tracking:
788 self.run_command('disableDataTracking')
789
790 def finalizeData(self):
791 """
792 Run anonymous functions and expand keys
793 """
794 with self._data_tracked_if_enabled():
795 return self._reconvert_type(self.run_command('finalizeData'), 'DataStoreConnectionHandle')
796
625 def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): 797 def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None):
626 """ 798 """
627 Parse the specified recipe file (with or without bbappends) 799 Parse the specified recipe file (with or without bbappends)
@@ -634,10 +806,7 @@ class Tinfoil:
634 appendlist: optional list of bbappend files to apply, if you 806 appendlist: optional list of bbappend files to apply, if you
635 want to filter them 807 want to filter them
636 """ 808 """
637 if self.tracking: 809 with self._data_tracked_if_enabled():
638 # Enable history tracking just for the parse operation
639 self.run_command('enableDataTracking')
640 try:
641 if appends and appendlist == []: 810 if appends and appendlist == []:
642 appends = False 811 appends = False
643 if config_data: 812 if config_data:
@@ -649,9 +818,6 @@ class Tinfoil:
649 return self._reconvert_type(dscon, 'DataStoreConnectionHandle') 818 return self._reconvert_type(dscon, 'DataStoreConnectionHandle')
650 else: 819 else:
651 return None 820 return None
652 finally:
653 if self.tracking:
654 self.run_command('disableDataTracking')
655 821
656 def build_file(self, buildfile, task, internal=True): 822 def build_file(self, buildfile, task, internal=True):
657 """ 823 """
@@ -663,6 +829,10 @@ class Tinfoil:
663 """ 829 """
664 return self.run_command('buildFile', buildfile, task, internal) 830 return self.run_command('buildFile', buildfile, task, internal)
665 831
832 @wait_for
833 def build_file_sync(self, *args):
834 self.build_file(*args)
835
666 def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None): 836 def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None):
667 """ 837 """
668 Builds the specified targets. This is equivalent to a normal invocation 838 Builds the specified targets. This is equivalent to a normal invocation
@@ -725,6 +895,7 @@ class Tinfoil:
725 895
726 ret = self.run_command('buildTargets', targets, task) 896 ret = self.run_command('buildTargets', targets, task)
727 if handle_events: 897 if handle_events:
898 lastevent = time.time()
728 result = False 899 result = False
729 # Borrowed from knotty, instead somewhat hackily we use the helper 900 # Borrowed from knotty, instead somewhat hackily we use the helper
730 # as the object to store "shutdown" on 901 # as the object to store "shutdown" on
@@ -737,6 +908,7 @@ class Tinfoil:
737 try: 908 try:
738 event = self.wait_event(0.25) 909 event = self.wait_event(0.25)
739 if event: 910 if event:
911 lastevent = time.time()
740 if event_callback and event_callback(event): 912 if event_callback and event_callback(event):
741 continue 913 continue
742 if helper.eventHandler(event): 914 if helper.eventHandler(event):
@@ -757,7 +929,7 @@ class Tinfoil:
757 if parseprogress: 929 if parseprogress:
758 parseprogress.update(event.progress) 930 parseprogress.update(event.progress)
759 else: 931 else:
760 bb.warn("Got ProcessProgress event for someting that never started?") 932 bb.warn("Got ProcessProgress event for something that never started?")
761 continue 933 continue
762 if isinstance(event, bb.event.ProcessFinished): 934 if isinstance(event, bb.event.ProcessFinished):
763 if self.quiet > 1: 935 if self.quiet > 1:
@@ -769,7 +941,7 @@ class Tinfoil:
769 if isinstance(event, bb.command.CommandCompleted): 941 if isinstance(event, bb.command.CommandCompleted):
770 result = True 942 result = True
771 break 943 break
772 if isinstance(event, bb.command.CommandFailed): 944 if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit)):
773 self.logger.error(str(event)) 945 self.logger.error(str(event))
774 result = False 946 result = False
775 break 947 break
@@ -781,10 +953,13 @@ class Tinfoil:
781 self.logger.error(str(event)) 953 self.logger.error(str(event))
782 result = False 954 result = False
783 break 955 break
784
785 elif helper.shutdown > 1: 956 elif helper.shutdown > 1:
786 break 957 break
787 termfilter.updateFooter() 958 termfilter.updateFooter()
959 if time.time() > (lastevent + (3*60)):
960 if not self.run_command('ping', handle_events=False):
961 print("\nUnable to ping server and no events, closing down...\n")
962 return False
788 except KeyboardInterrupt: 963 except KeyboardInterrupt:
789 termfilter.clearFooter() 964 termfilter.clearFooter()
790 if helper.shutdown == 1: 965 if helper.shutdown == 1: