diff options
Diffstat (limited to 'bitbake/lib/bb/tinfoil.py')
-rw-r--r-- | bitbake/lib/bb/tinfoil.py | 183 |
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 | |||
14 | import atexit | 14 | import atexit |
15 | import re | 15 | import re |
16 | from collections import OrderedDict, defaultdict | 16 | from collections import OrderedDict, defaultdict |
17 | from functools import partial | 17 | from functools import partial, wraps |
18 | from contextlib import contextmanager | ||
18 | 19 | ||
19 | import bb.cache | 20 | import bb.cache |
20 | import bb.cooker | 21 | import bb.cooker |
@@ -26,6 +27,135 @@ import bb.remotedata | |||
26 | from bb.main import setup_bitbake, BitBakeConfigParameters | 27 | from bb.main import setup_bitbake, BitBakeConfigParameters |
27 | import bb.fetch2 | 28 | import bb.fetch2 |
28 | 29 | ||
30 | def 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 |