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.py183
1 files changed, 171 insertions, 12 deletions
diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py
index dcd3910cc4..e7fbcbca0a 100644
--- a/bitbake/lib/bb/tinfoil.py
+++ b/bitbake/lib/bb/tinfoil.py
@@ -14,7 +14,8 @@ import time
14import atexit 14import atexit
15import re 15import re
16from collections import OrderedDict, defaultdict 16from collections import OrderedDict, defaultdict
17from functools import partial 17from functools import partial, wraps
18from contextlib import contextmanager
18 19
19import bb.cache 20import bb.cache
20import bb.cooker 21import bb.cooker
@@ -26,6 +27,135 @@ import bb.remotedata
26from bb.main import setup_bitbake, BitBakeConfigParameters 27from bb.main import setup_bitbake, BitBakeConfigParameters
27import bb.fetch2 28import bb.fetch2
28 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
29 159
30# 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,
31# otherwise the process will never properly exit 161# otherwise the process will never properly exit
@@ -188,11 +318,19 @@ class TinfoilCookerAdapter:
188 self._cache[name] = attrvalue 318 self._cache[name] = attrvalue
189 return attrvalue 319 return attrvalue
190 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
191 def __init__(self, tinfoil): 328 def __init__(self, tinfoil):
192 self.tinfoil = tinfoil 329 self.tinfoil = tinfoil
193 self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split() 330 self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split()
194 self.collections = {} 331 self.collections = {}
195 self.recipecaches = {} 332 self.recipecaches = {}
333 self.skiplist_by_mc = self.TinfoilSkiplistByMcAdapter(tinfoil)
196 for mc in self.multiconfigs: 334 for mc in self.multiconfigs:
197 self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc) 335 self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc)
198 self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc) 336 self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc)
@@ -201,8 +339,6 @@ class TinfoilCookerAdapter:
201 # 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
202 if name in self._cache: 340 if name in self._cache:
203 return self._cache[name] 341 return self._cache[name]
204 elif name == 'skiplist':
205 attrvalue = self.tinfoil.get_skipped_recipes()
206 elif name == 'bbfile_config_priorities': 342 elif name == 'bbfile_config_priorities':
207 ret = self.tinfoil.run_command('getLayerPriorities') 343 ret = self.tinfoil.run_command('getLayerPriorities')
208 bbfile_config_priorities = [] 344 bbfile_config_priorities = []
@@ -514,12 +650,12 @@ class Tinfoil:
514 """ 650 """
515 return defaultdict(list, self.run_command('getOverlayedRecipes', mc)) 651 return defaultdict(list, self.run_command('getOverlayedRecipes', mc))
516 652
517 def get_skipped_recipes(self): 653 def get_skipped_recipes(self, mc=''):
518 """ 654 """
519 Find recipes which were skipped (i.e. SkipRecipe was raised 655 Find recipes which were skipped (i.e. SkipRecipe was raised
520 during parsing). 656 during parsing).
521 """ 657 """
522 return OrderedDict(self.run_command('getSkippedRecipes')) 658 return OrderedDict(self.run_command('getSkippedRecipes', mc))
523 659
524 def get_all_providers(self, mc=''): 660 def get_all_providers(self, mc=''):
525 return defaultdict(list, self.run_command('allProviders', mc)) 661 return defaultdict(list, self.run_command('allProviders', mc))
@@ -533,6 +669,7 @@ class Tinfoil:
533 def get_runtime_providers(self, rdep): 669 def get_runtime_providers(self, rdep):
534 return self.run_command('getRuntimeProviders', rdep) 670 return self.run_command('getRuntimeProviders', rdep)
535 671
672 # TODO: teach this method about mc
536 def get_recipe_file(self, pn): 673 def get_recipe_file(self, pn):
537 """ 674 """
538 Get the file name for the specified recipe/target. Raises 675 Get the file name for the specified recipe/target. Raises
@@ -541,6 +678,7 @@ class Tinfoil:
541 """ 678 """
542 best = self.find_best_provider(pn) 679 best = self.find_best_provider(pn)
543 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
544 skiplist = self.get_skipped_recipes() 682 skiplist = self.get_skipped_recipes()
545 taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) 683 taskdata = bb.taskdata.TaskData(None, skiplist=skiplist)
546 skipreasons = taskdata.get_reasons(pn) 684 skipreasons = taskdata.get_reasons(pn)
@@ -633,6 +771,29 @@ class Tinfoil:
633 fn = self.get_recipe_file(pn) 771 fn = self.get_recipe_file(pn)
634 return self.parse_recipe_file(fn) 772 return self.parse_recipe_file(fn)
635 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
636 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):
637 """ 798 """
638 Parse the specified recipe file (with or without bbappends) 799 Parse the specified recipe file (with or without bbappends)
@@ -645,10 +806,7 @@ class Tinfoil:
645 appendlist: optional list of bbappend files to apply, if you 806 appendlist: optional list of bbappend files to apply, if you
646 want to filter them 807 want to filter them
647 """ 808 """
648 if self.tracking: 809 with self._data_tracked_if_enabled():
649 # Enable history tracking just for the parse operation
650 self.run_command('enableDataTracking')
651 try:
652 if appends and appendlist == []: 810 if appends and appendlist == []:
653 appends = False 811 appends = False
654 if config_data: 812 if config_data:
@@ -660,9 +818,6 @@ class Tinfoil:
660 return self._reconvert_type(dscon, 'DataStoreConnectionHandle') 818 return self._reconvert_type(dscon, 'DataStoreConnectionHandle')
661 else: 819 else:
662 return None 820 return None
663 finally:
664 if self.tracking:
665 self.run_command('disableDataTracking')
666 821
667 def build_file(self, buildfile, task, internal=True): 822 def build_file(self, buildfile, task, internal=True):
668 """ 823 """
@@ -674,6 +829,10 @@ class Tinfoil:
674 """ 829 """
675 return self.run_command('buildFile', buildfile, task, internal) 830 return self.run_command('buildFile', buildfile, task, internal)
676 831
832 @wait_for
833 def build_file_sync(self, *args):
834 self.build_file(*args)
835
677 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):
678 """ 837 """
679 Builds the specified targets. This is equivalent to a normal invocation 838 Builds the specified targets. This is equivalent to a normal invocation