diff options
Diffstat (limited to 'bitbake/lib/bb/ui')
-rw-r--r-- | bitbake/lib/bb/ui/buildinfohelper.py | 101 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/eventreplay.py | 86 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/knotty.py | 193 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/ncurses.py | 3 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/taskexp.py | 7 | ||||
-rwxr-xr-x | bitbake/lib/bb/ui/taskexp_ncurses.py | 1511 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/toasterui.py | 2 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/uievent.py | 32 | ||||
-rw-r--r-- | bitbake/lib/bb/ui/uihelper.py | 6 |
9 files changed, 1807 insertions, 134 deletions
diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py index 43aa592842..4ee45d67a2 100644 --- a/bitbake/lib/bb/ui/buildinfohelper.py +++ b/bitbake/lib/bb/ui/buildinfohelper.py | |||
@@ -45,7 +45,7 @@ from pprint import pformat | |||
45 | import logging | 45 | import logging |
46 | from datetime import datetime, timedelta | 46 | from datetime import datetime, timedelta |
47 | 47 | ||
48 | from django.db import transaction, connection | 48 | from django.db import transaction |
49 | 49 | ||
50 | 50 | ||
51 | # pylint: disable=invalid-name | 51 | # pylint: disable=invalid-name |
@@ -227,6 +227,12 @@ class ORMWrapper(object): | |||
227 | build.completed_on = timezone.now() | 227 | build.completed_on = timezone.now() |
228 | build.outcome = outcome | 228 | build.outcome = outcome |
229 | build.save() | 229 | build.save() |
230 | |||
231 | # We force a sync point here to force the outcome status commit, | ||
232 | # which resolves a race condition with the build completion takedown | ||
233 | transaction.set_autocommit(True) | ||
234 | transaction.set_autocommit(False) | ||
235 | |||
230 | signal_runbuilds() | 236 | signal_runbuilds() |
231 | 237 | ||
232 | def update_target_set_license_manifest(self, target, license_manifest_path): | 238 | def update_target_set_license_manifest(self, target, license_manifest_path): |
@@ -483,14 +489,14 @@ class ORMWrapper(object): | |||
483 | 489 | ||
484 | # we already created the root directory, so ignore any | 490 | # we already created the root directory, so ignore any |
485 | # entry for it | 491 | # entry for it |
486 | if len(path) == 0: | 492 | if not path: |
487 | continue | 493 | continue |
488 | 494 | ||
489 | parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1]) | 495 | parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1]) |
490 | if len(parent_path) == 0: | 496 | if not parent_path: |
491 | parent_path = "/" | 497 | parent_path = "/" |
492 | parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) | 498 | parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) |
493 | tf_obj = Target_File.objects.create( | 499 | Target_File.objects.create( |
494 | target = target_obj, | 500 | target = target_obj, |
495 | path = path, | 501 | path = path, |
496 | size = size, | 502 | size = size, |
@@ -553,9 +559,12 @@ class ORMWrapper(object): | |||
553 | # we might have an invalid link; no way to detect this. just set it to None | 559 | # we might have an invalid link; no way to detect this. just set it to None |
554 | filetarget_obj = None | 560 | filetarget_obj = None |
555 | 561 | ||
556 | parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) | 562 | try: |
563 | parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) | ||
564 | except Target_File.DoesNotExist: | ||
565 | parent_obj = None | ||
557 | 566 | ||
558 | tf_obj = Target_File.objects.create( | 567 | Target_File.objects.create( |
559 | target = target_obj, | 568 | target = target_obj, |
560 | path = path, | 569 | path = path, |
561 | size = size, | 570 | size = size, |
@@ -571,7 +580,7 @@ class ORMWrapper(object): | |||
571 | assert isinstance(build_obj, Build) | 580 | assert isinstance(build_obj, Build) |
572 | assert isinstance(target_obj, Target) | 581 | assert isinstance(target_obj, Target) |
573 | 582 | ||
574 | errormsg = "" | 583 | errormsg = [] |
575 | for p in packagedict: | 584 | for p in packagedict: |
576 | # Search name swtiches round the installed name vs package name | 585 | # Search name swtiches round the installed name vs package name |
577 | # by default installed name == package name | 586 | # by default installed name == package name |
@@ -633,10 +642,10 @@ class ORMWrapper(object): | |||
633 | packagefile_objects.append(Package_File( package = packagedict[p]['object'], | 642 | packagefile_objects.append(Package_File( package = packagedict[p]['object'], |
634 | path = targetpath, | 643 | path = targetpath, |
635 | size = targetfilesize)) | 644 | size = targetfilesize)) |
636 | if len(packagefile_objects): | 645 | if packagefile_objects: |
637 | Package_File.objects.bulk_create(packagefile_objects) | 646 | Package_File.objects.bulk_create(packagefile_objects) |
638 | except KeyError as e: | 647 | except KeyError as e: |
639 | errormsg += " stpi: Key error, package %s key %s \n" % ( p, e ) | 648 | errormsg.append(" stpi: Key error, package %s key %s \n" % (p, e)) |
640 | 649 | ||
641 | # save disk installed size | 650 | # save disk installed size |
642 | packagedict[p]['object'].installed_size = packagedict[p]['size'] | 651 | packagedict[p]['object'].installed_size = packagedict[p]['size'] |
@@ -673,13 +682,13 @@ class ORMWrapper(object): | |||
673 | logger.warning("Could not add dependency to the package %s " | 682 | logger.warning("Could not add dependency to the package %s " |
674 | "because %s is an unknown package", p, px) | 683 | "because %s is an unknown package", p, px) |
675 | 684 | ||
676 | if len(packagedeps_objs) > 0: | 685 | if packagedeps_objs: |
677 | Package_Dependency.objects.bulk_create(packagedeps_objs) | 686 | Package_Dependency.objects.bulk_create(packagedeps_objs) |
678 | else: | 687 | else: |
679 | logger.info("No package dependencies created") | 688 | logger.info("No package dependencies created") |
680 | 689 | ||
681 | if len(errormsg) > 0: | 690 | if errormsg: |
682 | logger.warning("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg) | 691 | logger.warning("buildinfohelper: target_package_info could not identify recipes: \n%s", "".join(errormsg)) |
683 | 692 | ||
684 | def save_target_image_file_information(self, target_obj, file_name, file_size): | 693 | def save_target_image_file_information(self, target_obj, file_name, file_size): |
685 | Target_Image_File.objects.create(target=target_obj, | 694 | Target_Image_File.objects.create(target=target_obj, |
@@ -767,7 +776,7 @@ class ORMWrapper(object): | |||
767 | packagefile_objects.append(Package_File( package = bp_object, | 776 | packagefile_objects.append(Package_File( package = bp_object, |
768 | path = path, | 777 | path = path, |
769 | size = package_info['FILES_INFO'][path] )) | 778 | size = package_info['FILES_INFO'][path] )) |
770 | if len(packagefile_objects): | 779 | if packagefile_objects: |
771 | Package_File.objects.bulk_create(packagefile_objects) | 780 | Package_File.objects.bulk_create(packagefile_objects) |
772 | 781 | ||
773 | def _po_byname(p): | 782 | def _po_byname(p): |
@@ -809,7 +818,7 @@ class ORMWrapper(object): | |||
809 | packagedeps_objs.append(Package_Dependency( package = bp_object, | 818 | packagedeps_objs.append(Package_Dependency( package = bp_object, |
810 | depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS)) | 819 | depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS)) |
811 | 820 | ||
812 | if len(packagedeps_objs) > 0: | 821 | if packagedeps_objs: |
813 | Package_Dependency.objects.bulk_create(packagedeps_objs) | 822 | Package_Dependency.objects.bulk_create(packagedeps_objs) |
814 | 823 | ||
815 | return bp_object | 824 | return bp_object |
@@ -826,7 +835,7 @@ class ORMWrapper(object): | |||
826 | desc = vardump[root_var]['doc'] | 835 | desc = vardump[root_var]['doc'] |
827 | if desc is None: | 836 | if desc is None: |
828 | desc = '' | 837 | desc = '' |
829 | if len(desc): | 838 | if desc: |
830 | HelpText.objects.get_or_create(build=build_obj, | 839 | HelpText.objects.get_or_create(build=build_obj, |
831 | area=HelpText.VARIABLE, | 840 | area=HelpText.VARIABLE, |
832 | key=k, text=desc) | 841 | key=k, text=desc) |
@@ -846,7 +855,7 @@ class ORMWrapper(object): | |||
846 | file_name = vh['file'], | 855 | file_name = vh['file'], |
847 | line_number = vh['line'], | 856 | line_number = vh['line'], |
848 | operation = vh['op'])) | 857 | operation = vh['op'])) |
849 | if len(varhist_objects): | 858 | if varhist_objects: |
850 | VariableHistory.objects.bulk_create(varhist_objects) | 859 | VariableHistory.objects.bulk_create(varhist_objects) |
851 | 860 | ||
852 | 861 | ||
@@ -893,9 +902,6 @@ class BuildInfoHelper(object): | |||
893 | self.task_order = 0 | 902 | self.task_order = 0 |
894 | self.autocommit_step = 1 | 903 | self.autocommit_step = 1 |
895 | self.server = server | 904 | self.server = server |
896 | # we use manual transactions if the database doesn't autocommit on us | ||
897 | if not connection.features.autocommits_when_autocommit_is_off: | ||
898 | transaction.set_autocommit(False) | ||
899 | self.orm_wrapper = ORMWrapper() | 905 | self.orm_wrapper = ORMWrapper() |
900 | self.has_build_history = has_build_history | 906 | self.has_build_history = has_build_history |
901 | self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0] | 907 | self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0] |
@@ -1059,27 +1065,6 @@ class BuildInfoHelper(object): | |||
1059 | 1065 | ||
1060 | return recipe_info | 1066 | return recipe_info |
1061 | 1067 | ||
1062 | def _get_path_information(self, task_object): | ||
1063 | self._ensure_build() | ||
1064 | |||
1065 | assert isinstance(task_object, Task) | ||
1066 | build_stats_format = "{tmpdir}/buildstats/{buildname}/{package}/" | ||
1067 | build_stats_path = [] | ||
1068 | |||
1069 | for t in self.internal_state['targets']: | ||
1070 | buildname = self.internal_state['build'].build_name | ||
1071 | pe, pv = task_object.recipe.version.split(":",1) | ||
1072 | if len(pe) > 0: | ||
1073 | package = task_object.recipe.name + "-" + pe + "_" + pv | ||
1074 | else: | ||
1075 | package = task_object.recipe.name + "-" + pv | ||
1076 | |||
1077 | build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir, | ||
1078 | buildname=buildname, | ||
1079 | package=package)) | ||
1080 | |||
1081 | return build_stats_path | ||
1082 | |||
1083 | 1068 | ||
1084 | ################################ | 1069 | ################################ |
1085 | ## external available methods to store information | 1070 | ## external available methods to store information |
@@ -1313,12 +1298,11 @@ class BuildInfoHelper(object): | |||
1313 | task_information['outcome'] = Task.OUTCOME_FAILED | 1298 | task_information['outcome'] = Task.OUTCOME_FAILED |
1314 | del self.internal_state['taskdata'][identifier] | 1299 | del self.internal_state['taskdata'][identifier] |
1315 | 1300 | ||
1316 | if not connection.features.autocommits_when_autocommit_is_off: | 1301 | # we force a sync point here, to get the progress bar to show |
1317 | # we force a sync point here, to get the progress bar to show | 1302 | if self.autocommit_step % 3 == 0: |
1318 | if self.autocommit_step % 3 == 0: | 1303 | transaction.set_autocommit(True) |
1319 | transaction.set_autocommit(True) | 1304 | transaction.set_autocommit(False) |
1320 | transaction.set_autocommit(False) | 1305 | self.autocommit_step += 1 |
1321 | self.autocommit_step += 1 | ||
1322 | 1306 | ||
1323 | self.orm_wrapper.get_update_task_object(task_information, True) # must exist | 1307 | self.orm_wrapper.get_update_task_object(task_information, True) # must exist |
1324 | 1308 | ||
@@ -1404,7 +1388,7 @@ class BuildInfoHelper(object): | |||
1404 | assert 'pn' in event._depgraph | 1388 | assert 'pn' in event._depgraph |
1405 | assert 'tdepends' in event._depgraph | 1389 | assert 'tdepends' in event._depgraph |
1406 | 1390 | ||
1407 | errormsg = "" | 1391 | errormsg = [] |
1408 | 1392 | ||
1409 | # save layer version priorities | 1393 | # save layer version priorities |
1410 | if 'layer-priorities' in event._depgraph.keys(): | 1394 | if 'layer-priorities' in event._depgraph.keys(): |
@@ -1496,7 +1480,7 @@ class BuildInfoHelper(object): | |||
1496 | elif dep in self.internal_state['recipes']: | 1480 | elif dep in self.internal_state['recipes']: |
1497 | dependency = self.internal_state['recipes'][dep] | 1481 | dependency = self.internal_state['recipes'][dep] |
1498 | else: | 1482 | else: |
1499 | errormsg += " stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep) | 1483 | errormsg.append(" stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep)) |
1500 | continue | 1484 | continue |
1501 | recipe_dep = Recipe_Dependency(recipe=target, | 1485 | recipe_dep = Recipe_Dependency(recipe=target, |
1502 | depends_on=dependency, | 1486 | depends_on=dependency, |
@@ -1537,8 +1521,8 @@ class BuildInfoHelper(object): | |||
1537 | taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep )) | 1521 | taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep )) |
1538 | Task_Dependency.objects.bulk_create(taskdeps_objects) | 1522 | Task_Dependency.objects.bulk_create(taskdeps_objects) |
1539 | 1523 | ||
1540 | if len(errormsg) > 0: | 1524 | if errormsg: |
1541 | logger.warning("buildinfohelper: dependency info not identify recipes: \n%s", errormsg) | 1525 | logger.warning("buildinfohelper: dependency info not identify recipes: \n%s", "".join(errormsg)) |
1542 | 1526 | ||
1543 | 1527 | ||
1544 | def store_build_package_information(self, event): | 1528 | def store_build_package_information(self, event): |
@@ -1618,7 +1602,7 @@ class BuildInfoHelper(object): | |||
1618 | 1602 | ||
1619 | if 'backlog' in self.internal_state: | 1603 | if 'backlog' in self.internal_state: |
1620 | # if we have a backlog of events, do our best to save them here | 1604 | # if we have a backlog of events, do our best to save them here |
1621 | if len(self.internal_state['backlog']): | 1605 | if self.internal_state['backlog']: |
1622 | tempevent = self.internal_state['backlog'].pop() | 1606 | tempevent = self.internal_state['backlog'].pop() |
1623 | logger.debug("buildinfohelper: Saving stored event %s " | 1607 | logger.debug("buildinfohelper: Saving stored event %s " |
1624 | % tempevent) | 1608 | % tempevent) |
@@ -1765,7 +1749,6 @@ class BuildInfoHelper(object): | |||
1765 | 1749 | ||
1766 | buildname = self.server.runCommand(['getVariable', 'BUILDNAME'])[0] | 1750 | buildname = self.server.runCommand(['getVariable', 'BUILDNAME'])[0] |
1767 | machine = self.server.runCommand(['getVariable', 'MACHINE'])[0] | 1751 | machine = self.server.runCommand(['getVariable', 'MACHINE'])[0] |
1768 | image_name = self.server.runCommand(['getVariable', 'IMAGE_NAME'])[0] | ||
1769 | 1752 | ||
1770 | # location of the manifest files for this build; | 1753 | # location of the manifest files for this build; |
1771 | # note that this file is only produced if an image is produced | 1754 | # note that this file is only produced if an image is produced |
@@ -1786,6 +1769,18 @@ class BuildInfoHelper(object): | |||
1786 | # filter out anything which isn't an image target | 1769 | # filter out anything which isn't an image target |
1787 | image_targets = [target for target in targets if target.is_image] | 1770 | image_targets = [target for target in targets if target.is_image] |
1788 | 1771 | ||
1772 | if len(image_targets) > 0: | ||
1773 | #if there are image targets retrieve image_name | ||
1774 | image_name = self.server.runCommand(['getVariable', 'IMAGE_NAME'])[0] | ||
1775 | if not image_name: | ||
1776 | #When build target is an image and image_name is not found as an environment variable | ||
1777 | logger.info("IMAGE_NAME not found, extracting from bitbake command") | ||
1778 | cmd = self.server.runCommand(['getVariable','BB_CMDLINE'])[0] | ||
1779 | #filter out tokens that are command line options | ||
1780 | cmd = [token for token in cmd if not token.startswith('-')] | ||
1781 | image_name = cmd[1].split(':', 1)[0] # remove everything after : in image name | ||
1782 | logger.info("IMAGE_NAME found as : %s " % image_name) | ||
1783 | |||
1789 | for image_target in image_targets: | 1784 | for image_target in image_targets: |
1790 | # this is set to True if we find at least one file relating to | 1785 | # this is set to True if we find at least one file relating to |
1791 | # this target; if this remains False after the scan, we copy the | 1786 | # this target; if this remains False after the scan, we copy the |
@@ -1990,8 +1985,6 @@ class BuildInfoHelper(object): | |||
1990 | # Do not skip command line build events | 1985 | # Do not skip command line build events |
1991 | self.store_log_event(tempevent,False) | 1986 | self.store_log_event(tempevent,False) |
1992 | 1987 | ||
1993 | if not connection.features.autocommits_when_autocommit_is_off: | ||
1994 | transaction.set_autocommit(True) | ||
1995 | 1988 | ||
1996 | # unset the brbe; this is to prevent subsequent command-line builds | 1989 | # unset the brbe; this is to prevent subsequent command-line builds |
1997 | # being incorrectly attached to the previous Toaster-triggered build; | 1990 | # being incorrectly attached to the previous Toaster-triggered build; |
diff --git a/bitbake/lib/bb/ui/eventreplay.py b/bitbake/lib/bb/ui/eventreplay.py new file mode 100644 index 0000000000..d62ecbfa56 --- /dev/null +++ b/bitbake/lib/bb/ui/eventreplay.py | |||
@@ -0,0 +1,86 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | # | ||
3 | # SPDX-License-Identifier: GPL-2.0-only | ||
4 | # | ||
5 | # This file re-uses code spread throughout other Bitbake source files. | ||
6 | # As such, all other copyrights belong to their own right holders. | ||
7 | # | ||
8 | |||
9 | |||
10 | import os | ||
11 | import sys | ||
12 | import json | ||
13 | import pickle | ||
14 | import codecs | ||
15 | |||
16 | |||
17 | class EventPlayer: | ||
18 | """Emulate a connection to a bitbake server.""" | ||
19 | |||
20 | def __init__(self, eventfile, variables): | ||
21 | self.eventfile = eventfile | ||
22 | self.variables = variables | ||
23 | self.eventmask = [] | ||
24 | |||
25 | def waitEvent(self, _timeout): | ||
26 | """Read event from the file.""" | ||
27 | line = self.eventfile.readline().strip() | ||
28 | if not line: | ||
29 | return | ||
30 | try: | ||
31 | decodedline = json.loads(line) | ||
32 | if 'allvariables' in decodedline: | ||
33 | self.variables = decodedline['allvariables'] | ||
34 | return | ||
35 | if not 'vars' in decodedline: | ||
36 | raise ValueError | ||
37 | event_str = decodedline['vars'].encode('utf-8') | ||
38 | event = pickle.loads(codecs.decode(event_str, 'base64')) | ||
39 | event_name = "%s.%s" % (event.__module__, event.__class__.__name__) | ||
40 | if event_name not in self.eventmask: | ||
41 | return | ||
42 | return event | ||
43 | except ValueError as err: | ||
44 | print("Failed loading ", line) | ||
45 | raise err | ||
46 | |||
47 | def runCommand(self, command_line): | ||
48 | """Emulate running a command on the server.""" | ||
49 | name = command_line[0] | ||
50 | |||
51 | if name == "getVariable": | ||
52 | var_name = command_line[1] | ||
53 | variable = self.variables.get(var_name) | ||
54 | if variable: | ||
55 | return variable['v'], None | ||
56 | return None, "Missing variable %s" % var_name | ||
57 | |||
58 | elif name == "getAllKeysWithFlags": | ||
59 | dump = {} | ||
60 | flaglist = command_line[1] | ||
61 | for key, val in self.variables.items(): | ||
62 | try: | ||
63 | if not key.startswith("__"): | ||
64 | dump[key] = { | ||
65 | 'v': val['v'], | ||
66 | 'history' : val['history'], | ||
67 | } | ||
68 | for flag in flaglist: | ||
69 | dump[key][flag] = val[flag] | ||
70 | except Exception as err: | ||
71 | print(err) | ||
72 | return (dump, None) | ||
73 | |||
74 | elif name == 'setEventMask': | ||
75 | self.eventmask = command_line[-1] | ||
76 | return True, None | ||
77 | |||
78 | else: | ||
79 | raise Exception("Command %s not implemented" % command_line[0]) | ||
80 | |||
81 | def getEventHandle(self): | ||
82 | """ | ||
83 | This method is called by toasterui. | ||
84 | The return value is passed to self.runCommand but not used there. | ||
85 | """ | ||
86 | pass | ||
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py index 0efa614dfc..f86999bb09 100644 --- a/bitbake/lib/bb/ui/knotty.py +++ b/bitbake/lib/bb/ui/knotty.py | |||
@@ -21,10 +21,11 @@ import fcntl | |||
21 | import struct | 21 | import struct |
22 | import copy | 22 | import copy |
23 | import atexit | 23 | import atexit |
24 | from itertools import groupby | ||
24 | 25 | ||
25 | from bb.ui import uihelper | 26 | from bb.ui import uihelper |
26 | 27 | ||
27 | featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS] | 28 | featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING] |
28 | 29 | ||
29 | logger = logging.getLogger("BitBake") | 30 | logger = logging.getLogger("BitBake") |
30 | interactive = sys.stdout.isatty() | 31 | interactive = sys.stdout.isatty() |
@@ -178,7 +179,7 @@ class TerminalFilter(object): | |||
178 | new[3] = new[3] & ~termios.ECHO | 179 | new[3] = new[3] & ~termios.ECHO |
179 | termios.tcsetattr(fd, termios.TCSADRAIN, new) | 180 | termios.tcsetattr(fd, termios.TCSADRAIN, new) |
180 | curses.setupterm() | 181 | curses.setupterm() |
181 | if curses.tigetnum("colors") > 2: | 182 | if curses.tigetnum("colors") > 2 and os.environ.get('NO_COLOR', '') == '': |
182 | for h in handlers: | 183 | for h in handlers: |
183 | try: | 184 | try: |
184 | h.formatter.enable_color() | 185 | h.formatter.enable_color() |
@@ -227,7 +228,9 @@ class TerminalFilter(object): | |||
227 | 228 | ||
228 | def keepAlive(self, t): | 229 | def keepAlive(self, t): |
229 | if not self.cuu: | 230 | if not self.cuu: |
230 | print("Bitbake still alive (%ds)" % t) | 231 | print("Bitbake still alive (no events for %ds). Active tasks:" % t) |
232 | for t in self.helper.running_tasks: | ||
233 | print(t) | ||
231 | sys.stdout.flush() | 234 | sys.stdout.flush() |
232 | 235 | ||
233 | def updateFooter(self): | 236 | def updateFooter(self): |
@@ -249,58 +252,68 @@ class TerminalFilter(object): | |||
249 | return | 252 | return |
250 | tasks = [] | 253 | tasks = [] |
251 | for t in runningpids: | 254 | for t in runningpids: |
255 | start_time = activetasks[t].get("starttime", None) | ||
256 | if start_time: | ||
257 | msg = "%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"]) | ||
258 | else: | ||
259 | msg = "%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]) | ||
252 | progress = activetasks[t].get("progress", None) | 260 | progress = activetasks[t].get("progress", None) |
253 | if progress is not None: | 261 | if progress is not None: |
254 | pbar = activetasks[t].get("progressbar", None) | 262 | pbar = activetasks[t].get("progressbar", None) |
255 | rate = activetasks[t].get("rate", None) | 263 | rate = activetasks[t].get("rate", None) |
256 | start_time = activetasks[t].get("starttime", None) | ||
257 | if not pbar or pbar.bouncing != (progress < 0): | 264 | if not pbar or pbar.bouncing != (progress < 0): |
258 | if progress < 0: | 265 | if progress < 0: |
259 | pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle) | 266 | pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.BouncingSlider(), ''], extrapos=3, resize_handler=self.sigwinch_handle) |
260 | pbar.bouncing = True | 267 | pbar.bouncing = True |
261 | else: | 268 | else: |
262 | pbar = BBProgress("0: %s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"]), 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle) | 269 | pbar = BBProgress("0: %s" % msg, 100, widgets=[' ', progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=5, resize_handler=self.sigwinch_handle) |
263 | pbar.bouncing = False | 270 | pbar.bouncing = False |
264 | activetasks[t]["progressbar"] = pbar | 271 | activetasks[t]["progressbar"] = pbar |
265 | tasks.append((pbar, progress, rate, start_time)) | 272 | tasks.append((pbar, msg, progress, rate, start_time)) |
266 | else: | 273 | else: |
267 | start_time = activetasks[t].get("starttime", None) | 274 | tasks.append(msg) |
268 | if start_time: | ||
269 | tasks.append("%s - %s (pid %s)" % (activetasks[t]["title"], self.elapsed(currenttime - start_time), activetasks[t]["pid"])) | ||
270 | else: | ||
271 | tasks.append("%s (pid %s)" % (activetasks[t]["title"], activetasks[t]["pid"])) | ||
272 | 275 | ||
273 | if self.main.shutdown: | 276 | if self.main.shutdown: |
274 | content = "Waiting for %s running tasks to finish:" % len(activetasks) | 277 | content = pluralise("Waiting for %s running task to finish", |
278 | "Waiting for %s running tasks to finish", len(activetasks)) | ||
279 | if not self.quiet: | ||
280 | content += ':' | ||
275 | print(content) | 281 | print(content) |
276 | else: | 282 | else: |
283 | scene_tasks = "%s of %s" % (self.helper.setscene_current, self.helper.setscene_total) | ||
284 | cur_tasks = "%s of %s" % (self.helper.tasknumber_current, self.helper.tasknumber_total) | ||
285 | |||
286 | content = '' | ||
287 | if not self.quiet: | ||
288 | msg = "Setscene tasks: %s" % scene_tasks | ||
289 | content += msg + "\n" | ||
290 | print(msg) | ||
291 | |||
277 | if self.quiet: | 292 | if self.quiet: |
278 | content = "Running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) | 293 | msg = "Running tasks (%s, %s)" % (scene_tasks, cur_tasks) |
279 | elif not len(activetasks): | 294 | elif not len(activetasks): |
280 | content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) | 295 | msg = "No currently running tasks (%s)" % cur_tasks |
281 | else: | 296 | else: |
282 | content = "Currently %2s running tasks (%s of %s)" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) | 297 | msg = "Currently %2s running tasks (%s)" % (len(activetasks), cur_tasks) |
283 | maxtask = self.helper.tasknumber_total | 298 | maxtask = self.helper.tasknumber_total |
284 | if not self.main_progress or self.main_progress.maxval != maxtask: | 299 | if not self.main_progress or self.main_progress.maxval != maxtask: |
285 | widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] | 300 | widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] |
286 | self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle) | 301 | self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle) |
287 | self.main_progress.start(False) | 302 | self.main_progress.start(False) |
288 | self.main_progress.setmessage(content) | 303 | self.main_progress.setmessage(msg) |
289 | progress = self.helper.tasknumber_current - 1 | 304 | progress = max(0, self.helper.tasknumber_current - 1) |
290 | if progress < 0: | 305 | content += self.main_progress.update(progress) |
291 | progress = 0 | ||
292 | content = self.main_progress.update(progress) | ||
293 | print('') | 306 | print('') |
294 | lines = 1 + int(len(content) / (self.columns + 1)) | 307 | lines = self.getlines(content) |
295 | if self.quiet == 0: | 308 | if not self.quiet: |
296 | for tasknum, task in enumerate(tasks[:(self.rows - 2)]): | 309 | for tasknum, task in enumerate(tasks[:(self.rows - 1 - lines)]): |
297 | if isinstance(task, tuple): | 310 | if isinstance(task, tuple): |
298 | pbar, progress, rate, start_time = task | 311 | pbar, msg, progress, rate, start_time = task |
299 | if not pbar.start_time: | 312 | if not pbar.start_time: |
300 | pbar.start(False) | 313 | pbar.start(False) |
301 | if start_time: | 314 | if start_time: |
302 | pbar.start_time = start_time | 315 | pbar.start_time = start_time |
303 | pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1])) | 316 | pbar.setmessage('%s: %s' % (tasknum, msg)) |
304 | pbar.setextra(rate) | 317 | pbar.setextra(rate) |
305 | if progress > -1: | 318 | if progress > -1: |
306 | content = pbar.update(progress) | 319 | content = pbar.update(progress) |
@@ -310,11 +323,17 @@ class TerminalFilter(object): | |||
310 | else: | 323 | else: |
311 | content = "%s: %s" % (tasknum, task) | 324 | content = "%s: %s" % (tasknum, task) |
312 | print(content) | 325 | print(content) |
313 | lines = lines + 1 + int(len(content) / (self.columns + 1)) | 326 | lines = lines + self.getlines(content) |
314 | self.footer_present = lines | 327 | self.footer_present = lines |
315 | self.lastpids = runningpids[:] | 328 | self.lastpids = runningpids[:] |
316 | self.lastcount = self.helper.tasknumber_current | 329 | self.lastcount = self.helper.tasknumber_current |
317 | 330 | ||
331 | def getlines(self, content): | ||
332 | lines = 0 | ||
333 | for line in content.split("\n"): | ||
334 | lines = lines + 1 + int(len(line) / (self.columns + 1)) | ||
335 | return lines | ||
336 | |||
318 | def finish(self): | 337 | def finish(self): |
319 | if self.stdinbackup: | 338 | if self.stdinbackup: |
320 | fd = sys.stdin.fileno() | 339 | fd = sys.stdin.fileno() |
@@ -401,6 +420,11 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
401 | except bb.BBHandledException: | 420 | except bb.BBHandledException: |
402 | drain_events_errorhandling(eventHandler) | 421 | drain_events_errorhandling(eventHandler) |
403 | return 1 | 422 | return 1 |
423 | except Exception as e: | ||
424 | # bitbake-server comms failure | ||
425 | early_logger = bb.msg.logger_create('bitbake', sys.stdout) | ||
426 | early_logger.fatal("Attempting to set server environment: %s", e) | ||
427 | return 1 | ||
404 | 428 | ||
405 | if params.options.quiet == 0: | 429 | if params.options.quiet == 0: |
406 | console_loglevel = loglevel | 430 | console_loglevel = loglevel |
@@ -539,6 +563,13 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
539 | except OSError: | 563 | except OSError: |
540 | pass | 564 | pass |
541 | 565 | ||
566 | # Add the logging domains specified by the user on the command line | ||
567 | for (domainarg, iterator) in groupby(params.debug_domains): | ||
568 | dlevel = len(tuple(iterator)) | ||
569 | l = logconfig["loggers"].setdefault("BitBake.%s" % domainarg, {}) | ||
570 | l["level"] = logging.DEBUG - dlevel + 1 | ||
571 | l.setdefault("handlers", []).extend(["BitBake.verbconsole"]) | ||
572 | |||
542 | conf = bb.msg.setLoggingConfig(logconfig, logconfigfile) | 573 | conf = bb.msg.setLoggingConfig(logconfig, logconfigfile) |
543 | 574 | ||
544 | if sys.stdin.isatty() and sys.stdout.isatty(): | 575 | if sys.stdin.isatty() and sys.stdout.isatty(): |
@@ -559,7 +590,12 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
559 | return | 590 | return |
560 | 591 | ||
561 | llevel, debug_domains = bb.msg.constructLogOptions() | 592 | llevel, debug_domains = bb.msg.constructLogOptions() |
562 | server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) | 593 | try: |
594 | server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) | ||
595 | except (BrokenPipeError, EOFError) as e: | ||
596 | # bitbake-server comms failure | ||
597 | logger.fatal("Attempting to set event mask: %s", e) | ||
598 | return 1 | ||
563 | 599 | ||
564 | # The logging_tree module is *extremely* helpful in debugging logging | 600 | # The logging_tree module is *extremely* helpful in debugging logging |
565 | # domains. Uncomment here to dump the logging tree when bitbake starts | 601 | # domains. Uncomment here to dump the logging tree when bitbake starts |
@@ -568,7 +604,11 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
568 | 604 | ||
569 | universe = False | 605 | universe = False |
570 | if not params.observe_only: | 606 | if not params.observe_only: |
571 | params.updateFromServer(server) | 607 | try: |
608 | params.updateFromServer(server) | ||
609 | except Exception as e: | ||
610 | logger.fatal("Fetching command line: %s", e) | ||
611 | return 1 | ||
572 | cmdline = params.parseActions() | 612 | cmdline = params.parseActions() |
573 | if not cmdline: | 613 | if not cmdline: |
574 | print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") | 614 | print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") |
@@ -579,7 +619,12 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
579 | if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]: | 619 | if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]: |
580 | universe = True | 620 | universe = True |
581 | 621 | ||
582 | ret, error = server.runCommand(cmdline['action']) | 622 | try: |
623 | ret, error = server.runCommand(cmdline['action']) | ||
624 | except (BrokenPipeError, EOFError) as e: | ||
625 | # bitbake-server comms failure | ||
626 | logger.fatal("Command '{}' failed: %s".format(cmdline), e) | ||
627 | return 1 | ||
583 | if error: | 628 | if error: |
584 | logger.error("Command '%s' failed: %s" % (cmdline, error)) | 629 | logger.error("Command '%s' failed: %s" % (cmdline, error)) |
585 | return 1 | 630 | return 1 |
@@ -597,26 +642,40 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
597 | warnings = 0 | 642 | warnings = 0 |
598 | taskfailures = [] | 643 | taskfailures = [] |
599 | 644 | ||
600 | printinterval = 5000 | 645 | printintervaldelta = 10 * 60 # 10 minutes |
601 | lastprint = time.time() | 646 | printinterval = printintervaldelta |
647 | pinginterval = 1 * 60 # 1 minute | ||
648 | lastevent = lastprint = time.time() | ||
602 | 649 | ||
603 | termfilter = tf(main, helper, console_handlers, params.options.quiet) | 650 | termfilter = tf(main, helper, console_handlers, params.options.quiet) |
604 | atexit.register(termfilter.finish) | 651 | atexit.register(termfilter.finish) |
605 | 652 | ||
606 | while True: | 653 | # shutdown levels |
654 | # 0 - normal operation | ||
655 | # 1 - no new task execution, let current running tasks finish | ||
656 | # 2 - interrupting currently executing tasks | ||
657 | # 3 - we're done, exit | ||
658 | while main.shutdown < 3: | ||
607 | try: | 659 | try: |
608 | if (lastprint + printinterval) <= time.time(): | 660 | if (lastprint + printinterval) <= time.time(): |
609 | termfilter.keepAlive(printinterval) | 661 | termfilter.keepAlive(printinterval) |
610 | printinterval += 5000 | 662 | printinterval += printintervaldelta |
611 | event = eventHandler.waitEvent(0) | 663 | event = eventHandler.waitEvent(0) |
612 | if event is None: | 664 | if event is None: |
613 | if main.shutdown > 1: | 665 | if (lastevent + pinginterval) <= time.time(): |
614 | break | 666 | ret, error = server.runCommand(["ping"]) |
667 | if error or not ret: | ||
668 | termfilter.clearFooter() | ||
669 | print("No reply after pinging server (%s, %s), exiting." % (str(error), str(ret))) | ||
670 | return_value = 3 | ||
671 | main.shutdown = 3 | ||
672 | lastevent = time.time() | ||
615 | if not parseprogress: | 673 | if not parseprogress: |
616 | termfilter.updateFooter() | 674 | termfilter.updateFooter() |
617 | event = eventHandler.waitEvent(0.25) | 675 | event = eventHandler.waitEvent(0.25) |
618 | if event is None: | 676 | if event is None: |
619 | continue | 677 | continue |
678 | lastevent = time.time() | ||
620 | helper.eventHandler(event) | 679 | helper.eventHandler(event) |
621 | if isinstance(event, bb.runqueue.runQueueExitWait): | 680 | if isinstance(event, bb.runqueue.runQueueExitWait): |
622 | if not main.shutdown: | 681 | if not main.shutdown: |
@@ -638,8 +697,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
638 | 697 | ||
639 | if isinstance(event, logging.LogRecord): | 698 | if isinstance(event, logging.LogRecord): |
640 | lastprint = time.time() | 699 | lastprint = time.time() |
641 | printinterval = 5000 | 700 | printinterval = printintervaldelta |
642 | if event.levelno >= bb.msg.BBLogFormatter.ERROR: | 701 | if event.levelno >= bb.msg.BBLogFormatter.ERRORONCE: |
643 | errors = errors + 1 | 702 | errors = errors + 1 |
644 | return_value = 1 | 703 | return_value = 1 |
645 | elif event.levelno == bb.msg.BBLogFormatter.WARNING: | 704 | elif event.levelno == bb.msg.BBLogFormatter.WARNING: |
@@ -653,10 +712,10 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
653 | continue | 712 | continue |
654 | 713 | ||
655 | # Prefix task messages with recipe/task | 714 | # Prefix task messages with recipe/task |
656 | if event.taskpid in helper.pidmap and event.levelno != bb.msg.BBLogFormatter.PLAIN: | 715 | if event.taskpid in helper.pidmap and event.levelno not in [bb.msg.BBLogFormatter.PLAIN, bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]: |
657 | taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]] | 716 | taskinfo = helper.running_tasks[helper.pidmap[event.taskpid]] |
658 | event.msg = taskinfo['title'] + ': ' + event.msg | 717 | event.msg = taskinfo['title'] + ': ' + event.msg |
659 | if hasattr(event, 'fn'): | 718 | if hasattr(event, 'fn') and event.levelno not in [bb.msg.BBLogFormatter.WARNONCE, bb.msg.BBLogFormatter.ERRORONCE]: |
660 | event.msg = event.fn + ': ' + event.msg | 719 | event.msg = event.fn + ': ' + event.msg |
661 | logging.getLogger(event.name).handle(event) | 720 | logging.getLogger(event.name).handle(event) |
662 | continue | 721 | continue |
@@ -721,15 +780,15 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
721 | if event.error: | 780 | if event.error: |
722 | errors = errors + 1 | 781 | errors = errors + 1 |
723 | logger.error(str(event)) | 782 | logger.error(str(event)) |
724 | main.shutdown = 2 | 783 | main.shutdown = 3 |
725 | continue | 784 | continue |
726 | if isinstance(event, bb.command.CommandExit): | 785 | if isinstance(event, bb.command.CommandExit): |
727 | if not return_value: | 786 | if not return_value: |
728 | return_value = event.exitcode | 787 | return_value = event.exitcode |
729 | main.shutdown = 2 | 788 | main.shutdown = 3 |
730 | continue | 789 | continue |
731 | if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): | 790 | if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)): |
732 | main.shutdown = 2 | 791 | main.shutdown = 3 |
733 | continue | 792 | continue |
734 | if isinstance(event, bb.event.MultipleProviders): | 793 | if isinstance(event, bb.event.MultipleProviders): |
735 | logger.info(str(event)) | 794 | logger.info(str(event)) |
@@ -745,7 +804,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
745 | continue | 804 | continue |
746 | 805 | ||
747 | if isinstance(event, bb.runqueue.sceneQueueTaskStarted): | 806 | if isinstance(event, bb.runqueue.sceneQueueTaskStarted): |
748 | logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring)) | 807 | logger.info("Running setscene task %d of %d (%s)" % (event.stats.setscene_covered + event.stats.setscene_active + event.stats.setscene_notcovered + 1, event.stats.setscene_total, event.taskstring)) |
749 | continue | 808 | continue |
750 | 809 | ||
751 | if isinstance(event, bb.runqueue.runQueueTaskStarted): | 810 | if isinstance(event, bb.runqueue.runQueueTaskStarted): |
@@ -814,15 +873,26 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
814 | 873 | ||
815 | logger.error("Unknown event: %s", event) | 874 | logger.error("Unknown event: %s", event) |
816 | 875 | ||
876 | except (BrokenPipeError, EOFError) as e: | ||
877 | # bitbake-server comms failure, don't attempt further comms and exit | ||
878 | logger.fatal("Executing event: %s", e) | ||
879 | return_value = 1 | ||
880 | errors = errors + 1 | ||
881 | main.shutdown = 3 | ||
817 | except EnvironmentError as ioerror: | 882 | except EnvironmentError as ioerror: |
818 | termfilter.clearFooter() | 883 | termfilter.clearFooter() |
819 | # ignore interrupted io | 884 | # ignore interrupted io |
820 | if ioerror.args[0] == 4: | 885 | if ioerror.args[0] == 4: |
821 | continue | 886 | continue |
822 | sys.stderr.write(str(ioerror)) | 887 | sys.stderr.write(str(ioerror)) |
823 | if not params.observe_only: | ||
824 | _, error = server.runCommand(["stateForceShutdown"]) | ||
825 | main.shutdown = 2 | 888 | main.shutdown = 2 |
889 | if not params.observe_only: | ||
890 | try: | ||
891 | _, error = server.runCommand(["stateForceShutdown"]) | ||
892 | except (BrokenPipeError, EOFError) as e: | ||
893 | # bitbake-server comms failure, don't attempt further comms and exit | ||
894 | logger.fatal("Unable to force shutdown: %s", e) | ||
895 | main.shutdown = 3 | ||
826 | except KeyboardInterrupt: | 896 | except KeyboardInterrupt: |
827 | termfilter.clearFooter() | 897 | termfilter.clearFooter() |
828 | if params.observe_only: | 898 | if params.observe_only: |
@@ -831,9 +901,13 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
831 | 901 | ||
832 | def state_force_shutdown(): | 902 | def state_force_shutdown(): |
833 | print("\nSecond Keyboard Interrupt, stopping...\n") | 903 | print("\nSecond Keyboard Interrupt, stopping...\n") |
834 | _, error = server.runCommand(["stateForceShutdown"]) | 904 | try: |
835 | if error: | 905 | _, error = server.runCommand(["stateForceShutdown"]) |
836 | logger.error("Unable to cleanly stop: %s" % error) | 906 | if error: |
907 | logger.error("Unable to cleanly stop: %s" % error) | ||
908 | except (BrokenPipeError, EOFError) as e: | ||
909 | # bitbake-server comms failure | ||
910 | logger.fatal("Unable to cleanly stop: %s", e) | ||
837 | 911 | ||
838 | if not params.observe_only and main.shutdown == 1: | 912 | if not params.observe_only and main.shutdown == 1: |
839 | state_force_shutdown() | 913 | state_force_shutdown() |
@@ -846,17 +920,24 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
846 | _, error = server.runCommand(["stateShutdown"]) | 920 | _, error = server.runCommand(["stateShutdown"]) |
847 | if error: | 921 | if error: |
848 | logger.error("Unable to cleanly shutdown: %s" % error) | 922 | logger.error("Unable to cleanly shutdown: %s" % error) |
923 | except (BrokenPipeError, EOFError) as e: | ||
924 | # bitbake-server comms failure | ||
925 | logger.fatal("Unable to cleanly shutdown: %s", e) | ||
849 | except KeyboardInterrupt: | 926 | except KeyboardInterrupt: |
850 | state_force_shutdown() | 927 | state_force_shutdown() |
851 | 928 | ||
852 | main.shutdown = main.shutdown + 1 | 929 | main.shutdown = main.shutdown + 1 |
853 | pass | ||
854 | except Exception as e: | 930 | except Exception as e: |
855 | import traceback | 931 | import traceback |
856 | sys.stderr.write(traceback.format_exc()) | 932 | sys.stderr.write(traceback.format_exc()) |
857 | if not params.observe_only: | ||
858 | _, error = server.runCommand(["stateForceShutdown"]) | ||
859 | main.shutdown = 2 | 933 | main.shutdown = 2 |
934 | if not params.observe_only: | ||
935 | try: | ||
936 | _, error = server.runCommand(["stateForceShutdown"]) | ||
937 | except (BrokenPipeError, EOFError) as e: | ||
938 | # bitbake-server comms failure, don't attempt further comms and exit | ||
939 | logger.fatal("Unable to force shutdown: %s", e) | ||
940 | main.shudown = 3 | ||
860 | return_value = 1 | 941 | return_value = 1 |
861 | try: | 942 | try: |
862 | termfilter.clearFooter() | 943 | termfilter.clearFooter() |
@@ -867,11 +948,11 @@ def main(server, eventHandler, params, tf = TerminalFilter): | |||
867 | for failure in taskfailures: | 948 | for failure in taskfailures: |
868 | summary += "\n %s" % failure | 949 | summary += "\n %s" % failure |
869 | if warnings: | 950 | if warnings: |
870 | summary += pluralise("\nSummary: There was %s WARNING message shown.", | 951 | summary += pluralise("\nSummary: There was %s WARNING message.", |
871 | "\nSummary: There were %s WARNING messages shown.", warnings) | 952 | "\nSummary: There were %s WARNING messages.", warnings) |
872 | if return_value and errors: | 953 | if return_value and errors: |
873 | summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.", | 954 | summary += pluralise("\nSummary: There was %s ERROR message, returning a non-zero exit code.", |
874 | "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors) | 955 | "\nSummary: There were %s ERROR messages, returning a non-zero exit code.", errors) |
875 | if summary and params.options.quiet == 0: | 956 | if summary and params.options.quiet == 0: |
876 | print(summary) | 957 | print(summary) |
877 | 958 | ||
diff --git a/bitbake/lib/bb/ui/ncurses.py b/bitbake/lib/bb/ui/ncurses.py index cf1c876a51..18a706547a 100644 --- a/bitbake/lib/bb/ui/ncurses.py +++ b/bitbake/lib/bb/ui/ncurses.py | |||
@@ -227,6 +227,9 @@ class NCursesUI: | |||
227 | shutdown = 0 | 227 | shutdown = 0 |
228 | 228 | ||
229 | try: | 229 | try: |
230 | if not params.observe_only: | ||
231 | params.updateToServer(server, os.environ.copy()) | ||
232 | |||
230 | params.updateFromServer(server) | 233 | params.updateFromServer(server) |
231 | cmdline = params.parseActions() | 234 | cmdline = params.parseActions() |
232 | if not cmdline: | 235 | if not cmdline: |
diff --git a/bitbake/lib/bb/ui/taskexp.py b/bitbake/lib/bb/ui/taskexp.py index 2b246710ca..bedfd69b09 100644 --- a/bitbake/lib/bb/ui/taskexp.py +++ b/bitbake/lib/bb/ui/taskexp.py | |||
@@ -8,6 +8,7 @@ | |||
8 | # | 8 | # |
9 | 9 | ||
10 | import sys | 10 | import sys |
11 | import traceback | ||
11 | 12 | ||
12 | try: | 13 | try: |
13 | import gi | 14 | import gi |
@@ -176,7 +177,7 @@ class gtkthread(threading.Thread): | |||
176 | quit = threading.Event() | 177 | quit = threading.Event() |
177 | def __init__(self, shutdown): | 178 | def __init__(self, shutdown): |
178 | threading.Thread.__init__(self) | 179 | threading.Thread.__init__(self) |
179 | self.setDaemon(True) | 180 | self.daemon = True |
180 | self.shutdown = shutdown | 181 | self.shutdown = shutdown |
181 | if not Gtk.init_check()[0]: | 182 | if not Gtk.init_check()[0]: |
182 | sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n") | 183 | sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n") |
@@ -196,6 +197,7 @@ def main(server, eventHandler, params): | |||
196 | gtkgui.start() | 197 | gtkgui.start() |
197 | 198 | ||
198 | try: | 199 | try: |
200 | params.updateToServer(server, os.environ.copy()) | ||
199 | params.updateFromServer(server) | 201 | params.updateFromServer(server) |
200 | cmdline = params.parseActions() | 202 | cmdline = params.parseActions() |
201 | if not cmdline: | 203 | if not cmdline: |
@@ -218,6 +220,9 @@ def main(server, eventHandler, params): | |||
218 | except client.Fault as x: | 220 | except client.Fault as x: |
219 | print("XMLRPC Fault getting commandline:\n %s" % x) | 221 | print("XMLRPC Fault getting commandline:\n %s" % x) |
220 | return | 222 | return |
223 | except Exception as e: | ||
224 | print("Exception in startup:\n %s" % traceback.format_exc()) | ||
225 | return | ||
221 | 226 | ||
222 | if gtkthread.quit.isSet(): | 227 | if gtkthread.quit.isSet(): |
223 | return | 228 | return |
diff --git a/bitbake/lib/bb/ui/taskexp_ncurses.py b/bitbake/lib/bb/ui/taskexp_ncurses.py new file mode 100755 index 0000000000..ea94a4987f --- /dev/null +++ b/bitbake/lib/bb/ui/taskexp_ncurses.py | |||
@@ -0,0 +1,1511 @@ | |||
1 | # | ||
2 | # BitBake Graphical ncurses-based Dependency Explorer | ||
3 | # * Based on the GTK implementation | ||
4 | # * Intended to run on any Linux host | ||
5 | # | ||
6 | # Copyright (C) 2007 Ross Burton | ||
7 | # Copyright (C) 2007 - 2008 Richard Purdie | ||
8 | # Copyright (C) 2022 - 2024 David Reyna | ||
9 | # | ||
10 | # SPDX-License-Identifier: GPL-2.0-only | ||
11 | # | ||
12 | |||
13 | # | ||
14 | # Execution example: | ||
15 | # $ bitbake -g -u taskexp_ncurses zlib acl | ||
16 | # | ||
17 | # Self-test example (executes a script of GUI actions): | ||
18 | # $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_ncurses zlib acl | ||
19 | # ... | ||
20 | # $ echo $? | ||
21 | # 0 | ||
22 | # $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_ncurses zlib acl foo | ||
23 | # ERROR: Nothing PROVIDES 'foo'. Close matches: | ||
24 | # ofono | ||
25 | # $ echo $? | ||
26 | # 1 | ||
27 | # | ||
28 | # Self-test with no terminal example (only tests dependency fetch from bitbake): | ||
29 | # $ TASK_EXP_UNIT_TEST_NOTERM=1 bitbake -g -u taskexp_ncurses quilt | ||
30 | # $ echo $? | ||
31 | # 0 | ||
32 | # | ||
33 | # Features: | ||
34 | # * Ncurses is used for the presentation layer. Only the 'curses' | ||
35 | # library is used (none of the extension libraries), plus only | ||
36 | # one main screen is used (no sub-windows) | ||
37 | # * Uses the 'generateDepTreeEvent' bitbake event to fetch the | ||
38 | # dynamic dependency data based on passed recipes | ||
39 | # * Computes and provides reverse dependencies | ||
40 | # * Supports task sorting on: | ||
41 | # (a) Task dependency order within each recipe | ||
42 | # (b) Pure alphabetical order | ||
43 | # (c) Provisions for third sort order (bitbake order?) | ||
44 | # * The 'Filter' does a "*string*" wildcard filter on tasks in the | ||
45 | # main window, dynamically re-ordering and re-centering the content | ||
46 | # * A 'Print' function exports the selected task or its whole recipe | ||
47 | # task set to the default file "taskdep.txt" | ||
48 | # * Supports a progress bar for bitbake loads and file printing | ||
49 | # * Line art for box drawing supported, ASCII art an alernative | ||
50 | # * No horizontal scrolling support. Selected task's full name | ||
51 | # shown in bottom bar | ||
52 | # * Dynamically catches terminals that are (or become) too small | ||
53 | # * Exception to insure return to normal terminal on errors | ||
54 | # * Debugging support, self test option | ||
55 | # | ||
56 | |||
57 | import sys | ||
58 | import traceback | ||
59 | import curses | ||
60 | import re | ||
61 | import time | ||
62 | |||
63 | # Bitbake server support | ||
64 | import threading | ||
65 | from xmlrpc import client | ||
66 | import bb | ||
67 | import bb.event | ||
68 | |||
69 | # Dependency indexes (depends_model) | ||
70 | (TYPE_DEP, TYPE_RDEP) = (0, 1) | ||
71 | DEPENDS_TYPE = 0 | ||
72 | DEPENDS_TASK = 1 | ||
73 | DEPENDS_DEPS = 2 | ||
74 | # Task indexes (task_list) | ||
75 | TASK_NAME = 0 | ||
76 | TASK_PRIMARY = 1 | ||
77 | TASK_SORT_ALPHA = 2 | ||
78 | TASK_SORT_DEPS = 3 | ||
79 | TASK_SORT_BITBAKE = 4 | ||
80 | # Sort options (default is SORT_DEPS) | ||
81 | SORT_ALPHA = 0 | ||
82 | SORT_DEPS = 1 | ||
83 | SORT_BITBAKE_ENABLE = False # NOTE: future sort | ||
84 | SORT_BITBAKE = 2 | ||
85 | sort_model = SORT_DEPS | ||
86 | # Print options | ||
87 | PRINT_MODEL_1 = 0 | ||
88 | PRINT_MODEL_2 = 1 | ||
89 | print_model = PRINT_MODEL_2 | ||
90 | print_file_name = "taskdep_print.log" | ||
91 | print_file_backup_name = "taskdep_print_backup.log" | ||
92 | is_printed = False | ||
93 | is_filter = False | ||
94 | |||
95 | # Standard (and backup) key mappings | ||
96 | CHAR_NUL = 0 # Used as self-test nop char | ||
97 | CHAR_BS_H = 8 # Alternate backspace key | ||
98 | CHAR_TAB = 9 | ||
99 | CHAR_RETURN = 10 | ||
100 | CHAR_ESCAPE = 27 | ||
101 | CHAR_UP = ord('{') # Used as self-test ASCII char | ||
102 | CHAR_DOWN = ord('}') # Used as self-test ASCII char | ||
103 | |||
104 | # Color_pair IDs | ||
105 | CURSES_NORMAL = 0 | ||
106 | CURSES_HIGHLIGHT = 1 | ||
107 | CURSES_WARNING = 2 | ||
108 | |||
109 | |||
110 | ################################################# | ||
111 | ### Debugging support | ||
112 | ### | ||
113 | |||
114 | verbose = False | ||
115 | |||
116 | # Debug: message display slow-step through display update issues | ||
117 | def alert(msg,screen): | ||
118 | if msg: | ||
119 | screen.addstr(0, 10, '[%-4s]' % msg) | ||
120 | screen.refresh(); | ||
121 | curses.napms(2000) | ||
122 | else: | ||
123 | if do_line_art: | ||
124 | for i in range(10, 24): | ||
125 | screen.addch(0, i, curses.ACS_HLINE) | ||
126 | else: | ||
127 | screen.addstr(0, 10, '-' * 14) | ||
128 | screen.refresh(); | ||
129 | |||
130 | # Debug: display edge conditions on frame movements | ||
131 | def debug_frame(nbox_ojb): | ||
132 | if verbose: | ||
133 | nbox_ojb.screen.addstr(0, 50, '[I=%2d,O=%2d,S=%3s,H=%2d,M=%4d]' % ( | ||
134 | nbox_ojb.cursor_index, | ||
135 | nbox_ojb.cursor_offset, | ||
136 | nbox_ojb.scroll_offset, | ||
137 | nbox_ojb.inside_height, | ||
138 | len(nbox_ojb.task_list), | ||
139 | )) | ||
140 | nbox_ojb.screen.refresh(); | ||
141 | |||
142 | # | ||
143 | # Unit test (assumes that 'quilt-native' is always present) | ||
144 | # | ||
145 | |||
146 | unit_test = os.environ.get('TASK_EXP_UNIT_TEST') | ||
147 | unit_test_cmnds=[ | ||
148 | '# Default selected task in primary box', | ||
149 | 'tst_selected=<TASK>.do_recipe_qa', | ||
150 | '# Default selected task in deps', | ||
151 | 'tst_entry=<TAB>', | ||
152 | 'tst_selected=', | ||
153 | '# Default selected task in rdeps', | ||
154 | 'tst_entry=<TAB>', | ||
155 | 'tst_selected=<TASK>.do_fetch', | ||
156 | "# Test 'select' back to primary box", | ||
157 | 'tst_entry=<CR>', | ||
158 | '#tst_entry=<DOWN>', # optional injected error | ||
159 | 'tst_selected=<TASK>.do_fetch', | ||
160 | '# Check filter', | ||
161 | 'tst_entry=/uilt-nativ/', | ||
162 | 'tst_selected=quilt-native.do_recipe_qa', | ||
163 | '# Check print', | ||
164 | 'tst_entry=p', | ||
165 | 'tst_printed=quilt-native.do_fetch', | ||
166 | '#tst_printed=quilt-foo.do_nothing', # optional injected error | ||
167 | '# Done!', | ||
168 | 'tst_entry=q', | ||
169 | ] | ||
170 | unit_test_idx=0 | ||
171 | unit_test_command_chars='' | ||
172 | unit_test_results=[] | ||
173 | def unit_test_action(active_package): | ||
174 | global unit_test_idx | ||
175 | global unit_test_command_chars | ||
176 | global unit_test_results | ||
177 | ret = CHAR_NUL | ||
178 | if unit_test_command_chars: | ||
179 | ch = unit_test_command_chars[0] | ||
180 | unit_test_command_chars = unit_test_command_chars[1:] | ||
181 | time.sleep(0.5) | ||
182 | ret = ord(ch) | ||
183 | else: | ||
184 | line = unit_test_cmnds[unit_test_idx] | ||
185 | unit_test_idx += 1 | ||
186 | line = re.sub('#.*', '', line).strip() | ||
187 | line = line.replace('<TASK>',active_package.primary[0]) | ||
188 | line = line.replace('<TAB>','\t').replace('<CR>','\n') | ||
189 | line = line.replace('<UP>','{').replace('<DOWN>','}') | ||
190 | if not line: line = 'nop=nop' | ||
191 | cmnd,value = line.split('=') | ||
192 | if cmnd == 'tst_entry': | ||
193 | unit_test_command_chars = value | ||
194 | elif cmnd == 'tst_selected': | ||
195 | active_selected = active_package.get_selected() | ||
196 | if active_selected != value: | ||
197 | unit_test_results.append("ERROR:SELFTEST:expected '%s' but got '%s' (NOTE:bitbake may have changed)" % (value,active_selected)) | ||
198 | ret = ord('Q') | ||
199 | else: | ||
200 | unit_test_results.append("Pass:SELFTEST:found '%s'" % (value)) | ||
201 | elif cmnd == 'tst_printed': | ||
202 | result = os.system('grep %s %s' % (value,print_file_name)) | ||
203 | if result: | ||
204 | unit_test_results.append("ERROR:PRINTTEST:expected '%s' in '%s'" % (value,print_file_name)) | ||
205 | ret = ord('Q') | ||
206 | else: | ||
207 | unit_test_results.append("Pass:PRINTTEST:found '%s'" % (value)) | ||
208 | # Return the action (CHAR_NUL for no action til next round) | ||
209 | return(ret) | ||
210 | |||
211 | # Unit test without an interative terminal (e.g. ptest) | ||
212 | unit_test_noterm = os.environ.get('TASK_EXP_UNIT_TEST_NOTERM') | ||
213 | |||
214 | |||
215 | ################################################# | ||
216 | ### Window frame rendering | ||
217 | ### | ||
218 | ### By default, use the normal line art. Since | ||
219 | ### these extended characters are not ASCII, one | ||
220 | ### must use the ncursus API to render them | ||
221 | ### The alternate ASCII line art set is optionally | ||
222 | ### available via the 'do_line_art' flag | ||
223 | |||
224 | # By default, render frames using line art | ||
225 | do_line_art = True | ||
226 | |||
227 | # ASCII render set option | ||
228 | CHAR_HBAR = '-' | ||
229 | CHAR_VBAR = '|' | ||
230 | CHAR_UL_CORNER = '/' | ||
231 | CHAR_UR_CORNER = '\\' | ||
232 | CHAR_LL_CORNER = '\\' | ||
233 | CHAR_LR_CORNER = '/' | ||
234 | |||
235 | # Box frame drawing with line-art | ||
236 | def line_art_frame(box): | ||
237 | x = box.base_x | ||
238 | y = box.base_y | ||
239 | w = box.width | ||
240 | h = box.height + 1 | ||
241 | |||
242 | if do_line_art: | ||
243 | for i in range(1, w - 1): | ||
244 | box.screen.addch(y, x + i, curses.ACS_HLINE, box.color) | ||
245 | box.screen.addch(y + h - 1, x + i, curses.ACS_HLINE, box.color) | ||
246 | body_line = "%s" % (' ' * (w - 2)) | ||
247 | for i in range(1, h - 1): | ||
248 | box.screen.addch(y + i, x, curses.ACS_VLINE, box.color) | ||
249 | box.screen.addstr(y + i, x + 1, body_line, box.color) | ||
250 | box.screen.addch(y + i, x + w - 1, curses.ACS_VLINE, box.color) | ||
251 | box.screen.addch(y, x, curses.ACS_ULCORNER, box.color) | ||
252 | box.screen.addch(y, x + w - 1, curses.ACS_URCORNER, box.color) | ||
253 | box.screen.addch(y + h - 1, x, curses.ACS_LLCORNER, box.color) | ||
254 | box.screen.addch(y + h - 1, x + w - 1, curses.ACS_LRCORNER, box.color) | ||
255 | else: | ||
256 | top_line = "%s%s%s" % (CHAR_UL_CORNER,CHAR_HBAR * (w - 2),CHAR_UR_CORNER) | ||
257 | body_line = "%s%s%s" % (CHAR_VBAR,' ' * (w - 2),CHAR_VBAR) | ||
258 | bot_line = "%s%s%s" % (CHAR_UR_CORNER,CHAR_HBAR * (w - 2),CHAR_UL_CORNER) | ||
259 | tag_line = "%s%s%s" % ('[',CHAR_HBAR * (w - 2),']') | ||
260 | # Top bar | ||
261 | box.screen.addstr(y, x, top_line) | ||
262 | # Middle frame | ||
263 | for i in range(1, (h - 1)): | ||
264 | box.screen.addstr(y+i, x, body_line) | ||
265 | # Bottom bar | ||
266 | box.screen.addstr(y + (h - 1), x, bot_line) | ||
267 | |||
268 | # Connect the separate boxes | ||
269 | def line_art_fixup(box): | ||
270 | if do_line_art: | ||
271 | box.screen.addch(box.base_y+2, box.base_x, curses.ACS_LTEE, box.color) | ||
272 | box.screen.addch(box.base_y+2, box.base_x+box.width-1, curses.ACS_RTEE, box.color) | ||
273 | |||
274 | |||
275 | ################################################# | ||
276 | ### Ncurses box object : box frame object to display | ||
277 | ### and manage a sub-window's display elements | ||
278 | ### using basic ncurses | ||
279 | ### | ||
280 | ### Supports: | ||
281 | ### * Frame drawing, content (re)drawing | ||
282 | ### * Content scrolling via ArrowUp, ArrowDn, PgUp, PgDN, | ||
283 | ### * Highlighting for active selected item | ||
284 | ### * Content sorting based on selected sort model | ||
285 | ### | ||
286 | |||
287 | class NBox(): | ||
288 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
289 | # Box description | ||
290 | self.screen = screen | ||
291 | self.label = label | ||
292 | self.primary = primary | ||
293 | self.color = curses.color_pair(CURSES_NORMAL) if screen else None | ||
294 | # Box boundaries | ||
295 | self.base_x = base_x | ||
296 | self.base_y = base_y | ||
297 | self.width = width | ||
298 | self.height = height | ||
299 | # Cursor/scroll management | ||
300 | self.cursor_enable = False | ||
301 | self.cursor_index = 0 # Absolute offset | ||
302 | self.cursor_offset = 0 # Frame centric offset | ||
303 | self.scroll_offset = 0 # Frame centric offset | ||
304 | # Box specific content | ||
305 | # Format of each entry is [package_name,is_primary_recipe,alpha_sort_key,deps_sort_key] | ||
306 | self.task_list = [] | ||
307 | |||
308 | @property | ||
309 | def inside_width(self): | ||
310 | return(self.width-2) | ||
311 | |||
312 | @property | ||
313 | def inside_height(self): | ||
314 | return(self.height-2) | ||
315 | |||
316 | # Populate the box's content, include the sort mappings and is_primary flag | ||
317 | def task_list_append(self,task_name,dep): | ||
318 | task_sort_alpha = task_name | ||
319 | task_sort_deps = dep.get_dep_sort(task_name) | ||
320 | is_primary = False | ||
321 | for primary in self.primary: | ||
322 | if task_name.startswith(primary+'.'): | ||
323 | is_primary = True | ||
324 | if SORT_BITBAKE_ENABLE: | ||
325 | task_sort_bitbake = dep.get_bb_sort(task_name) | ||
326 | self.task_list.append([task_name,is_primary,task_sort_alpha,task_sort_deps,task_sort_bitbake]) | ||
327 | else: | ||
328 | self.task_list.append([task_name,is_primary,task_sort_alpha,task_sort_deps]) | ||
329 | |||
330 | def reset(self): | ||
331 | self.task_list = [] | ||
332 | self.cursor_index = 0 # Absolute offset | ||
333 | self.cursor_offset = 0 # Frame centric offset | ||
334 | self.scroll_offset = 0 # Frame centric offset | ||
335 | |||
336 | # Sort the box's content based on the current sort model | ||
337 | def sort(self): | ||
338 | if SORT_ALPHA == sort_model: | ||
339 | self.task_list.sort(key = lambda x: x[TASK_SORT_ALPHA]) | ||
340 | elif SORT_DEPS == sort_model: | ||
341 | self.task_list.sort(key = lambda x: x[TASK_SORT_DEPS]) | ||
342 | elif SORT_BITBAKE == sort_model: | ||
343 | self.task_list.sort(key = lambda x: x[TASK_SORT_BITBAKE]) | ||
344 | |||
345 | # The target package list (to hightlight), from the command line | ||
346 | def set_primary(self,primary): | ||
347 | self.primary = primary | ||
348 | |||
349 | # Draw the box's outside frame | ||
350 | def draw_frame(self): | ||
351 | line_art_frame(self) | ||
352 | # Title | ||
353 | self.screen.addstr(self.base_y, | ||
354 | (self.base_x + (self.width//2))-((len(self.label)+2)//2), | ||
355 | '['+self.label+']') | ||
356 | self.screen.refresh() | ||
357 | |||
358 | # Draw the box's inside text content | ||
359 | def redraw(self): | ||
360 | task_list_len = len(self.task_list) | ||
361 | # Middle frame | ||
362 | body_line = "%s" % (' ' * (self.inside_width-1) ) | ||
363 | for i in range(0,self.inside_height+1): | ||
364 | if i < (task_list_len + self.scroll_offset): | ||
365 | str_ctl = "%%-%ss" % (self.width-3) | ||
366 | # Safety assert | ||
367 | if (i + self.scroll_offset) >= task_list_len: | ||
368 | alert("REDRAW:%2d,%4d,%4d" % (i,self.scroll_offset,task_list_len),self.screen) | ||
369 | break | ||
370 | |||
371 | task_obj = self.task_list[i + self.scroll_offset] | ||
372 | task = task_obj[TASK_NAME][:self.inside_width-1] | ||
373 | task_primary = task_obj[TASK_PRIMARY] | ||
374 | |||
375 | if task_primary: | ||
376 | line = str_ctl % task[:self.inside_width-1] | ||
377 | self.screen.addstr(self.base_y+1+i, self.base_x+2, line, curses.A_BOLD) | ||
378 | else: | ||
379 | line = str_ctl % task[:self.inside_width-1] | ||
380 | self.screen.addstr(self.base_y+1+i, self.base_x+2, line) | ||
381 | else: | ||
382 | line = "%s" % (' ' * (self.inside_width-1) ) | ||
383 | self.screen.addstr(self.base_y+1+i, self.base_x+2, line) | ||
384 | self.screen.refresh() | ||
385 | |||
386 | # Show the current selected task over the bottom of the frame | ||
387 | def show_selected(self,selected_task): | ||
388 | if not selected_task: | ||
389 | selected_task = self.get_selected() | ||
390 | tag_line = "%s%s%s" % ('[',CHAR_HBAR * (self.width-2),']') | ||
391 | self.screen.addstr(self.base_y + self.height, self.base_x, tag_line) | ||
392 | self.screen.addstr(self.base_y + self.height, | ||
393 | (self.base_x + (self.width//2))-((len(selected_task)+2)//2), | ||
394 | '['+selected_task+']') | ||
395 | self.screen.refresh() | ||
396 | |||
397 | # Load box with new table of content | ||
398 | def update_content(self,task_list): | ||
399 | self.task_list = task_list | ||
400 | if self.cursor_enable: | ||
401 | cursor_update(turn_on=False) | ||
402 | self.cursor_index = 0 | ||
403 | self.cursor_offset = 0 | ||
404 | self.scroll_offset = 0 | ||
405 | self.redraw() | ||
406 | if self.cursor_enable: | ||
407 | cursor_update(turn_on=True) | ||
408 | |||
409 | # Manage the box's highlighted task and blinking cursor character | ||
410 | def cursor_on(self,is_on): | ||
411 | self.cursor_enable = is_on | ||
412 | self.cursor_update(is_on) | ||
413 | |||
414 | # High-light the current pointed package, normal for released packages | ||
415 | def cursor_update(self,turn_on=True): | ||
416 | str_ctl = "%%-%ss" % (self.inside_width-1) | ||
417 | try: | ||
418 | if len(self.task_list): | ||
419 | task_obj = self.task_list[self.cursor_index] | ||
420 | task = task_obj[TASK_NAME][:self.inside_width-1] | ||
421 | task_primary = task_obj[TASK_PRIMARY] | ||
422 | task_font = curses.A_BOLD if task_primary else 0 | ||
423 | else: | ||
424 | task = '' | ||
425 | task_font = 0 | ||
426 | except Exception as e: | ||
427 | alert("CURSOR_UPDATE:%s" % (e),self.screen) | ||
428 | return | ||
429 | if turn_on: | ||
430 | self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+1,">", curses.color_pair(CURSES_HIGHLIGHT) | curses.A_BLINK) | ||
431 | self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+2,str_ctl % task, curses.color_pair(CURSES_HIGHLIGHT) | task_font) | ||
432 | else: | ||
433 | self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+1," ") | ||
434 | self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+2,str_ctl % task, task_font) | ||
435 | |||
436 | # Down arrow | ||
437 | def line_down(self): | ||
438 | if len(self.task_list) <= (self.cursor_index+1): | ||
439 | return | ||
440 | self.cursor_update(turn_on=False) | ||
441 | self.cursor_index += 1 | ||
442 | self.cursor_offset += 1 | ||
443 | if self.cursor_offset > (self.inside_height): | ||
444 | self.cursor_offset -= 1 | ||
445 | self.scroll_offset += 1 | ||
446 | self.redraw() | ||
447 | self.cursor_update(turn_on=True) | ||
448 | debug_frame(self) | ||
449 | |||
450 | # Up arrow | ||
451 | def line_up(self): | ||
452 | if 0 > (self.cursor_index-1): | ||
453 | return | ||
454 | self.cursor_update(turn_on=False) | ||
455 | self.cursor_index -= 1 | ||
456 | self.cursor_offset -= 1 | ||
457 | if self.cursor_offset < 0: | ||
458 | self.cursor_offset += 1 | ||
459 | self.scroll_offset -= 1 | ||
460 | self.redraw() | ||
461 | self.cursor_update(turn_on=True) | ||
462 | debug_frame(self) | ||
463 | |||
464 | # Page down | ||
465 | def page_down(self): | ||
466 | max_task = len(self.task_list)-1 | ||
467 | if max_task < self.inside_height: | ||
468 | return | ||
469 | self.cursor_update(turn_on=False) | ||
470 | self.cursor_index += 10 | ||
471 | self.cursor_index = min(self.cursor_index,max_task) | ||
472 | self.cursor_offset = min(self.inside_height,self.cursor_index) | ||
473 | self.scroll_offset = self.cursor_index - self.cursor_offset | ||
474 | self.redraw() | ||
475 | self.cursor_update(turn_on=True) | ||
476 | debug_frame(self) | ||
477 | |||
478 | # Page up | ||
479 | def page_up(self): | ||
480 | max_task = len(self.task_list)-1 | ||
481 | if max_task < self.inside_height: | ||
482 | return | ||
483 | self.cursor_update(turn_on=False) | ||
484 | self.cursor_index -= 10 | ||
485 | self.cursor_index = max(self.cursor_index,0) | ||
486 | self.cursor_offset = max(0, self.inside_height - (max_task - self.cursor_index)) | ||
487 | self.scroll_offset = self.cursor_index - self.cursor_offset | ||
488 | self.redraw() | ||
489 | self.cursor_update(turn_on=True) | ||
490 | debug_frame(self) | ||
491 | |||
492 | # Return the currently selected task name for this box | ||
493 | def get_selected(self): | ||
494 | if self.task_list: | ||
495 | return(self.task_list[self.cursor_index][TASK_NAME]) | ||
496 | else: | ||
497 | return('') | ||
498 | |||
499 | ################################################# | ||
500 | ### The helper sub-windows | ||
501 | ### | ||
502 | |||
503 | # Show persistent help at the top of the screen | ||
504 | class HelpBarView(NBox): | ||
505 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
506 | super(HelpBarView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
507 | |||
508 | def show_help(self,show): | ||
509 | self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.inside_width)) | ||
510 | if show: | ||
511 | help = "Help='?' Filter='/' NextBox=<Tab> Select=<Enter> Print='p','P' Quit='q'" | ||
512 | bar_size = self.inside_width - 5 - len(help) | ||
513 | self.screen.addstr(self.base_y,self.base_x+((self.inside_width-len(help))//2), help) | ||
514 | self.screen.refresh() | ||
515 | |||
516 | # Pop up a detailed Help box | ||
517 | class HelpBoxView(NBox): | ||
518 | def __init__(self, screen, label, primary, base_x, base_y, width, height, dep): | ||
519 | super(HelpBoxView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
520 | self.x_pos = 0 | ||
521 | self.y_pos = 0 | ||
522 | self.dep = dep | ||
523 | |||
524 | # Instantial the pop-up help box | ||
525 | def show_help(self,show): | ||
526 | self.x_pos = self.base_x + 4 | ||
527 | self.y_pos = self.base_y + 2 | ||
528 | |||
529 | def add_line(line): | ||
530 | if line: | ||
531 | self.screen.addstr(self.y_pos,self.x_pos,line) | ||
532 | self.y_pos += 1 | ||
533 | |||
534 | # Gather some statisics | ||
535 | dep_count = 0 | ||
536 | rdep_count = 0 | ||
537 | for task_obj in self.dep.depends_model: | ||
538 | if TYPE_DEP == task_obj[DEPENDS_TYPE]: | ||
539 | dep_count += 1 | ||
540 | elif TYPE_RDEP == task_obj[DEPENDS_TYPE]: | ||
541 | rdep_count += 1 | ||
542 | |||
543 | self.draw_frame() | ||
544 | line_art_fixup(self.dep) | ||
545 | add_line("Quit : 'q' ") | ||
546 | add_line("Filter task names : '/'") | ||
547 | add_line("Tab to next box : <Tab>") | ||
548 | add_line("Select a task : <Enter>") | ||
549 | add_line("Print task's deps : 'p'") | ||
550 | add_line("Print recipe's deps : 'P'") | ||
551 | add_line(" -> '%s'" % print_file_name) | ||
552 | add_line("Sort toggle : 's'") | ||
553 | add_line(" %s Recipe inner-depends order" % ('->' if (SORT_DEPS == sort_model) else '- ')) | ||
554 | add_line(" %s Alpha-numeric order" % ('->' if (SORT_ALPHA == sort_model) else '- ')) | ||
555 | if SORT_BITBAKE_ENABLE: | ||
556 | add_line(" %s Bitbake order" % ('->' if (TASK_SORT_BITBAKE == sort_model) else '- ')) | ||
557 | add_line("Alternate backspace : <CTRL-H>") | ||
558 | add_line("") | ||
559 | add_line("Primary recipes = %s" % ','.join(self.primary)) | ||
560 | add_line("Task count = %4d" % len(self.dep.pkg_model)) | ||
561 | add_line("Deps count = %4d" % dep_count) | ||
562 | add_line("RDeps count = %4d" % rdep_count) | ||
563 | add_line("") | ||
564 | self.screen.addstr(self.y_pos,self.x_pos+7,"<Press any key>", curses.color_pair(CURSES_HIGHLIGHT)) | ||
565 | self.screen.refresh() | ||
566 | c = self.screen.getch() | ||
567 | |||
568 | # Show a progress bar | ||
569 | class ProgressView(NBox): | ||
570 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
571 | super(ProgressView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
572 | |||
573 | def progress(self,title,current,max): | ||
574 | if title: | ||
575 | self.label = title | ||
576 | else: | ||
577 | title = self.label | ||
578 | if max <=0: max = 10 | ||
579 | bar_size = self.width - 7 - len(title) | ||
580 | bar_done = int( (float(current)/float(max)) * float(bar_size) ) | ||
581 | self.screen.addstr(self.base_y,self.base_x, " %s:[%s%s]" % (title,'*' * bar_done,' ' * (bar_size-bar_done))) | ||
582 | self.screen.refresh() | ||
583 | return(current+1) | ||
584 | |||
585 | def clear(self): | ||
586 | self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.width)) | ||
587 | self.screen.refresh() | ||
588 | |||
589 | # Implement a task filter bar | ||
590 | class FilterView(NBox): | ||
591 | SEARCH_NOP = 0 | ||
592 | SEARCH_GO = 1 | ||
593 | SEARCH_CANCEL = 2 | ||
594 | |||
595 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
596 | super(FilterView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
597 | self.do_show = False | ||
598 | self.filter_str = "" | ||
599 | |||
600 | def clear(self,enable_show=True): | ||
601 | self.filter_str = "" | ||
602 | |||
603 | def show(self,enable_show=True): | ||
604 | self.do_show = enable_show | ||
605 | if self.do_show: | ||
606 | self.screen.addstr(self.base_y,self.base_x, "[ Filter: %-25s ] '/'=cancel, format='abc' " % self.filter_str[0:25]) | ||
607 | else: | ||
608 | self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.width)) | ||
609 | self.screen.refresh() | ||
610 | |||
611 | def show_prompt(self): | ||
612 | self.screen.addstr(self.base_y,self.base_x + 10 + len(self.filter_str), " ") | ||
613 | self.screen.addstr(self.base_y,self.base_x + 10 + len(self.filter_str), "") | ||
614 | |||
615 | # Keys specific to the filter box (start/stop filter keys are in the main loop) | ||
616 | def input(self,c,ch): | ||
617 | ret = self.SEARCH_GO | ||
618 | if c in (curses.KEY_BACKSPACE,CHAR_BS_H): | ||
619 | # Backspace | ||
620 | if self.filter_str: | ||
621 | self.filter_str = self.filter_str[0:-1] | ||
622 | self.show() | ||
623 | elif ((ch >= 'a') and (ch <= 'z')) or ((ch >= 'A') and (ch <= 'Z')) or ((ch >= '0') and (ch <= '9')) or (ch in (' ','_','.','-')): | ||
624 | # The isalnum() acts strangly with keypad(True), so explicit bounds | ||
625 | self.filter_str += ch | ||
626 | self.show() | ||
627 | else: | ||
628 | ret = self.SEARCH_NOP | ||
629 | return(ret) | ||
630 | |||
631 | |||
632 | ################################################# | ||
633 | ### The primary dependency windows | ||
634 | ### | ||
635 | |||
636 | # The main list of package tasks | ||
637 | class PackageView(NBox): | ||
638 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
639 | super(PackageView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
640 | |||
641 | # Find and verticaly center a selected task (from filter or from dependent box) | ||
642 | # The 'task_filter_str' can be a full or a partial (filter) task name | ||
643 | def find(self,task_filter_str): | ||
644 | found = False | ||
645 | max = self.height-2 | ||
646 | if not task_filter_str: | ||
647 | return(found) | ||
648 | for i,task_obj in enumerate(self.task_list): | ||
649 | task = task_obj[TASK_NAME] | ||
650 | if task.startswith(task_filter_str): | ||
651 | self.cursor_on(False) | ||
652 | self.cursor_index = i | ||
653 | |||
654 | # Position selected at vertical center | ||
655 | vcenter = self.inside_height // 2 | ||
656 | if self.cursor_index <= vcenter: | ||
657 | self.scroll_offset = 0 | ||
658 | self.cursor_offset = self.cursor_index | ||
659 | elif self.cursor_index >= (len(self.task_list) - vcenter - 1): | ||
660 | self.cursor_offset = self.inside_height-1 | ||
661 | self.scroll_offset = self.cursor_index - self.cursor_offset | ||
662 | else: | ||
663 | self.cursor_offset = vcenter | ||
664 | self.scroll_offset = self.cursor_index - self.cursor_offset | ||
665 | |||
666 | self.redraw() | ||
667 | self.cursor_on(True) | ||
668 | found = True | ||
669 | break | ||
670 | return(found) | ||
671 | |||
672 | # The view of dependent packages | ||
673 | class PackageDepView(NBox): | ||
674 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
675 | super(PackageDepView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
676 | |||
677 | # The view of reverse-dependent packages | ||
678 | class PackageReverseDepView(NBox): | ||
679 | def __init__(self, screen, label, primary, base_x, base_y, width, height): | ||
680 | super(PackageReverseDepView, self).__init__(screen, label, primary, base_x, base_y, width, height) | ||
681 | |||
682 | |||
683 | ################################################# | ||
684 | ### DepExplorer : The parent frame and object | ||
685 | ### | ||
686 | |||
687 | class DepExplorer(NBox): | ||
688 | def __init__(self,screen): | ||
689 | title = "Task Dependency Explorer" | ||
690 | super(DepExplorer, self).__init__(screen, 'Task Dependency Explorer','',0,0,80,23) | ||
691 | |||
692 | self.screen = screen | ||
693 | self.pkg_model = [] | ||
694 | self.depends_model = [] | ||
695 | self.dep_sort_map = {} | ||
696 | self.bb_sort_map = {} | ||
697 | self.filter_str = '' | ||
698 | self.filter_prev = 'deadbeef' | ||
699 | |||
700 | if self.screen: | ||
701 | self.help_bar_view = HelpBarView(screen, "Help",'',1,1,79,1) | ||
702 | self.help_box_view = HelpBoxView(screen, "Help",'',0,2,40,20,self) | ||
703 | self.progress_view = ProgressView(screen, "Progress",'',2,1,76,1) | ||
704 | self.filter_view = FilterView(screen, "Filter",'',2,1,76,1) | ||
705 | self.package_view = PackageView(screen, "Package",'alpha', 0,2,40,20) | ||
706 | self.dep_view = PackageDepView(screen, "Dependencies",'beta',40,2,40,10) | ||
707 | self.reverse_view = PackageReverseDepView(screen, "Dependent Tasks",'gamma',40,13,40,9) | ||
708 | self.draw_frames() | ||
709 | |||
710 | # Draw this main window's frame and all sub-windows | ||
711 | def draw_frames(self): | ||
712 | self.draw_frame() | ||
713 | self.package_view.draw_frame() | ||
714 | self.dep_view.draw_frame() | ||
715 | self.reverse_view.draw_frame() | ||
716 | if is_filter: | ||
717 | self.filter_view.show(True) | ||
718 | self.filter_view.show_prompt() | ||
719 | else: | ||
720 | self.help_bar_view.show_help(True) | ||
721 | self.package_view.redraw() | ||
722 | self.dep_view.redraw() | ||
723 | self.reverse_view.redraw() | ||
724 | self.show_selected(self.package_view.get_selected()) | ||
725 | line_art_fixup(self) | ||
726 | |||
727 | # Parse the bitbake dependency event object | ||
728 | def parse(self, depgraph): | ||
729 | for task in depgraph["tdepends"]: | ||
730 | self.pkg_model.insert(0, task) | ||
731 | for depend in depgraph["tdepends"][task]: | ||
732 | self.depends_model.insert (0, (TYPE_DEP, task, depend)) | ||
733 | self.depends_model.insert (0, (TYPE_RDEP, depend, task)) | ||
734 | if self.screen: | ||
735 | self.dep_sort_prep() | ||
736 | |||
737 | # Prepare the dependency sort order keys | ||
738 | # This method creates sort keys per recipe tasks in | ||
739 | # the order of each recipe's internal dependecies | ||
740 | # Method: | ||
741 | # Filter the tasks in dep order in dep_sort_map = {} | ||
742 | # (a) Find a task that has no dependecies | ||
743 | # Ignore non-recipe specific tasks | ||
744 | # (b) Add it to the sort mapping dict with | ||
745 | # key of "<task_group>_<order>" | ||
746 | # (c) Remove it as a dependency from the other tasks | ||
747 | # (d) Repeat till all tasks are mapped | ||
748 | # Use placeholders to insure each sub-dict is instantiated | ||
749 | def dep_sort_prep(self): | ||
750 | self.progress_view.progress('DepSort',0,4) | ||
751 | # Init the task base entries | ||
752 | self.progress_view.progress('DepSort',1,4) | ||
753 | dep_table = {} | ||
754 | bb_index = 0 | ||
755 | for task in self.pkg_model: | ||
756 | # First define the incoming bitbake sort order | ||
757 | self.bb_sort_map[task] = "%04d" % (bb_index) | ||
758 | bb_index += 1 | ||
759 | task_group = task[0:task.find('.')] | ||
760 | if task_group not in dep_table: | ||
761 | dep_table[task_group] = {} | ||
762 | dep_table[task_group]['-'] = {} # Placeholder | ||
763 | if task not in dep_table[task_group]: | ||
764 | dep_table[task_group][task] = {} | ||
765 | dep_table[task_group][task]['-'] = {} # Placeholder | ||
766 | # Add the task dependecy entries | ||
767 | self.progress_view.progress('DepSort',2,4) | ||
768 | for task_obj in self.depends_model: | ||
769 | if task_obj[DEPENDS_TYPE] != TYPE_DEP: | ||
770 | continue | ||
771 | task = task_obj[DEPENDS_TASK] | ||
772 | task_dep = task_obj[DEPENDS_DEPS] | ||
773 | task_group = task[0:task.find('.')] | ||
774 | # Only track depends within same group | ||
775 | if task_dep.startswith(task_group+'.'): | ||
776 | dep_table[task_group][task][task_dep] = 1 | ||
777 | self.progress_view.progress('DepSort',3,4) | ||
778 | for task_group in dep_table: | ||
779 | dep_index = 0 | ||
780 | # Whittle down the tasks of each group | ||
781 | this_pass = 1 | ||
782 | do_loop = True | ||
783 | while (len(dep_table[task_group]) > 1) and do_loop: | ||
784 | this_pass += 1 | ||
785 | is_change = False | ||
786 | delete_list = [] | ||
787 | for task in dep_table[task_group]: | ||
788 | if '-' == task: | ||
789 | continue | ||
790 | if 1 == len(dep_table[task_group][task]): | ||
791 | is_change = True | ||
792 | # No more deps, so collect this task... | ||
793 | self.dep_sort_map[task] = "%s_%04d" % (task_group,dep_index) | ||
794 | dep_index += 1 | ||
795 | # ... remove it from other lists as resolved ... | ||
796 | for dep_task in dep_table[task_group]: | ||
797 | if task in dep_table[task_group][dep_task]: | ||
798 | del dep_table[task_group][dep_task][task] | ||
799 | # ... and remove it from from the task group | ||
800 | delete_list.append(task) | ||
801 | for task in delete_list: | ||
802 | del dep_table[task_group][task] | ||
803 | if not is_change: | ||
804 | alert("ERROR:DEP_SIEVE_NO_CHANGE:%s" % task_group,self.screen) | ||
805 | do_loop = False | ||
806 | continue | ||
807 | self.progress_view.progress('',4,4) | ||
808 | self.progress_view.clear() | ||
809 | self.help_bar_view.show_help(True) | ||
810 | if len(self.dep_sort_map) != len(self.pkg_model): | ||
811 | alert("ErrorDepSort:%d/%d" % (len(self.dep_sort_map),len(self.pkg_model)),self.screen) | ||
812 | |||
813 | # Look up a dep sort order key | ||
814 | def get_dep_sort(self,key): | ||
815 | if key in self.dep_sort_map: | ||
816 | return(self.dep_sort_map[key]) | ||
817 | else: | ||
818 | return(key) | ||
819 | |||
820 | # Look up a bitbake sort order key | ||
821 | def get_bb_sort(self,key): | ||
822 | if key in self.bb_sort_map: | ||
823 | return(self.bb_sort_map[key]) | ||
824 | else: | ||
825 | return(key) | ||
826 | |||
827 | # Find the selected package in the main frame, update the dependency frames content accordingly | ||
828 | def select(self, package_name, only_update_dependents=False): | ||
829 | if not package_name: | ||
830 | package_name = self.package_view.get_selected() | ||
831 | # alert("SELECT:%s:" % package_name,self.screen) | ||
832 | |||
833 | if self.filter_str != self.filter_prev: | ||
834 | self.package_view.cursor_on(False) | ||
835 | # Fill of the main package task list using new filter | ||
836 | self.package_view.task_list = [] | ||
837 | for package in self.pkg_model: | ||
838 | if self.filter_str: | ||
839 | if self.filter_str in package: | ||
840 | self.package_view.task_list_append(package,self) | ||
841 | else: | ||
842 | self.package_view.task_list_append(package,self) | ||
843 | self.package_view.sort() | ||
844 | self.filter_prev = self.filter_str | ||
845 | |||
846 | # Old position is lost, assert new position of previous task (if still filtered in) | ||
847 | self.package_view.cursor_index = 0 | ||
848 | self.package_view.cursor_offset = 0 | ||
849 | self.package_view.scroll_offset = 0 | ||
850 | self.package_view.redraw() | ||
851 | self.package_view.cursor_on(True) | ||
852 | |||
853 | # Make sure the selected package is in view, with implicit redraw() | ||
854 | if (not only_update_dependents): | ||
855 | self.package_view.find(package_name) | ||
856 | # In case selected name change (i.e. filter removed previous) | ||
857 | package_name = self.package_view.get_selected() | ||
858 | |||
859 | # Filter the package's dependent list to the dependent view | ||
860 | self.dep_view.reset() | ||
861 | for package_def in self.depends_model: | ||
862 | if (package_def[DEPENDS_TYPE] == TYPE_DEP) and (package_def[DEPENDS_TASK] == package_name): | ||
863 | self.dep_view.task_list_append(package_def[DEPENDS_DEPS],self) | ||
864 | self.dep_view.sort() | ||
865 | self.dep_view.redraw() | ||
866 | # Filter the package's dependent list to the reverse dependent view | ||
867 | self.reverse_view.reset() | ||
868 | for package_def in self.depends_model: | ||
869 | if (package_def[DEPENDS_TYPE] == TYPE_RDEP) and (package_def[DEPENDS_TASK] == package_name): | ||
870 | self.reverse_view.task_list_append(package_def[DEPENDS_DEPS],self) | ||
871 | self.reverse_view.sort() | ||
872 | self.reverse_view.redraw() | ||
873 | self.show_selected(package_name) | ||
874 | self.screen.refresh() | ||
875 | |||
876 | # The print-to-file method | ||
877 | def print_deps(self,whole_group=False): | ||
878 | global is_printed | ||
879 | # Print the selected deptree(s) to a file | ||
880 | if not is_printed: | ||
881 | try: | ||
882 | # Move to backup any exiting file before first write | ||
883 | if os.path.isfile(print_file_name): | ||
884 | os.system('mv -f %s %s' % (print_file_name,print_file_backup_name)) | ||
885 | except Exception as e: | ||
886 | alert(e,self.screen) | ||
887 | alert('',self.screen) | ||
888 | print_list = [] | ||
889 | selected_task = self.package_view.get_selected() | ||
890 | if not selected_task: | ||
891 | return | ||
892 | if not whole_group: | ||
893 | print_list.append(selected_task) | ||
894 | else: | ||
895 | # Use the presorted task_group order from 'package_view' | ||
896 | task_group = selected_task[0:selected_task.find('.')+1] | ||
897 | for task_obj in self.package_view.task_list: | ||
898 | task = task_obj[TASK_NAME] | ||
899 | if task.startswith(task_group): | ||
900 | print_list.append(task) | ||
901 | with open(print_file_name, "a") as fd: | ||
902 | print_max = len(print_list) | ||
903 | print_count = 1 | ||
904 | self.progress_view.progress('Write "%s"' % print_file_name,0,print_max) | ||
905 | for task in print_list: | ||
906 | print_count = self.progress_view.progress('',print_count,print_max) | ||
907 | self.select(task) | ||
908 | self.screen.refresh(); | ||
909 | # Utilize the current print output model | ||
910 | if print_model == PRINT_MODEL_1: | ||
911 | print("=== Dependendency Snapshot ===",file=fd) | ||
912 | print(" = Package =",file=fd) | ||
913 | print(' '+task,file=fd) | ||
914 | # Fill in the matching dependencies | ||
915 | print(" = Dependencies =",file=fd) | ||
916 | for task_obj in self.dep_view.task_list: | ||
917 | print(' '+ task_obj[TASK_NAME],file=fd) | ||
918 | print(" = Dependent Tasks =",file=fd) | ||
919 | for task_obj in self.reverse_view.task_list: | ||
920 | print(' '+ task_obj[TASK_NAME],file=fd) | ||
921 | if print_model == PRINT_MODEL_2: | ||
922 | print("=== Dependendency Snapshot ===",file=fd) | ||
923 | dep_count = len(self.dep_view.task_list) - 1 | ||
924 | for i,task_obj in enumerate(self.dep_view.task_list): | ||
925 | print('%s%s' % ("Dep =" if (i==dep_count) else " ",task_obj[TASK_NAME]),file=fd) | ||
926 | if not self.dep_view.task_list: | ||
927 | print('Dep =',file=fd) | ||
928 | print("Package=%s" % task,file=fd) | ||
929 | for i,task_obj in enumerate(self.reverse_view.task_list): | ||
930 | print('%s%s' % ("RDep =" if (i==0) else " ",task_obj[TASK_NAME]),file=fd) | ||
931 | if not self.reverse_view.task_list: | ||
932 | print('RDep =',file=fd) | ||
933 | curses.napms(2000) | ||
934 | self.progress_view.clear() | ||
935 | self.help_bar_view.show_help(True) | ||
936 | print('',file=fd) | ||
937 | # Restore display to original selected task | ||
938 | self.select(selected_task) | ||
939 | is_printed = True | ||
940 | |||
941 | ################################################# | ||
942 | ### Load bitbake data | ||
943 | ### | ||
944 | |||
945 | def bitbake_load(server, eventHandler, params, dep, curses_off, screen): | ||
946 | global bar_len_old | ||
947 | bar_len_old = 0 | ||
948 | |||
949 | # Support no screen | ||
950 | def progress(msg,count,max): | ||
951 | global bar_len_old | ||
952 | if screen: | ||
953 | dep.progress_view.progress(msg,count,max) | ||
954 | else: | ||
955 | if msg: | ||
956 | if bar_len_old: | ||
957 | bar_len_old = 0 | ||
958 | print("\n") | ||
959 | print(f"{msg}: ({count} of {max})") | ||
960 | else: | ||
961 | bar_len = int((count*40)/max) | ||
962 | if bar_len_old != bar_len: | ||
963 | print(f"{'*' * (bar_len-bar_len_old)}",end='',flush=True) | ||
964 | bar_len_old = bar_len | ||
965 | def clear(): | ||
966 | if screen: | ||
967 | dep.progress_view.clear() | ||
968 | def clear_curses(screen): | ||
969 | if screen: | ||
970 | curses_off(screen) | ||
971 | |||
972 | # | ||
973 | # Trigger bitbake "generateDepTreeEvent" | ||
974 | # | ||
975 | |||
976 | cmdline = '' | ||
977 | try: | ||
978 | params.updateToServer(server, os.environ.copy()) | ||
979 | params.updateFromServer(server) | ||
980 | cmdline = params.parseActions() | ||
981 | if not cmdline: | ||
982 | clear_curses(screen) | ||
983 | print("ERROR: nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") | ||
984 | return 1,cmdline | ||
985 | if 'msg' in cmdline and cmdline['msg']: | ||
986 | clear_curses(screen) | ||
987 | print('ERROR: ' + cmdline['msg']) | ||
988 | return 1,cmdline | ||
989 | cmdline = cmdline['action'] | ||
990 | if not cmdline or cmdline[0] != "generateDotGraph": | ||
991 | clear_curses(screen) | ||
992 | print("ERROR: This UI requires the -g option") | ||
993 | return 1,cmdline | ||
994 | ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]]) | ||
995 | if error: | ||
996 | clear_curses(screen) | ||
997 | print("ERROR: running command '%s': %s" % (cmdline, error)) | ||
998 | return 1,cmdline | ||
999 | elif not ret: | ||
1000 | clear_curses(screen) | ||
1001 | print("ERROR: running command '%s': returned %s" % (cmdline, ret)) | ||
1002 | return 1,cmdline | ||
1003 | except client.Fault as x: | ||
1004 | clear_curses(screen) | ||
1005 | print("ERROR: XMLRPC Fault getting commandline:\n %s" % x) | ||
1006 | return 1,cmdline | ||
1007 | except Exception as e: | ||
1008 | clear_curses(screen) | ||
1009 | print("ERROR: in startup:\n %s" % traceback.format_exc()) | ||
1010 | return 1,cmdline | ||
1011 | |||
1012 | # | ||
1013 | # Receive data from bitbake | ||
1014 | # | ||
1015 | |||
1016 | progress_total = 0 | ||
1017 | load_bitbake = True | ||
1018 | quit = False | ||
1019 | try: | ||
1020 | while load_bitbake: | ||
1021 | try: | ||
1022 | event = eventHandler.waitEvent(0.25) | ||
1023 | if quit: | ||
1024 | _, error = server.runCommand(["stateForceShutdown"]) | ||
1025 | clear_curses(screen) | ||
1026 | if error: | ||
1027 | print('Unable to cleanly stop: %s' % error) | ||
1028 | break | ||
1029 | |||
1030 | if event is None: | ||
1031 | continue | ||
1032 | |||
1033 | if isinstance(event, bb.event.CacheLoadStarted): | ||
1034 | progress_total = event.total | ||
1035 | progress('Loading Cache',0,progress_total) | ||
1036 | continue | ||
1037 | |||
1038 | if isinstance(event, bb.event.CacheLoadProgress): | ||
1039 | x = event.current | ||
1040 | progress('',x,progress_total) | ||
1041 | continue | ||
1042 | |||
1043 | if isinstance(event, bb.event.CacheLoadCompleted): | ||
1044 | clear() | ||
1045 | progress('Bitbake... ',1,2) | ||
1046 | continue | ||
1047 | |||
1048 | if isinstance(event, bb.event.ParseStarted): | ||
1049 | progress_total = event.total | ||
1050 | progress('Processing recipes',0,progress_total) | ||
1051 | if progress_total == 0: | ||
1052 | continue | ||
1053 | |||
1054 | if isinstance(event, bb.event.ParseProgress): | ||
1055 | x = event.current | ||
1056 | progress('',x,progress_total) | ||
1057 | continue | ||
1058 | |||
1059 | if isinstance(event, bb.event.ParseCompleted): | ||
1060 | progress('Generating dependency tree',0,3) | ||
1061 | continue | ||
1062 | |||
1063 | if isinstance(event, bb.event.DepTreeGenerated): | ||
1064 | progress('Generating dependency tree',1,3) | ||
1065 | dep.parse(event._depgraph) | ||
1066 | progress('Generating dependency tree',2,3) | ||
1067 | |||
1068 | if isinstance(event, bb.command.CommandCompleted): | ||
1069 | load_bitbake = False | ||
1070 | progress('Generating dependency tree',3,3) | ||
1071 | clear() | ||
1072 | if screen: | ||
1073 | dep.help_bar_view.show_help(True) | ||
1074 | continue | ||
1075 | |||
1076 | if isinstance(event, bb.event.NoProvider): | ||
1077 | clear_curses(screen) | ||
1078 | print('ERROR: %s' % event) | ||
1079 | |||
1080 | _, error = server.runCommand(["stateShutdown"]) | ||
1081 | if error: | ||
1082 | print('ERROR: Unable to cleanly shutdown: %s' % error) | ||
1083 | return 1,cmdline | ||
1084 | |||
1085 | if isinstance(event, bb.command.CommandFailed): | ||
1086 | clear_curses(screen) | ||
1087 | print('ERROR: ' + str(event)) | ||
1088 | return event.exitcode,cmdline | ||
1089 | |||
1090 | if isinstance(event, bb.command.CommandExit): | ||
1091 | clear_curses(screen) | ||
1092 | return event.exitcode,cmdline | ||
1093 | |||
1094 | if isinstance(event, bb.cooker.CookerExit): | ||
1095 | break | ||
1096 | |||
1097 | continue | ||
1098 | except EnvironmentError as ioerror: | ||
1099 | # ignore interrupted io | ||
1100 | if ioerror.args[0] == 4: | ||
1101 | pass | ||
1102 | except KeyboardInterrupt: | ||
1103 | if shutdown == 2: | ||
1104 | clear_curses(screen) | ||
1105 | print("\nThird Keyboard Interrupt, exit.\n") | ||
1106 | break | ||
1107 | if shutdown == 1: | ||
1108 | clear_curses(screen) | ||
1109 | print("\nSecond Keyboard Interrupt, stopping...\n") | ||
1110 | _, error = server.runCommand(["stateForceShutdown"]) | ||
1111 | if error: | ||
1112 | print('Unable to cleanly stop: %s' % error) | ||
1113 | if shutdown == 0: | ||
1114 | clear_curses(screen) | ||
1115 | print("\nKeyboard Interrupt, closing down...\n") | ||
1116 | _, error = server.runCommand(["stateShutdown"]) | ||
1117 | if error: | ||
1118 | print('Unable to cleanly shutdown: %s' % error) | ||
1119 | shutdown = shutdown + 1 | ||
1120 | pass | ||
1121 | except Exception as e: | ||
1122 | # Safe exit on error | ||
1123 | clear_curses(screen) | ||
1124 | print("Exception : %s" % e) | ||
1125 | print("Exception in startup:\n %s" % traceback.format_exc()) | ||
1126 | |||
1127 | return 0,cmdline | ||
1128 | |||
1129 | ################################################# | ||
1130 | ### main | ||
1131 | ### | ||
1132 | |||
1133 | SCREEN_COL_MIN = 83 | ||
1134 | SCREEN_ROW_MIN = 26 | ||
1135 | |||
1136 | def main(server, eventHandler, params): | ||
1137 | global verbose | ||
1138 | global sort_model | ||
1139 | global print_model | ||
1140 | global is_printed | ||
1141 | global is_filter | ||
1142 | global screen_too_small | ||
1143 | |||
1144 | shutdown = 0 | ||
1145 | screen_too_small = False | ||
1146 | quit = False | ||
1147 | |||
1148 | # Unit test with no terminal? | ||
1149 | if unit_test_noterm: | ||
1150 | # Load bitbake, test that there is valid dependency data, then exit | ||
1151 | screen = None | ||
1152 | print("* UNIT TEST:START") | ||
1153 | dep = DepExplorer(screen) | ||
1154 | print("* UNIT TEST:BITBAKE FETCH") | ||
1155 | ret,cmdline = bitbake_load(server, eventHandler, params, dep, None, screen) | ||
1156 | if ret: | ||
1157 | print("* UNIT TEST: BITBAKE FAILED") | ||
1158 | return ret | ||
1159 | # Test the acquired dependency data | ||
1160 | quilt_native_deps = 0 | ||
1161 | quilt_native_rdeps = 0 | ||
1162 | quilt_deps = 0 | ||
1163 | quilt_rdeps = 0 | ||
1164 | for i,task_obj in enumerate(dep.depends_model): | ||
1165 | if TYPE_DEP == task_obj[0]: | ||
1166 | task = task_obj[1] | ||
1167 | if task.startswith('quilt-native'): | ||
1168 | quilt_native_deps += 1 | ||
1169 | elif task.startswith('quilt'): | ||
1170 | quilt_deps += 1 | ||
1171 | elif TYPE_RDEP == task_obj[0]: | ||
1172 | task = task_obj[1] | ||
1173 | if task.startswith('quilt-native'): | ||
1174 | quilt_native_rdeps += 1 | ||
1175 | elif task.startswith('quilt'): | ||
1176 | quilt_rdeps += 1 | ||
1177 | # Print results | ||
1178 | failed = False | ||
1179 | if 0 < len(dep.depends_model): | ||
1180 | print(f"Pass:Bitbake dependency count = {len(dep.depends_model)}") | ||
1181 | else: | ||
1182 | failed = True | ||
1183 | print(f"FAIL:Bitbake dependency count = 0") | ||
1184 | if quilt_native_deps: | ||
1185 | print(f"Pass:Quilt-native depends count = {quilt_native_deps}") | ||
1186 | else: | ||
1187 | failed = True | ||
1188 | print(f"FAIL:Quilt-native depends count = 0") | ||
1189 | if quilt_native_rdeps: | ||
1190 | print(f"Pass:Quilt-native rdepends count = {quilt_native_rdeps}") | ||
1191 | else: | ||
1192 | failed = True | ||
1193 | print(f"FAIL:Quilt-native rdepends count = 0") | ||
1194 | if quilt_deps: | ||
1195 | print(f"Pass:Quilt depends count = {quilt_deps}") | ||
1196 | else: | ||
1197 | failed = True | ||
1198 | print(f"FAIL:Quilt depends count = 0") | ||
1199 | if quilt_rdeps: | ||
1200 | print(f"Pass:Quilt rdepends count = {quilt_rdeps}") | ||
1201 | else: | ||
1202 | failed = True | ||
1203 | print(f"FAIL:Quilt rdepends count = 0") | ||
1204 | print("* UNIT TEST:STOP") | ||
1205 | return failed | ||
1206 | |||
1207 | # Help method to dynamically test parent window too small | ||
1208 | def check_screen_size(dep, active_package): | ||
1209 | global screen_too_small | ||
1210 | rows, cols = screen.getmaxyx() | ||
1211 | if (rows >= SCREEN_ROW_MIN) and (cols >= SCREEN_COL_MIN): | ||
1212 | if screen_too_small: | ||
1213 | # Now big enough, remove error message and redraw screen | ||
1214 | dep.draw_frames() | ||
1215 | active_package.cursor_on(True) | ||
1216 | screen_too_small = False | ||
1217 | return True | ||
1218 | # Test on App init | ||
1219 | if not dep: | ||
1220 | # Do not start this app if screen not big enough | ||
1221 | curses.endwin() | ||
1222 | print("") | ||
1223 | print("ERROR(Taskexp_cli): Mininal screen size is %dx%d" % (SCREEN_COL_MIN,SCREEN_ROW_MIN)) | ||
1224 | print("Current screen is Cols=%s,Rows=%d" % (cols,rows)) | ||
1225 | return False | ||
1226 | # First time window too small | ||
1227 | if not screen_too_small: | ||
1228 | active_package.cursor_on(False) | ||
1229 | dep.screen.addstr(0,2,'[BIGGER WINDOW PLEASE]', curses.color_pair(CURSES_WARNING) | curses.A_BLINK) | ||
1230 | screen_too_small = True | ||
1231 | return False | ||
1232 | |||
1233 | # Helper method to turn off curses mode | ||
1234 | def curses_off(screen): | ||
1235 | if not screen: return | ||
1236 | # Safe error exit | ||
1237 | screen.keypad(False) | ||
1238 | curses.echo() | ||
1239 | curses.curs_set(1) | ||
1240 | curses.endwin() | ||
1241 | |||
1242 | if unit_test_results: | ||
1243 | print('\nUnit Test Results:') | ||
1244 | for line in unit_test_results: | ||
1245 | print(" %s" % line) | ||
1246 | |||
1247 | # | ||
1248 | # Initialize the ncurse environment | ||
1249 | # | ||
1250 | |||
1251 | screen = curses.initscr() | ||
1252 | try: | ||
1253 | if not check_screen_size(None, None): | ||
1254 | exit(1) | ||
1255 | try: | ||
1256 | curses.start_color() | ||
1257 | curses.use_default_colors(); | ||
1258 | curses.init_pair(0xFF, curses.COLOR_BLACK, curses.COLOR_WHITE); | ||
1259 | curses.init_pair(CURSES_NORMAL, curses.COLOR_WHITE, curses.COLOR_BLACK) | ||
1260 | curses.init_pair(CURSES_HIGHLIGHT, curses.COLOR_WHITE, curses.COLOR_BLUE) | ||
1261 | curses.init_pair(CURSES_WARNING, curses.COLOR_WHITE, curses.COLOR_RED) | ||
1262 | except: | ||
1263 | curses.endwin() | ||
1264 | print("") | ||
1265 | print("ERROR(Taskexp_cli): Requires 256 colors. Please use this or the equivalent:") | ||
1266 | print(" $ export TERM='xterm-256color'") | ||
1267 | exit(1) | ||
1268 | |||
1269 | screen.keypad(True) | ||
1270 | curses.noecho() | ||
1271 | curses.curs_set(0) | ||
1272 | screen.refresh(); | ||
1273 | except Exception as e: | ||
1274 | # Safe error exit | ||
1275 | curses_off(screen) | ||
1276 | print("Exception : %s" % e) | ||
1277 | print("Exception in startup:\n %s" % traceback.format_exc()) | ||
1278 | exit(1) | ||
1279 | |||
1280 | try: | ||
1281 | # | ||
1282 | # Instantiate the presentation layers | ||
1283 | # | ||
1284 | |||
1285 | dep = DepExplorer(screen) | ||
1286 | |||
1287 | # | ||
1288 | # Prepare bitbake | ||
1289 | # | ||
1290 | |||
1291 | # Fetch bitbake dependecy data | ||
1292 | ret,cmdline = bitbake_load(server, eventHandler, params, dep, curses_off, screen) | ||
1293 | if ret: return ret | ||
1294 | |||
1295 | # | ||
1296 | # Preset the views | ||
1297 | # | ||
1298 | |||
1299 | # Cmdline example = ['generateDotGraph', ['acl', 'zlib'], 'build'] | ||
1300 | primary_packages = cmdline[1] | ||
1301 | dep.package_view.set_primary(primary_packages) | ||
1302 | dep.dep_view.set_primary(primary_packages) | ||
1303 | dep.reverse_view.set_primary(primary_packages) | ||
1304 | dep.help_box_view.set_primary(primary_packages) | ||
1305 | dep.help_bar_view.show_help(True) | ||
1306 | active_package = dep.package_view | ||
1307 | active_package.cursor_on(True) | ||
1308 | dep.select(primary_packages[0]+'.') | ||
1309 | if unit_test: | ||
1310 | alert('UNIT_TEST',screen) | ||
1311 | |||
1312 | # Help method to start/stop the filter feature | ||
1313 | def filter_mode(new_filter_status): | ||
1314 | global is_filter | ||
1315 | if is_filter == new_filter_status: | ||
1316 | # Ignore no changes | ||
1317 | return | ||
1318 | if not new_filter_status: | ||
1319 | # Turn off | ||
1320 | curses.curs_set(0) | ||
1321 | #active_package.cursor_on(False) | ||
1322 | active_package = dep.package_view | ||
1323 | active_package.cursor_on(True) | ||
1324 | is_filter = False | ||
1325 | dep.help_bar_view.show_help(True) | ||
1326 | dep.filter_str = '' | ||
1327 | dep.select('') | ||
1328 | else: | ||
1329 | # Turn on | ||
1330 | curses.curs_set(1) | ||
1331 | dep.help_bar_view.show_help(False) | ||
1332 | dep.filter_view.clear() | ||
1333 | dep.filter_view.show(True) | ||
1334 | dep.filter_view.show_prompt() | ||
1335 | is_filter = True | ||
1336 | |||
1337 | # | ||
1338 | # Main user loop | ||
1339 | # | ||
1340 | |||
1341 | while not quit: | ||
1342 | if is_filter: | ||
1343 | dep.filter_view.show_prompt() | ||
1344 | if unit_test: | ||
1345 | c = unit_test_action(active_package) | ||
1346 | else: | ||
1347 | c = screen.getch() | ||
1348 | ch = chr(c) | ||
1349 | |||
1350 | # Do not draw if window now too small | ||
1351 | if not check_screen_size(dep,active_package): | ||
1352 | continue | ||
1353 | |||
1354 | if verbose: | ||
1355 | if c == CHAR_RETURN: | ||
1356 | screen.addstr(0, 4, "|%3d,CR |" % (c)) | ||
1357 | else: | ||
1358 | screen.addstr(0, 4, "|%3d,%3s|" % (c,chr(c))) | ||
1359 | |||
1360 | # pre-map alternate filter close keys | ||
1361 | if is_filter and (c == CHAR_ESCAPE): | ||
1362 | # Alternate exit from filter | ||
1363 | ch = '/' | ||
1364 | c = ord(ch) | ||
1365 | |||
1366 | # Filter and non-filter mode command keys | ||
1367 | # https://docs.python.org/3/library/curses.html | ||
1368 | if c in (curses.KEY_UP,CHAR_UP): | ||
1369 | active_package.line_up() | ||
1370 | if active_package == dep.package_view: | ||
1371 | dep.select('',only_update_dependents=True) | ||
1372 | elif c in (curses.KEY_DOWN,CHAR_DOWN): | ||
1373 | active_package.line_down() | ||
1374 | if active_package == dep.package_view: | ||
1375 | dep.select('',only_update_dependents=True) | ||
1376 | elif curses.KEY_PPAGE == c: | ||
1377 | active_package.page_up() | ||
1378 | if active_package == dep.package_view: | ||
1379 | dep.select('',only_update_dependents=True) | ||
1380 | elif curses.KEY_NPAGE == c: | ||
1381 | active_package.page_down() | ||
1382 | if active_package == dep.package_view: | ||
1383 | dep.select('',only_update_dependents=True) | ||
1384 | elif CHAR_TAB == c: | ||
1385 | # Tab between boxes | ||
1386 | active_package.cursor_on(False) | ||
1387 | if active_package == dep.package_view: | ||
1388 | active_package = dep.dep_view | ||
1389 | elif active_package == dep.dep_view: | ||
1390 | active_package = dep.reverse_view | ||
1391 | else: | ||
1392 | active_package = dep.package_view | ||
1393 | active_package.cursor_on(True) | ||
1394 | elif curses.KEY_BTAB == c: | ||
1395 | # Shift-Tab reverse between boxes | ||
1396 | active_package.cursor_on(False) | ||
1397 | if active_package == dep.package_view: | ||
1398 | active_package = dep.reverse_view | ||
1399 | elif active_package == dep.reverse_view: | ||
1400 | active_package = dep.dep_view | ||
1401 | else: | ||
1402 | active_package = dep.package_view | ||
1403 | active_package.cursor_on(True) | ||
1404 | elif (CHAR_RETURN == c): | ||
1405 | # CR to select | ||
1406 | selected = active_package.get_selected() | ||
1407 | if selected: | ||
1408 | active_package.cursor_on(False) | ||
1409 | active_package = dep.package_view | ||
1410 | filter_mode(False) | ||
1411 | dep.select(selected) | ||
1412 | else: | ||
1413 | filter_mode(False) | ||
1414 | dep.select(primary_packages[0]+'.') | ||
1415 | |||
1416 | elif '/' == ch: # Enter/exit dep.filter_view | ||
1417 | if is_filter: | ||
1418 | filter_mode(False) | ||
1419 | else: | ||
1420 | filter_mode(True) | ||
1421 | elif is_filter: | ||
1422 | # If in filter mode, re-direct all these other keys to the filter box | ||
1423 | result = dep.filter_view.input(c,ch) | ||
1424 | dep.filter_str = dep.filter_view.filter_str | ||
1425 | dep.select('') | ||
1426 | |||
1427 | # Non-filter mode command keys | ||
1428 | elif 'p' == ch: | ||
1429 | dep.print_deps(whole_group=False) | ||
1430 | elif 'P' == ch: | ||
1431 | dep.print_deps(whole_group=True) | ||
1432 | elif 'w' == ch: | ||
1433 | # Toggle the print model | ||
1434 | if print_model == PRINT_MODEL_1: | ||
1435 | print_model = PRINT_MODEL_2 | ||
1436 | else: | ||
1437 | print_model = PRINT_MODEL_1 | ||
1438 | elif 's' == ch: | ||
1439 | # Toggle the sort model | ||
1440 | if sort_model == SORT_DEPS: | ||
1441 | sort_model = SORT_ALPHA | ||
1442 | elif sort_model == SORT_ALPHA: | ||
1443 | if SORT_BITBAKE_ENABLE: | ||
1444 | sort_model = TASK_SORT_BITBAKE | ||
1445 | else: | ||
1446 | sort_model = SORT_DEPS | ||
1447 | else: | ||
1448 | sort_model = SORT_DEPS | ||
1449 | active_package.cursor_on(False) | ||
1450 | current_task = active_package.get_selected() | ||
1451 | dep.package_view.sort() | ||
1452 | dep.dep_view.sort() | ||
1453 | dep.reverse_view.sort() | ||
1454 | active_package = dep.package_view | ||
1455 | active_package.cursor_on(True) | ||
1456 | dep.select(current_task) | ||
1457 | # Announce the new sort model | ||
1458 | alert("SORT=%s" % ("ALPHA" if (sort_model == SORT_ALPHA) else "DEPS"),screen) | ||
1459 | alert('',screen) | ||
1460 | |||
1461 | elif 'q' == ch: | ||
1462 | quit = True | ||
1463 | elif ch in ('h','?'): | ||
1464 | dep.help_box_view.show_help(True) | ||
1465 | dep.select(active_package.get_selected()) | ||
1466 | |||
1467 | # | ||
1468 | # Debugging commands | ||
1469 | # | ||
1470 | |||
1471 | elif 'V' == ch: | ||
1472 | verbose = not verbose | ||
1473 | alert('Verbose=%s' % str(verbose),screen) | ||
1474 | alert('',screen) | ||
1475 | elif 'R' == ch: | ||
1476 | screen.refresh() | ||
1477 | elif 'B' == ch: | ||
1478 | # Progress bar unit test | ||
1479 | dep.progress_view.progress('Test',0,40) | ||
1480 | curses.napms(1000) | ||
1481 | dep.progress_view.progress('',10,40) | ||
1482 | curses.napms(1000) | ||
1483 | dep.progress_view.progress('',20,40) | ||
1484 | curses.napms(1000) | ||
1485 | dep.progress_view.progress('',30,40) | ||
1486 | curses.napms(1000) | ||
1487 | dep.progress_view.progress('',40,40) | ||
1488 | curses.napms(1000) | ||
1489 | dep.progress_view.clear() | ||
1490 | dep.help_bar_view.show_help(True) | ||
1491 | elif 'Q' == ch: | ||
1492 | # Simulated error | ||
1493 | curses_off(screen) | ||
1494 | print('ERROR: simulated error exit') | ||
1495 | return 1 | ||
1496 | |||
1497 | # Safe exit | ||
1498 | curses_off(screen) | ||
1499 | except Exception as e: | ||
1500 | # Safe exit on error | ||
1501 | curses_off(screen) | ||
1502 | print("Exception : %s" % e) | ||
1503 | print("Exception in startup:\n %s" % traceback.format_exc()) | ||
1504 | |||
1505 | # Reminder to pick up your printed results | ||
1506 | if is_printed: | ||
1507 | print("") | ||
1508 | print("You have output ready!") | ||
1509 | print(" * Your printed dependency file is: %s" % print_file_name) | ||
1510 | print(" * Your previous results saved in: %s" % print_file_backup_name) | ||
1511 | print("") | ||
diff --git a/bitbake/lib/bb/ui/toasterui.py b/bitbake/lib/bb/ui/toasterui.py index ec5bd4f105..6bd21f1844 100644 --- a/bitbake/lib/bb/ui/toasterui.py +++ b/bitbake/lib/bb/ui/toasterui.py | |||
@@ -385,7 +385,7 @@ def main(server, eventHandler, params): | |||
385 | main.shutdown = 1 | 385 | main.shutdown = 1 |
386 | 386 | ||
387 | logger.info("ToasterUI build done, brbe: %s", brbe) | 387 | logger.info("ToasterUI build done, brbe: %s", brbe) |
388 | continue | 388 | break |
389 | 389 | ||
390 | if isinstance(event, (bb.command.CommandCompleted, | 390 | if isinstance(event, (bb.command.CommandCompleted, |
391 | bb.command.CommandFailed, | 391 | bb.command.CommandFailed, |
diff --git a/bitbake/lib/bb/ui/uievent.py b/bitbake/lib/bb/ui/uievent.py index 8607d0523b..c2f830d530 100644 --- a/bitbake/lib/bb/ui/uievent.py +++ b/bitbake/lib/bb/ui/uievent.py | |||
@@ -44,7 +44,7 @@ class BBUIEventQueue: | |||
44 | for count_tries in range(5): | 44 | for count_tries in range(5): |
45 | ret = self.BBServer.registerEventHandler(self.host, self.port) | 45 | ret = self.BBServer.registerEventHandler(self.host, self.port) |
46 | 46 | ||
47 | if isinstance(ret, collections.Iterable): | 47 | if isinstance(ret, collections.abc.Iterable): |
48 | self.EventHandle, error = ret | 48 | self.EventHandle, error = ret |
49 | else: | 49 | else: |
50 | self.EventHandle = ret | 50 | self.EventHandle = ret |
@@ -65,35 +65,27 @@ class BBUIEventQueue: | |||
65 | self.server = server | 65 | self.server = server |
66 | 66 | ||
67 | self.t = threading.Thread() | 67 | self.t = threading.Thread() |
68 | self.t.setDaemon(True) | 68 | self.t.daemon = True |
69 | self.t.run = self.startCallbackHandler | 69 | self.t.run = self.startCallbackHandler |
70 | self.t.start() | 70 | self.t.start() |
71 | 71 | ||
72 | def getEvent(self): | 72 | def getEvent(self): |
73 | 73 | with bb.utils.lock_timeout(self.eventQueueLock): | |
74 | self.eventQueueLock.acquire() | 74 | if not self.eventQueue: |
75 | 75 | return None | |
76 | if len(self.eventQueue) == 0: | 76 | item = self.eventQueue.pop(0) |
77 | self.eventQueueLock.release() | 77 | if not self.eventQueue: |
78 | return None | 78 | self.eventQueueNotify.clear() |
79 | 79 | return item | |
80 | item = self.eventQueue.pop(0) | ||
81 | |||
82 | if len(self.eventQueue) == 0: | ||
83 | self.eventQueueNotify.clear() | ||
84 | |||
85 | self.eventQueueLock.release() | ||
86 | return item | ||
87 | 80 | ||
88 | def waitEvent(self, delay): | 81 | def waitEvent(self, delay): |
89 | self.eventQueueNotify.wait(delay) | 82 | self.eventQueueNotify.wait(delay) |
90 | return self.getEvent() | 83 | return self.getEvent() |
91 | 84 | ||
92 | def queue_event(self, event): | 85 | def queue_event(self, event): |
93 | self.eventQueueLock.acquire() | 86 | with bb.utils.lock_timeout(self.eventQueueLock): |
94 | self.eventQueue.append(event) | 87 | self.eventQueue.append(event) |
95 | self.eventQueueNotify.set() | 88 | self.eventQueueNotify.set() |
96 | self.eventQueueLock.release() | ||
97 | 89 | ||
98 | def send_event(self, event): | 90 | def send_event(self, event): |
99 | self.queue_event(pickle.loads(event)) | 91 | self.queue_event(pickle.loads(event)) |
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py index 48d808ae28..82913e0da8 100644 --- a/bitbake/lib/bb/ui/uihelper.py +++ b/bitbake/lib/bb/ui/uihelper.py | |||
@@ -49,9 +49,11 @@ class BBUIHelper: | |||
49 | tid = event._fn + ":" + event._task | 49 | tid = event._fn + ":" + event._task |
50 | removetid(event.pid, tid) | 50 | removetid(event.pid, tid) |
51 | self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)}) | 51 | self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)}) |
52 | elif isinstance(event, bb.runqueue.runQueueTaskStarted): | 52 | elif isinstance(event, bb.runqueue.runQueueTaskStarted) or isinstance(event, bb.runqueue.sceneQueueTaskStarted): |
53 | self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1 | 53 | self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed |
54 | self.tasknumber_total = event.stats.total | 54 | self.tasknumber_total = event.stats.total |
55 | self.setscene_current = event.stats.setscene_active + event.stats.setscene_covered + event.stats.setscene_notcovered | ||
56 | self.setscene_total = event.stats.setscene_total | ||
55 | self.needUpdate = True | 57 | self.needUpdate = True |
56 | elif isinstance(event, bb.build.TaskProgress): | 58 | elif isinstance(event, bb.build.TaskProgress): |
57 | if event.pid > 0 and event.pid in self.pidmap: | 59 | if event.pid > 0 and event.pid in self.pidmap: |