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