summaryrefslogtreecommitdiffstats
path: root/import-layers/yocto-poky/bitbake/lib/bb/ui
diff options
context:
space:
mode:
Diffstat (limited to 'import-layers/yocto-poky/bitbake/lib/bb/ui')
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/__init__.py17
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py1521
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py17
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py0
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py44
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py70
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py219
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py172
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py298
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py437
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py122
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py38
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py904
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py186
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py23
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py59
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade606
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py551
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py34
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py333
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py121
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_display.pngbin0 -> 6898 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_hover.pngbin0 -> 7051 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add-hover.pngbin0 -> 1212 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add.pngbin0 -> 1176 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/alert.pngbin0 -> 3954 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/confirmation.pngbin0 -> 5789 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/denied.pngbin0 -> 3955 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/error.pngbin0 -> 6482 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/info.pngbin0 -> 3311 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/issues.pngbin0 -> 4549 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/refresh.pngbin0 -> 5250 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove-hover.pngbin0 -> 2809 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove.pngbin0 -> 1971 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/tick.pngbin0 -> 4563 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_display.pngbin0 -> 4117 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_hover.pngbin0 -> 4167 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_display.pngbin0 -> 4840 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_hover.pngbin0 -> 5257 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_display.pngbin0 -> 7011 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_hover.pngbin0 -> 7121 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_display.pngbin0 -> 4723 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_hover.pngbin0 -> 4866 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_display.pngbin0 -> 6076 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_hover.pngbin0 -> 6269 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_display.pngbin0 -> 5651 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_hover.pngbin0 -> 5791 bytes
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py594
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py373
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py465
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py161
-rw-r--r--import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py59
52 files changed, 7424 insertions, 0 deletions
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/__init__.py
new file mode 100644
index 000000000..a4805ed02
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/__init__.py
@@ -0,0 +1,17 @@
+#
+# BitBake UI Implementation
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py
new file mode 100644
index 000000000..93979054d
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py
@@ -0,0 +1,1521 @@
+#
+# BitBake ToasterUI Implementation
+#
+# Copyright (C) 2013 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import bb
+import re
+import os
+
+os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings"
+
+
+import django
+from django.utils import timezone
+
+
+def _configure_toaster():
+ """ Add toaster to sys path for importing modules
+ """
+ sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster'))
+_configure_toaster()
+
+django.setup()
+
+from orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText
+from orm.models import Target_Image_File, BuildArtifact
+from orm.models import Variable, VariableHistory
+from orm.models import Package, Package_File, Target_Installed_Package, Target_File
+from orm.models import Task_Dependency, Package_Dependency
+from orm.models import Recipe_Dependency, Provides
+from orm.models import Project, CustomImagePackage, CustomImageRecipe
+
+from bldcontrol.models import BuildEnvironment, BuildRequest
+
+from bb.msg import BBLogFormatter as formatter
+from django.db import models
+from pprint import pformat
+import logging
+from datetime import datetime, timedelta
+
+from django.db import transaction, connection
+
+# pylint: disable=invalid-name
+# the logger name is standard throughout BitBake
+logger = logging.getLogger("ToasterLogger")
+
+
+class NotExisting(Exception):
+ pass
+
+class ORMWrapper(object):
+ """ This class creates the dictionaries needed to store information in the database
+ following the format defined by the Django models. It is also used to save this
+ information in the database.
+ """
+
+ def __init__(self):
+ self.layer_version_objects = []
+ self.layer_version_built = []
+ self.task_objects = {}
+ self.recipe_objects = {}
+
+ @staticmethod
+ def _build_key(**kwargs):
+ key = "0"
+ for k in sorted(kwargs.keys()):
+ if isinstance(kwargs[k], models.Model):
+ key += "-%d" % kwargs[k].id
+ else:
+ key += "-%s" % str(kwargs[k])
+ return key
+
+
+ def _cached_get_or_create(self, clazz, **kwargs):
+ """ This is a memory-cached get_or_create. We assume that the objects will not be created in the
+ database through any other means.
+ """
+
+ assert issubclass(clazz, models.Model), "_cached_get_or_create needs to get the class as first argument"
+
+ key = ORMWrapper._build_key(**kwargs)
+ dictname = "objects_%s" % clazz.__name__
+ if not dictname in vars(self).keys():
+ vars(self)[dictname] = {}
+
+ created = False
+ if not key in vars(self)[dictname].keys():
+ vars(self)[dictname][key], created = \
+ clazz.objects.get_or_create(**kwargs)
+
+ return (vars(self)[dictname][key], created)
+
+
+ def _cached_get(self, clazz, **kwargs):
+ """ This is a memory-cached get. We assume that the objects will not change in the database between gets.
+ """
+ assert issubclass(clazz, models.Model), "_cached_get needs to get the class as first argument"
+
+ key = ORMWrapper._build_key(**kwargs)
+ dictname = "objects_%s" % clazz.__name__
+
+ if not dictname in vars(self).keys():
+ vars(self)[dictname] = {}
+
+ if not key in vars(self)[dictname].keys():
+ vars(self)[dictname][key] = clazz.objects.get(**kwargs)
+
+ return vars(self)[dictname][key]
+
+ def _timestamp_to_datetime(self, secs):
+ """
+ Convert timestamp in seconds to Python datetime
+ """
+ return datetime(1970, 1, 1) + timedelta(seconds=secs)
+
+ # pylint: disable=no-self-use
+ # we disable detection of no self use in functions because the methods actually work on the object
+ # even if they don't touch self anywhere
+
+ # pylint: disable=bad-continuation
+ # we do not follow the python conventions for continuation indentation due to long lines here
+
+ def create_build_object(self, build_info, brbe, project_id):
+ assert 'machine' in build_info
+ assert 'distro' in build_info
+ assert 'distro_version' in build_info
+ assert 'started_on' in build_info
+ assert 'cooker_log_path' in build_info
+ assert 'build_name' in build_info
+ assert 'bitbake_version' in build_info
+
+ prj = None
+ buildrequest = None
+ if brbe is not None: # this build was triggered by a request from a user
+ logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
+ br, _ = brbe.split(":")
+ buildrequest = BuildRequest.objects.get(pk = br)
+ prj = buildrequest.project
+
+ elif project_id is not None: # this build was triggered by an external system for a specific project
+ logger.debug(1, "buildinfohelper: project is %s" % prj)
+ prj = Project.objects.get(pk = project_id)
+
+ else: # this build was triggered by a legacy system, or command line interactive mode
+ prj = Project.objects.get_or_create_default_project()
+ logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj)
+
+
+ if buildrequest is not None:
+ build = buildrequest.build
+ logger.info("Updating existing build, with %s", build_info)
+ build.project = prj
+ build.machine=build_info['machine']
+ build.distro=build_info['distro']
+ build.distro_version=build_info['distro_version']
+ build.cooker_log_path=build_info['cooker_log_path']
+ build.build_name=build_info['build_name']
+ build.bitbake_version=build_info['bitbake_version']
+ build.save()
+
+ else:
+ build = Build.objects.create(
+ project = prj,
+ machine=build_info['machine'],
+ distro=build_info['distro'],
+ distro_version=build_info['distro_version'],
+ started_on=build_info['started_on'],
+ completed_on=build_info['started_on'],
+ cooker_log_path=build_info['cooker_log_path'],
+ build_name=build_info['build_name'],
+ bitbake_version=build_info['bitbake_version'])
+
+ logger.debug(1, "buildinfohelper: build is created %s" % build)
+
+ if buildrequest is not None:
+ buildrequest.build = build
+ buildrequest.save()
+
+ return build
+
+ @staticmethod
+ def get_or_create_targets(target_info):
+ result = []
+ for target in target_info['targets']:
+ task = ''
+ if ':' in target:
+ target, task = target.split(':', 1)
+ if task.startswith('do_'):
+ task = task[3:]
+ if task == 'build':
+ task = ''
+ obj, created = Target.objects.get_or_create(build=target_info['build'],
+ target=target)
+ if created:
+ obj.is_image = False
+ if task:
+ obj.task = task
+ obj.save()
+ result.append(obj)
+ return result
+
+ def update_build_object(self, build, errors, warnings, taskfailures):
+ assert isinstance(build,Build)
+ assert isinstance(errors, int)
+ assert isinstance(warnings, int)
+
+ if build.outcome == Build.CANCELLED:
+ return
+ try:
+ if build.buildrequest.state == BuildRequest.REQ_CANCELLING:
+ return
+ except AttributeError:
+ # We may not have a buildrequest if this is a command line build
+ pass
+
+ outcome = Build.SUCCEEDED
+ if errors or taskfailures:
+ outcome = Build.FAILED
+
+ build.completed_on = timezone.now()
+ build.outcome = outcome
+ build.save()
+
+ def update_target_set_license_manifest(self, target, license_manifest_path):
+ target.license_manifest_path = license_manifest_path
+ target.save()
+
+ def update_task_object(self, build, task_name, recipe_name, task_stats):
+ """
+ Find the task for build which matches the recipe and task name
+ to be stored
+ """
+ task_to_update = Task.objects.get(
+ build = build,
+ task_name = task_name,
+ recipe__name = recipe_name
+ )
+
+ if 'started' in task_stats and 'ended' in task_stats:
+ task_to_update.started = self._timestamp_to_datetime(task_stats['started'])
+ task_to_update.ended = self._timestamp_to_datetime(task_stats['ended'])
+ task_to_update.elapsed_time = (task_stats['ended'] - task_stats['started'])
+ task_to_update.cpu_time_user = task_stats.get('cpu_time_user')
+ task_to_update.cpu_time_system = task_stats.get('cpu_time_system')
+ if 'disk_io_read' in task_stats and 'disk_io_write' in task_stats:
+ task_to_update.disk_io_read = task_stats['disk_io_read']
+ task_to_update.disk_io_write = task_stats['disk_io_write']
+ task_to_update.disk_io = task_stats['disk_io_read'] + task_stats['disk_io_write']
+
+ task_to_update.save()
+
+ def get_update_task_object(self, task_information, must_exist = False):
+ assert 'build' in task_information
+ assert 'recipe' in task_information
+ assert 'task_name' in task_information
+
+ # we use must_exist info for database look-up optimization
+ task_object, created = self._cached_get_or_create(Task,
+ build=task_information['build'],
+ recipe=task_information['recipe'],
+ task_name=task_information['task_name']
+ )
+ if created and must_exist:
+ task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk)
+ raise NotExisting("Task object created when expected to exist", task_information)
+
+ object_changed = False
+ for v in vars(task_object):
+ if v in task_information.keys():
+ if vars(task_object)[v] != task_information[v]:
+ vars(task_object)[v] = task_information[v]
+ object_changed = True
+
+ # update setscene-related information if the task has a setscene
+ if task_object.outcome == Task.OUTCOME_COVERED and 1 == task_object.get_related_setscene().count():
+ task_object.outcome = Task.OUTCOME_CACHED
+ object_changed = True
+
+ outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build,
+ recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome
+ if outcome_task_setscene == Task.OUTCOME_SUCCESS:
+ task_object.sstate_result = Task.SSTATE_RESTORED
+ object_changed = True
+ elif outcome_task_setscene == Task.OUTCOME_FAILED:
+ task_object.sstate_result = Task.SSTATE_FAILED
+ object_changed = True
+
+ if object_changed:
+ task_object.save()
+ return task_object
+
+
+ def get_update_recipe_object(self, recipe_information, must_exist = False):
+ assert 'layer_version' in recipe_information
+ assert 'file_path' in recipe_information
+ assert 'pathflags' in recipe_information
+
+ assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times
+
+
+ def update_recipe_obj(recipe_object):
+ object_changed = False
+ for v in vars(recipe_object):
+ if v in recipe_information.keys():
+ object_changed = True
+ vars(recipe_object)[v] = recipe_information[v]
+
+ if object_changed:
+ recipe_object.save()
+
+ recipe, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'],
+ file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags'])
+
+ update_recipe_obj(recipe)
+
+ built_recipe = None
+ # Create a copy of the recipe for historical puposes and update it
+ for built_layer in self.layer_version_built:
+ if built_layer.layer == recipe_information['layer_version'].layer:
+ built_recipe, c = self._cached_get_or_create(Recipe,
+ layer_version=built_layer,
+ file_path=recipe_information['file_path'],
+ pathflags = recipe_information['pathflags'])
+ update_recipe_obj(built_recipe)
+ break
+
+
+ # If we're in analysis mode or if this is a custom recipe
+ # then we are wholly responsible for the data
+ # and therefore we return the 'real' recipe rather than the build
+ # history copy of the recipe.
+ if recipe_information['layer_version'].build is not None and \
+ recipe_information['layer_version'].build.project == \
+ Project.objects.get_or_create_default_project():
+ return recipe
+
+ if built_recipe is None:
+ return recipe
+
+ return built_recipe
+
+ def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information):
+ if isinstance(layer_obj, Layer_Version):
+ # Special case the toaster-custom-images layer which is created
+ # on the fly so don't update the values which may cause the layer
+ # to be duplicated on a future get_or_create
+ if layer_obj.layer.name == CustomImageRecipe.LAYER_NAME:
+ return layer_obj
+ # We already found our layer version for this build so just
+ # update it with the new build information
+ logger.debug("We found our layer from toaster")
+ layer_obj.local_path = layer_version_information['local_path']
+ layer_obj.save()
+ self.layer_version_objects.append(layer_obj)
+
+ # create a new copy of this layer version as a snapshot for
+ # historical purposes
+ layer_copy, c = Layer_Version.objects.get_or_create(
+ build=build_obj,
+ layer=layer_obj.layer,
+ up_branch=layer_obj.up_branch,
+ branch=layer_version_information['branch'],
+ commit=layer_version_information['commit'],
+ local_path=layer_version_information['local_path'],
+ )
+
+ logger.info("created new historical layer version %d",
+ layer_copy.pk)
+
+ self.layer_version_built.append(layer_copy)
+
+ return layer_obj
+
+ assert isinstance(build_obj, Build)
+ assert isinstance(layer_obj, Layer)
+ assert 'branch' in layer_version_information
+ assert 'commit' in layer_version_information
+ assert 'priority' in layer_version_information
+ assert 'local_path' in layer_version_information
+
+ # If we're doing a command line build then associate this new layer with the
+ # project to avoid it 'contaminating' toaster data
+ project = None
+ if build_obj.project == Project.objects.get_or_create_default_project():
+ project = build_obj.project
+
+ layer_version_object, _ = Layer_Version.objects.get_or_create(
+ build = build_obj,
+ layer = layer_obj,
+ branch = layer_version_information['branch'],
+ commit = layer_version_information['commit'],
+ priority = layer_version_information['priority'],
+ local_path = layer_version_information['local_path'],
+ project=project)
+
+ self.layer_version_objects.append(layer_version_object)
+
+ return layer_version_object
+
+ def get_update_layer_object(self, layer_information, brbe):
+ assert 'name' in layer_information
+ assert 'layer_index_url' in layer_information
+
+ if brbe is None:
+ layer_object, _ = Layer.objects.get_or_create(
+ name=layer_information['name'],
+ layer_index_url=layer_information['layer_index_url'])
+ return layer_object
+ else:
+ # we are under managed mode; we must match the layer used in the Project Layer
+ br_id, be_id = brbe.split(":")
+
+ # find layer by checkout path;
+ from bldcontrol import bbcontroller
+ bc = bbcontroller.getBuildEnvironmentController(pk = be_id)
+
+ # we might have a race condition here, as the project layers may change between the build trigger and the actual build execution
+ # but we can only match on the layer name, so the worst thing can happen is a mis-identification of the layer, not a total failure
+
+ # note that this is different
+ buildrequest = BuildRequest.objects.get(pk = br_id)
+ for brl in buildrequest.brlayer_set.all():
+ localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath)
+ # we get a relative path, unless running in HEAD mode where the path is absolute
+ if not localdirname.startswith("/"):
+ localdirname = os.path.join(bc.be.sourcedir, localdirname)
+ #logger.debug(1, "Localdirname %s lcal_path %s" % (localdirname, layer_information['local_path']))
+ if localdirname.startswith(layer_information['local_path']):
+ # If the build request came from toaster this field
+ # should contain the information from the layer_version
+ # That created this build request.
+ if brl.layer_version:
+ return brl.layer_version
+
+ # we matched the BRLayer, but we need the layer_version that generated this BR; reverse of the Project.schedule_build()
+ #logger.debug(1, "Matched %s to BRlayer %s" % (pformat(layer_information["local_path"]), localdirname))
+
+ for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name):
+ if pl.layercommit.layer.vcs_url == brl.giturl :
+ layer = pl.layercommit.layer
+ layer.save()
+ return layer
+
+ raise NotExisting("Unidentified layer %s" % pformat(layer_information))
+
+
+ def save_target_file_information(self, build_obj, target_obj, filedata):
+ assert isinstance(build_obj, Build)
+ assert isinstance(target_obj, Target)
+ dirs = filedata['dirs']
+ files = filedata['files']
+ syms = filedata['syms']
+
+ # always create the root directory as a special case;
+ # note that this is never displayed, so the owner, group,
+ # size, permission are irrelevant
+ tf_obj = Target_File.objects.create(target = target_obj,
+ path = '/',
+ size = 0,
+ owner = '',
+ group = '',
+ permission = '',
+ inodetype = Target_File.ITYPE_DIRECTORY)
+ tf_obj.save()
+
+ # insert directories, ordered by name depth
+ for d in sorted(dirs, key=lambda x:len(x[-1].split("/"))):
+ (user, group, size) = d[1:4]
+ permission = d[0][1:]
+ path = d[4].lstrip(".")
+
+ # we already created the root directory, so ignore any
+ # entry for it
+ if len(path) == 0:
+ continue
+
+ parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+ if len(parent_path) == 0:
+ parent_path = "/"
+ parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+ tf_obj = Target_File.objects.create(
+ target = target_obj,
+ path = unicode(path, 'utf-8'),
+ size = size,
+ inodetype = Target_File.ITYPE_DIRECTORY,
+ permission = permission,
+ owner = user,
+ group = group,
+ directory = parent_obj)
+
+
+ # we insert files
+ for d in files:
+ (user, group, size) = d[1:4]
+ permission = d[0][1:]
+ path = d[4].lstrip(".")
+ parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+ inodetype = Target_File.ITYPE_REGULAR
+ if d[0].startswith('b'):
+ inodetype = Target_File.ITYPE_BLOCK
+ if d[0].startswith('c'):
+ inodetype = Target_File.ITYPE_CHARACTER
+ if d[0].startswith('p'):
+ inodetype = Target_File.ITYPE_FIFO
+
+ tf_obj = Target_File.objects.create(
+ target = target_obj,
+ path = unicode(path, 'utf-8'),
+ size = size,
+ inodetype = inodetype,
+ permission = permission,
+ owner = user,
+ group = group)
+ parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+ tf_obj.directory = parent_obj
+ tf_obj.save()
+
+ # we insert symlinks
+ for d in syms:
+ (user, group, size) = d[1:4]
+ permission = d[0][1:]
+ path = d[4].lstrip(".")
+ filetarget_path = d[6]
+
+ parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+ if not filetarget_path.startswith("/"):
+ # we have a relative path, get a normalized absolute one
+ filetarget_path = parent_path + "/" + filetarget_path
+ fcp = filetarget_path.split("/")
+ fcpl = []
+ for i in fcp:
+ if i == "..":
+ fcpl.pop()
+ else:
+ fcpl.append(i)
+ filetarget_path = "/".join(fcpl)
+
+ try:
+ filetarget_obj = Target_File.objects.get(
+ target = target_obj,
+ path = unicode(filetarget_path, 'utf-8'))
+ except Target_File.DoesNotExist:
+ # we might have an invalid link; no way to detect this. just set it to None
+ filetarget_obj = None
+
+ parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+
+ tf_obj = Target_File.objects.create(
+ target = target_obj,
+ path = unicode(path, 'utf-8'),
+ size = size,
+ inodetype = Target_File.ITYPE_SYMLINK,
+ permission = permission,
+ owner = user,
+ group = group,
+ directory = parent_obj,
+ sym_target = filetarget_obj)
+
+
+ def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes, built_package=False):
+ assert isinstance(build_obj, Build)
+ assert isinstance(target_obj, Target)
+
+ errormsg = ""
+ for p in packagedict:
+ # Search name swtiches round the installed name vs package name
+ # by default installed name == package name
+ searchname = p
+ if p not in pkgpnmap:
+ logger.warning("Image packages list contains %p, but is"
+ " missing from all packages list where the"
+ " metadata comes from. Skipping...", p)
+ continue
+
+ if 'OPKGN' in pkgpnmap[p].keys():
+ searchname = pkgpnmap[p]['OPKGN']
+
+ built_recipe = recipes[pkgpnmap[p]['PN']]
+
+ if built_package:
+ packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
+ recipe = built_recipe
+ else:
+ packagedict[p]['object'], created = \
+ CustomImagePackage.objects.get_or_create(name=searchname)
+ # Clear the Package_Dependency objects as we're going to update
+ # the CustomImagePackage with the latest dependency information
+ packagedict[p]['object'].package_dependencies_target.all().delete()
+ packagedict[p]['object'].package_dependencies_source.all().delete()
+ try:
+ recipe = self._cached_get(
+ Recipe,
+ name=built_recipe.name,
+ layer_version__build=None,
+ layer_version__up_branch=
+ built_recipe.layer_version.up_branch,
+ file_path=built_recipe.file_path,
+ version=built_recipe.version
+ )
+ except (Recipe.DoesNotExist,
+ Recipe.MultipleObjectsReturned) as e:
+ logger.info("We did not find one recipe for the"
+ "configuration data package %s %s" % (p, e))
+ continue
+
+ if created or packagedict[p]['object'].size == -1: # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887]
+ # fill in everything we can from the runtime-reverse package data
+ try:
+ packagedict[p]['object'].recipe = recipe
+ packagedict[p]['object'].version = pkgpnmap[p]['PV']
+ packagedict[p]['object'].installed_name = p
+ packagedict[p]['object'].revision = pkgpnmap[p]['PR']
+ packagedict[p]['object'].license = pkgpnmap[p]['LICENSE']
+ packagedict[p]['object'].section = pkgpnmap[p]['SECTION']
+ packagedict[p]['object'].summary = pkgpnmap[p]['SUMMARY']
+ packagedict[p]['object'].description = pkgpnmap[p]['DESCRIPTION']
+ packagedict[p]['object'].size = int(pkgpnmap[p]['PKGSIZE'])
+
+ # no files recorded for this package, so save files info
+ packagefile_objects = []
+ for targetpath in pkgpnmap[p]['FILES_INFO']:
+ targetfilesize = pkgpnmap[p]['FILES_INFO'][targetpath]
+ packagefile_objects.append(Package_File( package = packagedict[p]['object'],
+ path = targetpath,
+ size = targetfilesize))
+ if len(packagefile_objects):
+ Package_File.objects.bulk_create(packagefile_objects)
+ except KeyError as e:
+ errormsg += " stpi: Key error, package %s key %s \n" % ( p, e )
+
+ # save disk installed size
+ packagedict[p]['object'].installed_size = packagedict[p]['size']
+ packagedict[p]['object'].save()
+
+ if built_package:
+ Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
+
+ packagedeps_objs = []
+ for p in packagedict:
+ for (px,deptype) in packagedict[p]['depends']:
+ if deptype == 'depends':
+ tdeptype = Package_Dependency.TYPE_TRDEPENDS
+ elif deptype == 'recommends':
+ tdeptype = Package_Dependency.TYPE_TRECOMMENDS
+
+ try:
+ packagedeps_objs.append(Package_Dependency(
+ package = packagedict[p]['object'],
+ depends_on = packagedict[px]['object'],
+ dep_type = tdeptype,
+ target = target_obj))
+ except KeyError as e:
+ logger.warn("Could not add dependency to the package %s "
+ "because %s is an unknown package", p, px)
+
+ if len(packagedeps_objs) > 0:
+ Package_Dependency.objects.bulk_create(packagedeps_objs)
+ else:
+ logger.info("No package dependencies created")
+
+ if len(errormsg) > 0:
+ logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg)
+
+ def save_target_image_file_information(self, target_obj, file_name, file_size):
+ Target_Image_File.objects.create( target = target_obj,
+ file_name = file_name,
+ file_size = file_size)
+
+ def save_artifact_information(self, build_obj, file_name, file_size):
+ # we skip the image files from other builds
+ if Target_Image_File.objects.filter(file_name = file_name).count() > 0:
+ return
+
+ # do not update artifacts found in other builds
+ if BuildArtifact.objects.filter(file_name = file_name).count() > 0:
+ return
+
+ BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size)
+
+ def create_logmessage(self, log_information):
+ assert 'build' in log_information
+ assert 'level' in log_information
+ assert 'message' in log_information
+
+ log_object = LogMessage.objects.create(
+ build = log_information['build'],
+ level = log_information['level'],
+ message = log_information['message'])
+
+ for v in vars(log_object):
+ if v in log_information.keys():
+ vars(log_object)[v] = log_information[v]
+
+ return log_object.save()
+
+
+ def save_build_package_information(self, build_obj, package_info, recipes,
+ built_package):
+ # assert isinstance(build_obj, Build)
+
+ # create and save the object
+ pname = package_info['PKG']
+ built_recipe = recipes[package_info['PN']]
+ if 'OPKGN' in package_info.keys():
+ pname = package_info['OPKGN']
+
+ if built_package:
+ bp_object, _ = Package.objects.get_or_create( build = build_obj,
+ name = pname )
+ recipe = built_recipe
+ else:
+ bp_object, created = \
+ CustomImagePackage.objects.get_or_create(name=pname)
+ try:
+ recipe = self._cached_get(Recipe,
+ name=built_recipe.name,
+ layer_version__build=None,
+ file_path=built_recipe.file_path,
+ version=built_recipe.version)
+
+ except (Recipe.DoesNotExist, Recipe.MultipleObjectsReturned):
+ logger.debug("We did not find one recipe for the configuration"
+ "data package %s" % pname)
+ return
+
+ bp_object.installed_name = package_info['PKG']
+ bp_object.recipe = recipe
+ bp_object.version = package_info['PKGV']
+ bp_object.revision = package_info['PKGR']
+ bp_object.summary = package_info['SUMMARY']
+ bp_object.description = package_info['DESCRIPTION']
+ bp_object.size = int(package_info['PKGSIZE'])
+ bp_object.section = package_info['SECTION']
+ bp_object.license = package_info['LICENSE']
+ bp_object.save()
+
+ # save any attached file information
+ packagefile_objects = []
+ for path in package_info['FILES_INFO']:
+ packagefile_objects.append(Package_File( package = bp_object,
+ path = path,
+ size = package_info['FILES_INFO'][path] ))
+ if len(packagefile_objects):
+ Package_File.objects.bulk_create(packagefile_objects)
+
+ def _po_byname(p):
+ if built_package:
+ pkg, created = Package.objects.get_or_create(build=build_obj,
+ name=p)
+ else:
+ pkg, created = CustomImagePackage.objects.get_or_create(name=p)
+
+ if created:
+ pkg.size = -1
+ pkg.save()
+ return pkg
+
+ packagedeps_objs = []
+ # save soft dependency information
+ if 'RDEPENDS' in package_info and package_info['RDEPENDS']:
+ for p in bb.utils.explode_deps(package_info['RDEPENDS']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RDEPENDS))
+ if 'RPROVIDES' in package_info and package_info['RPROVIDES']:
+ for p in bb.utils.explode_deps(package_info['RPROVIDES']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RPROVIDES))
+ if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']:
+ for p in bb.utils.explode_deps(package_info['RRECOMMENDS']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RRECOMMENDS))
+ if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']:
+ for p in bb.utils.explode_deps(package_info['RSUGGESTS']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RSUGGESTS))
+ if 'RREPLACES' in package_info and package_info['RREPLACES']:
+ for p in bb.utils.explode_deps(package_info['RREPLACES']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RREPLACES))
+ if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']:
+ for p in bb.utils.explode_deps(package_info['RCONFLICTS']):
+ packagedeps_objs.append(Package_Dependency( package = bp_object,
+ depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS))
+
+ if len(packagedeps_objs) > 0:
+ Package_Dependency.objects.bulk_create(packagedeps_objs)
+
+ return bp_object
+
+ def save_build_variables(self, build_obj, vardump):
+ assert isinstance(build_obj, Build)
+
+ for k in vardump:
+ desc = vardump[k]['doc']
+ if desc is None:
+ var_words = [word for word in k.split('_')]
+ root_var = "_".join([word for word in var_words if word.isupper()])
+ if root_var and root_var != k and root_var in vardump:
+ desc = vardump[root_var]['doc']
+ if desc is None:
+ desc = ''
+ if len(desc):
+ HelpText.objects.get_or_create(build=build_obj,
+ area=HelpText.VARIABLE,
+ key=k, text=desc)
+ if not bool(vardump[k]['func']):
+ value = vardump[k]['v']
+ if value is None:
+ value = ''
+ variable_obj = Variable.objects.create( build = build_obj,
+ variable_name = k,
+ variable_value = value,
+ description = desc)
+
+ varhist_objects = []
+ for vh in vardump[k]['history']:
+ if not 'documentation.conf' in vh['file']:
+ varhist_objects.append(VariableHistory( variable = variable_obj,
+ file_name = vh['file'],
+ line_number = vh['line'],
+ operation = vh['op']))
+ if len(varhist_objects):
+ VariableHistory.objects.bulk_create(varhist_objects)
+
+
+class MockEvent(object):
+ """ This object is used to create event, for which normal event-processing methods can
+ be used, out of data that is not coming via an actual event
+ """
+ def __init__(self):
+ self.msg = None
+ self.levelno = None
+ self.taskname = None
+ self.taskhash = None
+ self.pathname = None
+ self.lineno = None
+
+
+class BuildInfoHelper(object):
+ """ This class gathers the build information from the server and sends it
+ towards the ORM wrapper for storing in the database
+ It is instantiated once per build
+ Keeps in memory all data that needs matching before writing it to the database
+ """
+
+ # pylint: disable=protected-access
+ # the code will look into the protected variables of the event; no easy way around this
+ # pylint: disable=bad-continuation
+ # we do not follow the python conventions for continuation indentation due to long lines here
+
+ def __init__(self, server, has_build_history = False, brbe = None):
+ self.internal_state = {}
+ self.internal_state['taskdata'] = {}
+ self.internal_state['targets'] = []
+ self.task_order = 0
+ self.autocommit_step = 1
+ self.server = server
+ # we use manual transactions if the database doesn't autocommit on us
+ if not connection.features.autocommits_when_autocommit_is_off:
+ transaction.set_autocommit(False)
+ self.orm_wrapper = ORMWrapper()
+ self.has_build_history = has_build_history
+ self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
+
+ # this is set for Toaster-triggered builds by localhostbecontroller
+ # via toasterui
+ self.brbe = brbe
+
+ self.project = None
+
+ logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
+
+
+ ###################
+ ## methods to convert event/external info into objects that the ORM layer uses
+
+
+ def _get_build_information(self, build_log_path):
+ build_info = {}
+ build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
+ build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
+ build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
+ build_info['started_on'] = timezone.now()
+ build_info['completed_on'] = timezone.now()
+ build_info['cooker_log_path'] = build_log_path
+ build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
+ build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
+ build_info['project'] = self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0]
+ return build_info
+
+ def _get_task_information(self, event, recipe):
+ assert 'taskname' in vars(event)
+
+ task_information = {}
+ task_information['build'] = self.internal_state['build']
+ task_information['outcome'] = Task.OUTCOME_NA
+ task_information['recipe'] = recipe
+ task_information['task_name'] = event.taskname
+ try:
+ # some tasks don't come with a hash. and that's ok
+ task_information['sstate_checksum'] = event.taskhash
+ except AttributeError:
+ pass
+ return task_information
+
+ def _get_layer_version_for_path(self, path):
+ assert path.startswith("/")
+ assert 'build' in self.internal_state
+
+ def _slkey_interactive(layer_version):
+ assert isinstance(layer_version, Layer_Version)
+ return len(layer_version.local_path)
+
+ # Heuristics: we always match recipe to the deepest layer path in the discovered layers
+ for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive):
+ # we can match to the recipe file path
+ if path.startswith(lvo.local_path):
+ return lvo
+
+ #if we get here, we didn't read layers correctly; dump whatever information we have on the error log
+ logger.warn("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects)
+
+ #mockup the new layer
+ unknown_layer, _ = Layer.objects.get_or_create(name="Unidentified layer", layer_index_url="")
+ unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
+
+ # append it so we don't run into this error again and again
+ self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj)
+
+ return unknown_layer_version_obj
+
+ def _get_recipe_information_from_taskfile(self, taskfile):
+ localfilepath = taskfile.split(":")[-1]
+ filepath_flags = ":".join(sorted(taskfile.split(":")[:-1]))
+ layer_version_obj = self._get_layer_version_for_path(localfilepath)
+
+
+
+ recipe_info = {}
+ recipe_info['layer_version'] = layer_version_obj
+ recipe_info['file_path'] = localfilepath
+ recipe_info['pathflags'] = filepath_flags
+
+ if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+ recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+ else:
+ raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
+
+ return recipe_info
+
+ def _get_path_information(self, task_object):
+ assert isinstance(task_object, Task)
+ build_stats_format = "{tmpdir}/buildstats/{buildname}/{package}/"
+ build_stats_path = []
+
+ for t in self.internal_state['targets']:
+ buildname = self.internal_state['build'].build_name
+ pe, pv = task_object.recipe.version.split(":",1)
+ if len(pe) > 0:
+ package = task_object.recipe.name + "-" + pe + "_" + pv
+ else:
+ package = task_object.recipe.name + "-" + pv
+
+ build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir,
+ buildname=buildname,
+ package=package))
+
+ return build_stats_path
+
+
+ ################################
+ ## external available methods to store information
+ @staticmethod
+ def _get_data_from_event(event):
+ evdata = None
+ if '_localdata' in vars(event):
+ evdata = event._localdata
+ elif 'data' in vars(event):
+ evdata = event.data
+ else:
+ raise Exception("Event with neither _localdata or data properties")
+ return evdata
+
+ def store_layer_info(self, event):
+ layerinfos = BuildInfoHelper._get_data_from_event(event)
+ self.internal_state['lvs'] = {}
+ for layer in layerinfos:
+ try:
+ self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
+ self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path']
+ except NotExisting as nee:
+ logger.warn("buildinfohelper: cannot identify layer exception:%s ", nee)
+
+
+ def store_started_build(self, event, build_log_path):
+ assert '_pkgs' in vars(event)
+ build_information = self._get_build_information(build_log_path)
+
+ # Update brbe and project as they can be changed for every build
+ self.project = build_information['project']
+
+ build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project)
+
+ self.internal_state['build'] = build_obj
+
+ # save layer version information for this build
+ if not 'lvs' in self.internal_state:
+ logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
+ else:
+ for layer_obj in self.internal_state['lvs']:
+ self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
+
+ del self.internal_state['lvs']
+
+ # create target information
+ target_information = {}
+ target_information['targets'] = event._pkgs
+ target_information['build'] = build_obj
+
+ self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information)
+
+ # Save build configuration
+ data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
+
+ # convert the paths from absolute to relative to either the build directory or layer checkouts
+ path_prefixes = []
+
+ if self.brbe is not None:
+ _, be_id = self.brbe.split(":")
+ be = BuildEnvironment.objects.get(pk = be_id)
+ path_prefixes.append(be.builddir)
+
+ for layer in sorted(self.orm_wrapper.layer_version_objects, key = lambda x:len(x.local_path), reverse=True):
+ path_prefixes.append(layer.local_path)
+
+ # we strip the prefixes
+ for k in data:
+ if not bool(data[k]['func']):
+ for vh in data[k]['history']:
+ if not 'documentation.conf' in vh['file']:
+ abs_file_name = vh['file']
+ for pp in path_prefixes:
+ if abs_file_name.startswith(pp + "/"):
+ vh['file']=abs_file_name[len(pp + "/"):]
+ break
+
+ # save the variables
+ self.orm_wrapper.save_build_variables(build_obj, data)
+
+ return self.brbe
+
+
+ def update_target_image_file(self, event):
+ evdata = BuildInfoHelper._get_data_from_event(event)
+
+ for t in self.internal_state['targets']:
+ if t.is_image == True:
+ output_files = list(evdata.viewkeys())
+ for output in output_files:
+ if t.target in output and 'rootfs' in output and not output.endswith(".manifest"):
+ self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
+
+ def update_artifact_image_file(self, event):
+ evdata = BuildInfoHelper._get_data_from_event(event)
+ for artifact_path in evdata.keys():
+ self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path])
+
+ def update_build_information(self, event, errors, warnings, taskfailures):
+ if 'build' in self.internal_state:
+ self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
+
+
+ def store_license_manifest_path(self, event):
+ deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
+ image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
+ path = deploy_dir + "/licenses/" + image_name + "/license.manifest"
+ for target in self.internal_state['targets']:
+ if target.target in image_name:
+ self.orm_wrapper.update_target_set_license_manifest(target, path)
+
+
+ def store_started_task(self, event):
+ assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped))
+ assert 'taskfile' in vars(event)
+ localfilepath = event.taskfile.split(":")[-1]
+ assert localfilepath.startswith("/")
+
+ identifier = event.taskfile + ":" + event.taskname
+
+ recipe_information = self._get_recipe_information_from_taskfile(event.taskfile)
+ recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
+
+ task_information = self._get_task_information(event, recipe)
+ task_information['outcome'] = Task.OUTCOME_NA
+
+ if isinstance(event, bb.runqueue.runQueueTaskSkipped):
+ assert 'reason' in vars(event)
+ task_information['task_executed'] = False
+ if event.reason == "covered":
+ task_information['outcome'] = Task.OUTCOME_COVERED
+ if event.reason == "existing":
+ task_information['outcome'] = Task.OUTCOME_PREBUILT
+ else:
+ task_information['task_executed'] = True
+ if 'noexec' in vars(event) and event.noexec == True:
+ task_information['task_executed'] = False
+ task_information['outcome'] = Task.OUTCOME_EMPTY
+ task_information['script_type'] = Task.CODING_NA
+
+ # do not assign order numbers to scene tasks
+ if not isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+ self.task_order += 1
+ task_information['order'] = self.task_order
+
+ self.orm_wrapper.get_update_task_object(task_information)
+
+ self.internal_state['taskdata'][identifier] = {
+ 'outcome': task_information['outcome'],
+ }
+
+
+ def store_tasks_stats(self, event):
+ task_data = BuildInfoHelper._get_data_from_event(event)
+
+ for (task_file, task_name, task_stats, recipe_name) in task_data:
+ build = self.internal_state['build']
+ self.orm_wrapper.update_task_object(build, task_name, recipe_name, task_stats)
+
+ def update_and_store_task(self, event):
+ assert 'taskfile' in vars(event)
+ localfilepath = event.taskfile.split(":")[-1]
+ assert localfilepath.startswith("/")
+
+ identifier = event.taskfile + ":" + event.taskname
+ if not identifier in self.internal_state['taskdata']:
+ if isinstance(event, bb.build.TaskBase):
+ # we do a bit of guessing
+ candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)]
+ if len(candidates) == 1:
+ identifier = candidates[0]
+
+ assert identifier in self.internal_state['taskdata']
+ identifierlist = identifier.split(":")
+ realtaskfile = ":".join(identifierlist[0:len(identifierlist)-1])
+ recipe_information = self._get_recipe_information_from_taskfile(realtaskfile)
+ recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
+ task_information = self._get_task_information(event,recipe)
+
+ task_information['outcome'] = self.internal_state['taskdata'][identifier]['outcome']
+
+ if 'logfile' in vars(event):
+ task_information['logfile'] = event.logfile
+
+ if '_message' in vars(event):
+ task_information['message'] = event._message
+
+ if 'taskflags' in vars(event):
+ # with TaskStarted, we get even more information
+ if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1':
+ task_information['script_type'] = Task.CODING_PYTHON
+ else:
+ task_information['script_type'] = Task.CODING_SHELL
+
+ if task_information['outcome'] == Task.OUTCOME_NA:
+ if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
+ task_information['outcome'] = Task.OUTCOME_SUCCESS
+ del self.internal_state['taskdata'][identifier]
+
+ if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runqueue.sceneQueueTaskFailed)):
+ task_information['outcome'] = Task.OUTCOME_FAILED
+ del self.internal_state['taskdata'][identifier]
+
+ if not connection.features.autocommits_when_autocommit_is_off:
+ # we force a sync point here, to get the progress bar to show
+ if self.autocommit_step % 3 == 0:
+ transaction.set_autocommit(True)
+ transaction.set_autocommit(False)
+ self.autocommit_step += 1
+
+ self.orm_wrapper.get_update_task_object(task_information, True) # must exist
+
+
+ def store_missed_state_tasks(self, event):
+ for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']:
+
+ # identifier = fn + taskname + "_setscene"
+ recipe_information = self._get_recipe_information_from_taskfile(fn)
+ recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
+ mevent = MockEvent()
+ mevent.taskname = taskname
+ mevent.taskhash = taskhash
+ task_information = self._get_task_information(mevent,recipe)
+
+ task_information['start_time'] = timezone.now()
+ task_information['outcome'] = Task.OUTCOME_NA
+ task_information['sstate_checksum'] = taskhash
+ task_information['sstate_result'] = Task.SSTATE_MISS
+ task_information['path_to_sstate_obj'] = sstatefile
+
+ self.orm_wrapper.get_update_task_object(task_information)
+
+ for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']:
+
+ # identifier = fn + taskname + "_setscene"
+ recipe_information = self._get_recipe_information_from_taskfile(fn)
+ recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
+ mevent = MockEvent()
+ mevent.taskname = taskname
+ mevent.taskhash = taskhash
+ task_information = self._get_task_information(mevent,recipe)
+
+ task_information['path_to_sstate_obj'] = sstatefile
+
+ self.orm_wrapper.get_update_task_object(task_information)
+
+
+ def store_target_package_data(self, event):
+ # for all image targets
+ for target in self.internal_state['targets']:
+ if target.is_image:
+ pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata']
+ imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'].get(target.target, {})
+ filedata = BuildInfoHelper._get_data_from_event(event)['filedata'].get(target.target, {})
+
+ try:
+ self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True)
+ self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False)
+ except KeyError as e:
+ logger.warn("KeyError in save_target_package_information"
+ "%s ", e)
+
+ try:
+ self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata)
+ except KeyError as e:
+ logger.warn("KeyError in save_target_file_information"
+ "%s ", e)
+
+
+
+
+ def store_dependency_information(self, event):
+ assert '_depgraph' in vars(event)
+ assert 'layer-priorities' in event._depgraph
+ assert 'pn' in event._depgraph
+ assert 'tdepends' in event._depgraph
+
+ errormsg = ""
+
+ # save layer version priorities
+ if 'layer-priorities' in event._depgraph.keys():
+ for lv in event._depgraph['layer-priorities']:
+ (_, path, _, priority) = lv
+ layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
+ assert layer_version_obj is not None
+ layer_version_obj.priority = priority
+ layer_version_obj.save()
+
+ # save recipe information
+ self.internal_state['recipes'] = {}
+ for pn in event._depgraph['pn']:
+
+ file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1]
+ pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1]))
+ layer_version_obj = self._get_layer_version_for_path(file_name)
+
+ assert layer_version_obj is not None
+
+ recipe_info = {}
+ recipe_info['name'] = pn
+ recipe_info['layer_version'] = layer_version_obj
+
+ if 'version' in event._depgraph['pn'][pn]:
+ recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
+
+ if 'summary' in event._depgraph['pn'][pn]:
+ recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
+
+ if 'license' in event._depgraph['pn'][pn]:
+ recipe_info['license'] = event._depgraph['pn'][pn]['license']
+
+ if 'description' in event._depgraph['pn'][pn]:
+ recipe_info['description'] = event._depgraph['pn'][pn]['description']
+
+ if 'section' in event._depgraph['pn'][pn]:
+ recipe_info['section'] = event._depgraph['pn'][pn]['section']
+
+ if 'homepage' in event._depgraph['pn'][pn]:
+ recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
+
+ if 'bugtracker' in event._depgraph['pn'][pn]:
+ recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
+ recipe_info['file_path'] = file_name
+ recipe_info['pathflags'] = pathflags
+
+ if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+ recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+ else:
+ raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
+
+ recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
+ recipe.is_image = False
+ if 'inherits' in event._depgraph['pn'][pn].keys():
+ for cls in event._depgraph['pn'][pn]['inherits']:
+ if cls.endswith('/image.bbclass'):
+ recipe.is_image = True
+ recipe_info['is_image'] = True
+ # Save the is_image state to the relevant recipe objects
+ self.orm_wrapper.get_update_recipe_object(recipe_info)
+ break
+ if recipe.is_image:
+ for t in self.internal_state['targets']:
+ if pn == t.target:
+ t.is_image = True
+ t.save()
+ self.internal_state['recipes'][pn] = recipe
+
+ # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
+
+ assume_provided = self.server.runCommand(["getVariable", "ASSUME_PROVIDED"])[0].split()
+
+ # save recipe dependency
+ # buildtime
+ recipedeps_objects = []
+ for recipe in event._depgraph['depends']:
+ target = self.internal_state['recipes'][recipe]
+ for dep in event._depgraph['depends'][recipe]:
+ if dep in assume_provided:
+ continue
+ via = None
+ if 'providermap' in event._depgraph and dep in event._depgraph['providermap']:
+ deprecipe = event._depgraph['providermap'][dep][0]
+ dependency = self.internal_state['recipes'][deprecipe]
+ via = Provides.objects.get_or_create(name=dep,
+ recipe=dependency)[0]
+ elif dep in self.internal_state['recipes']:
+ dependency = self.internal_state['recipes'][dep]
+ else:
+ errormsg += " stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep)
+ continue
+ recipe_dep = Recipe_Dependency(recipe=target,
+ depends_on=dependency,
+ via=via,
+ dep_type=Recipe_Dependency.TYPE_DEPENDS)
+ recipedeps_objects.append(recipe_dep)
+
+ Recipe_Dependency.objects.bulk_create(recipedeps_objects)
+
+ # save all task information
+ def _save_a_task(taskdesc):
+ spec = re.split(r'\.', taskdesc)
+ pn = ".".join(spec[0:-1])
+ taskname = spec[-1]
+ e = event
+ e.taskname = pn
+ recipe = self.internal_state['recipes'][pn]
+ task_info = self._get_task_information(e, recipe)
+ task_info['task_name'] = taskname
+ task_obj = self.orm_wrapper.get_update_task_object(task_info)
+ return task_obj
+
+ # create tasks
+ tasks = {}
+ for taskdesc in event._depgraph['tdepends']:
+ tasks[taskdesc] = _save_a_task(taskdesc)
+
+ # create dependencies between tasks
+ taskdeps_objects = []
+ for taskdesc in event._depgraph['tdepends']:
+ target = tasks[taskdesc]
+ for taskdep in event._depgraph['tdepends'][taskdesc]:
+ if taskdep not in tasks:
+ # Fetch tasks info is not collected previously
+ dep = _save_a_task(taskdep)
+ else:
+ dep = tasks[taskdep]
+ taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep ))
+ Task_Dependency.objects.bulk_create(taskdeps_objects)
+
+ if len(errormsg) > 0:
+ logger.warn("buildinfohelper: dependency info not identify recipes: \n%s", errormsg)
+
+
+ def store_build_package_information(self, event):
+ package_info = BuildInfoHelper._get_data_from_event(event)
+ self.orm_wrapper.save_build_package_information(
+ self.internal_state['build'],
+ package_info,
+ self.internal_state['recipes'],
+ built_package=True)
+
+ self.orm_wrapper.save_build_package_information(
+ self.internal_state['build'],
+ package_info,
+ self.internal_state['recipes'],
+ built_package=False)
+
+ def _store_build_done(self, errorcode):
+ logger.info("Build exited with errorcode %d", errorcode)
+ br_id, be_id = self.brbe.split(":")
+ be = BuildEnvironment.objects.get(pk = be_id)
+ be.lock = BuildEnvironment.LOCK_LOCK
+ be.save()
+ br = BuildRequest.objects.get(pk = br_id)
+
+ # if we're 'done' because we got cancelled update the build outcome
+ if br.state == BuildRequest.REQ_CANCELLING:
+ logger.info("Build cancelled")
+ br.build.outcome = Build.CANCELLED
+ br.build.save()
+ self.internal_state['build'] = br.build
+ errorcode = 0
+
+ if errorcode == 0:
+ # request archival of the project artifacts
+ br.state = BuildRequest.REQ_COMPLETED
+ else:
+ br.state = BuildRequest.REQ_FAILED
+ br.save()
+
+
+ def store_log_error(self, text):
+ mockevent = MockEvent()
+ mockevent.levelno = formatter.ERROR
+ mockevent.msg = text
+ mockevent.pathname = '-- None'
+ mockevent.lineno = LogMessage.ERROR
+ self.store_log_event(mockevent)
+
+ def store_log_exception(self, text, backtrace = ""):
+ mockevent = MockEvent()
+ mockevent.levelno = -1
+ mockevent.msg = text
+ mockevent.pathname = backtrace
+ mockevent.lineno = -1
+ self.store_log_event(mockevent)
+
+
+ def store_log_event(self, event):
+ if event.levelno < formatter.WARNING:
+ return
+
+ if 'args' in vars(event):
+ event.msg = event.msg % event.args
+
+ if not 'build' in self.internal_state:
+ if self.brbe is None:
+ if not 'backlog' in self.internal_state:
+ self.internal_state['backlog'] = []
+ self.internal_state['backlog'].append(event)
+ return
+ else: # we're under Toaster control, the build is already created
+ br, _ = self.brbe.split(":")
+ buildrequest = BuildRequest.objects.get(pk = br)
+ self.internal_state['build'] = buildrequest.build
+
+ if 'build' in self.internal_state and 'backlog' in self.internal_state:
+ # if we have a backlog of events, do our best to save them here
+ if len(self.internal_state['backlog']):
+ tempevent = self.internal_state['backlog'].pop()
+ logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
+ self.store_log_event(tempevent)
+ else:
+ logger.info("buildinfohelper: All events saved")
+ del self.internal_state['backlog']
+
+ log_information = {}
+ log_information['build'] = self.internal_state['build']
+ if event.levelno == formatter.CRITICAL:
+ log_information['level'] = LogMessage.CRITICAL
+ elif event.levelno == formatter.ERROR:
+ log_information['level'] = LogMessage.ERROR
+ elif event.levelno == formatter.WARNING:
+ log_information['level'] = LogMessage.WARNING
+ elif event.levelno == -2: # toaster self-logging
+ log_information['level'] = -2
+ else:
+ log_information['level'] = LogMessage.INFO
+
+ log_information['message'] = event.msg
+ log_information['pathname'] = event.pathname
+ log_information['lineno'] = event.lineno
+ logger.info("Logging error 2: %s", log_information)
+
+ self.orm_wrapper.create_logmessage(log_information)
+
+ def close(self, errorcode):
+ if self.brbe is not None:
+ self._store_build_done(errorcode)
+
+ if 'backlog' in self.internal_state:
+ if 'build' in self.internal_state:
+ # we save missed events in the database for the current build
+ tempevent = self.internal_state['backlog'].pop()
+ self.store_log_event(tempevent)
+ else:
+ # we have no build, and we still have events; something amazingly wrong happend
+ for event in self.internal_state['backlog']:
+ logger.error("UNSAVED log: %s", event.msg)
+
+ if not connection.features.autocommits_when_autocommit_is_off:
+ transaction.set_autocommit(True)
+
+ # unset the brbe; this is to prevent subsequent command-line builds
+ # being incorrectly attached to the previous Toaster-triggered build;
+ # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9021
+ self.brbe = None
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py
new file mode 100644
index 000000000..b7cbe1a4f
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py
@@ -0,0 +1,17 @@
+#
+# Gtk+ UI pieces for BitBake
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
new file mode 100644
index 000000000..c679f9a07
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
@@ -0,0 +1,44 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CrumbsDialog(gtk.Dialog):
+ """
+ A GNOME HIG compliant dialog widget.
+ Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
+ """
+ def __init__(self, title="", parent=None, flags=0, buttons=None):
+ super(CrumbsDialog, self).__init__(title, parent, flags, buttons)
+
+ self.set_property("has-separator", False) # note: deprecated in 2.22
+
+ self.set_border_width(6)
+ self.vbox.set_property("spacing", 12)
+ self.action_area.set_property("spacing", 12)
+ self.action_area.set_property("border-width", 6)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
new file mode 100644
index 000000000..3b998e463
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
@@ -0,0 +1,70 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import glib
+import gtk
+from bb.ui.crumbs.hobwidget import HobIconChecker
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CrumbsMessageDialog(gtk.MessageDialog):
+ """
+ A GNOME HIG compliant dialog widget.
+ Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
+ """
+ def __init__(self, parent = None, label="", dialog_type = gtk.MESSAGE_QUESTION, msg=""):
+ super(CrumbsMessageDialog, self).__init__(None,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+ dialog_type,
+ gtk.BUTTONS_NONE,
+ None)
+
+ self.set_skip_taskbar_hint(False)
+
+ self.set_markup(label)
+
+ if 0 <= len(msg) < 300:
+ self.format_secondary_markup(msg)
+ else:
+ vbox = self.get_message_area()
+ vbox.set_border_width(1)
+ vbox.set_property("spacing", 12)
+ self.textWindow = gtk.ScrolledWindow()
+ self.textWindow.set_shadow_type(gtk.SHADOW_IN)
+ self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.msgView = gtk.TextView()
+ self.msgView.set_editable(False)
+ self.msgView.set_wrap_mode(gtk.WRAP_WORD)
+ self.msgView.set_cursor_visible(False)
+ self.msgView.set_size_request(300, 300)
+ self.buf = gtk.TextBuffer()
+ self.buf.set_text(msg)
+ self.msgView.set_buffer(self.buf)
+ self.textWindow.add(self.msgView)
+ self.msgView.show()
+ vbox.add(self.textWindow)
+ self.textWindow.show()
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
new file mode 100644
index 000000000..a13fff906
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
@@ -0,0 +1,219 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import glob
+import gtk
+import gobject
+import os
+import re
+import shlex
+import subprocess
+import tempfile
+from bb.ui.crumbs.hobwidget import hic, HobButton
+from bb.ui.crumbs.progressbar import HobProgressBar
+import bb.ui.crumbs.utils
+import bb.process
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class DeployImageDialog (CrumbsDialog):
+
+ __dummy_usb__ = "--select a usb drive--"
+
+ def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False):
+ super(DeployImageDialog, self).__init__(title, parent, flags, buttons)
+
+ self.image_path = image_path
+ self.standalone = standalone
+
+ self.create_visual_elements()
+ self.connect("response", self.response_cb)
+
+ def create_visual_elements(self):
+ self.set_size_request(600, 400)
+ label = gtk.Label()
+ label.set_alignment(0.0, 0.5)
+ markup = "<span font_desc='12'>The image to be written into usb drive:</span>"
+ label.set_markup(markup)
+ self.vbox.pack_start(label, expand=False, fill=False, padding=2)
+
+ table = gtk.Table(2, 10, False)
+ table.set_col_spacings(5)
+ table.set_row_spacings(5)
+ self.vbox.pack_start(table, expand=True, fill=True)
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scroll.set_shadow_type(gtk.SHADOW_IN)
+ tv = gtk.TextView()
+ tv.set_editable(False)
+ tv.set_wrap_mode(gtk.WRAP_WORD)
+ tv.set_cursor_visible(False)
+ self.buf = gtk.TextBuffer()
+ self.buf.set_text(self.image_path)
+ tv.set_buffer(self.buf)
+ scroll.add(tv)
+ table.attach(scroll, 0, 10, 0, 1)
+
+ # There are 2 ways to use DeployImageDialog
+ # One way is that called by HOB when the 'Deploy Image' button is clicked
+ # The other way is that called by a standalone script.
+ # Following block of codes handles the latter way. It adds a 'Select Image' button and
+ # emit a signal when the button is clicked.
+ if self.standalone:
+ gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ())
+ icon = gtk.Image()
+ pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE)
+ icon.set_from_pixbuf(pix_buffer)
+ button = gtk.Button("Select Image")
+ button.set_image(icon)
+ #button.set_size_request(140, 50)
+ table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0)
+ button.connect("clicked", self.select_image_button_clicked_cb)
+
+ separator = gtk.HSeparator()
+ self.vbox.pack_start(separator, expand=False, fill=False, padding=10)
+
+ self.usb_desc = gtk.Label()
+ self.usb_desc.set_alignment(0.0, 0.5)
+ markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
+ self.usb_desc.set_markup(markup)
+
+ self.usb_combo = gtk.combo_box_new_text()
+ self.usb_combo.connect("changed", self.usb_combo_changed_cb)
+ model = self.usb_combo.get_model()
+ model.clear()
+ self.usb_combo.append_text(self.__dummy_usb__)
+ for usb in self.find_all_usb_devices():
+ self.usb_combo.append_text("/dev/" + usb)
+ self.usb_combo.set_active(0)
+ self.vbox.pack_start(self.usb_combo, expand=False, fill=False)
+ self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2)
+
+ self.progress_bar = HobProgressBar()
+ self.vbox.pack_start(self.progress_bar, expand=False, fill=False)
+ separator = gtk.HSeparator()
+ self.vbox.pack_start(separator, expand=False, fill=True, padding=10)
+
+ self.vbox.show_all()
+ self.progress_bar.hide()
+
+ def set_image_text_buffer(self, image_path):
+ self.buf.set_text(image_path)
+
+ def set_image_path(self, image_path):
+ self.image_path = image_path
+
+ def popen_read(self, cmd):
+ tmpout, errors = bb.process.run("%s" % cmd)
+ return tmpout.strip()
+
+ def find_all_usb_devices(self):
+ usb_devs = [ os.readlink(u)
+ for u in glob.glob('/dev/disk/by-id/usb*')
+ if not re.search(r'part\d+', u) ]
+ return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ]
+
+ def get_usb_info(self, dev):
+ return "%s %s" % \
+ (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev),
+ self.popen_read('cat /sys/class/block/%s/device/model' % dev))
+
+ def select_image_button_clicked_cb(self, button):
+ self.emit('select_image_clicked')
+
+ def usb_combo_changed_cb(self, usb_combo):
+ combo_item = self.usb_combo.get_active_text()
+ if not combo_item or combo_item == self.__dummy_usb__:
+ markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
+ self.usb_desc.set_markup(markup)
+ else:
+ markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>"
+ self.usb_desc.set_markup(markup)
+
+ def response_cb(self, dialog, response_id):
+ if response_id == gtk.RESPONSE_YES:
+ lbl = ''
+ msg = ''
+ combo_item = self.usb_combo.get_active_text()
+ if combo_item and combo_item != self.__dummy_usb__ and self.image_path:
+ cmdline = bb.ui.crumbs.utils.which_terminal()
+ if cmdline:
+ tmpfile = tempfile.NamedTemporaryFile()
+ cmdline += "\"sudo dd if=" + self.image_path + \
+ " of=" + combo_item + " && sync; echo $? > " + tmpfile.name + "\""
+ subprocess.call(shlex.split(cmdline))
+
+ if int(tmpfile.readline().strip()) == 0:
+ lbl = "<b>Deploy image successfully.</b>"
+ else:
+ lbl = "<b>Failed to deploy image.</b>"
+ msg = "Please check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item)
+ tmpfile.close()
+ else:
+ if not self.image_path:
+ lbl = "<b>No selection made.</b>"
+ msg = "You have not selected an image to deploy."
+ else:
+ lbl = "<b>No selection made.</b>"
+ msg = "You have not selected a USB device."
+ if len(lbl):
+ crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+ button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
+ HobButton.style_button(button)
+ crumbs_dialog.run()
+ crumbs_dialog.destroy()
+
+ def update_progress_bar(self, title, fraction, status=None):
+ self.progress_bar.update(fraction)
+ self.progress_bar.set_title(title)
+ self.progress_bar.set_rcstyle(status)
+
+ def write_file(self, ifile, ofile):
+ self.progress_bar.reset()
+ self.progress_bar.show()
+
+ f_from = os.open(ifile, os.O_RDONLY)
+ f_to = os.open(ofile, os.O_WRONLY)
+
+ total_size = os.stat(ifile).st_size
+ written_size = 0
+
+ while True:
+ buf = os.read(f_from, 1024*1024)
+ if not buf:
+ break
+ os.write(f_to, buf)
+ written_size += 1024*1024
+ self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
+
+ self.update_progress_bar("Writing completed:", 1.0)
+ os.close(f_from)
+ os.close(f_to)
+ self.progress_bar.hide()
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
new file mode 100644
index 000000000..21216adc9
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
@@ -0,0 +1,172 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import os
+from bb.ui.crumbs.hobwidget import HobViewTable, HobInfoButton, HobButton, HobAltButton
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class ImageSelectionDialog (CrumbsDialog):
+
+ __columns__ = [{
+ 'col_name' : 'Image name',
+ 'col_id' : 0,
+ 'col_style': 'text',
+ 'col_min' : 400,
+ 'col_max' : 400
+ }, {
+ 'col_name' : 'Select',
+ 'col_id' : 1,
+ 'col_style': 'radio toggle',
+ 'col_min' : 160,
+ 'col_max' : 160
+ }]
+
+
+ def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}):
+ super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons)
+ self.connect("response", self.response_cb)
+
+ self.image_folder = image_folder
+ self.image_types = image_types
+ self.image_list = []
+ self.image_names = []
+ self.image_extension = image_extension
+
+ # create visual elements on the dialog
+ self.create_visual_elements()
+
+ self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
+ self.fill_image_store()
+
+ def create_visual_elements(self):
+ hbox = gtk.HBox(False, 6)
+
+ self.vbox.pack_start(hbox, expand=False, fill=False)
+
+ entry = gtk.Entry()
+ entry.set_text(self.image_folder)
+ table = gtk.Table(1, 10, True)
+ table.set_size_request(560, -1)
+ hbox.pack_start(table, expand=False, fill=False)
+ table.attach(entry, 0, 9, 0, 1)
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
+ open_button = gtk.Button()
+ open_button.set_image(image)
+ open_button.connect("clicked", self.select_path_cb, self, entry)
+ table.attach(open_button, 9, 10, 0, 1)
+
+ self.image_table = HobViewTable(self.__columns__, "Images")
+ self.image_table.set_size_request(-1, 300)
+ self.image_table.connect("toggled", self.toggled_cb)
+ self.image_table.connect_group_selection(self.table_selected_cb)
+ self.image_table.connect("row-activated", self.row_actived_cb)
+ self.vbox.pack_start(self.image_table, expand=True, fill=True)
+
+ self.show_all()
+
+ def change_image_cb(self, model, path, columnid):
+ if not model:
+ return
+ iter = model.get_iter_first()
+ while iter:
+ rowpath = model.get_path(iter)
+ model[rowpath][columnid] = False
+ iter = model.iter_next(iter)
+
+ model[path][columnid] = True
+
+ def toggled_cb(self, table, cell, path, columnid, tree):
+ model = tree.get_model()
+ self.change_image_cb(model, path, columnid)
+
+ def table_selected_cb(self, selection):
+ model, paths = selection.get_selected_rows()
+ if paths:
+ self.change_image_cb(model, paths[0], 1)
+
+ def row_actived_cb(self, tab, model, path):
+ self.change_image_cb(model, path, 1)
+ self.emit('response', gtk.RESPONSE_YES)
+
+ def select_path_cb(self, action, parent, entry):
+ dialog = gtk.FileChooserDialog("", parent,
+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ text = entry.get_text()
+ dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
+ button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+ HobAltButton.style_button(button)
+ button = dialog.add_button("Open", gtk.RESPONSE_YES)
+ HobButton.style_button(button)
+ response = dialog.run()
+ if response == gtk.RESPONSE_YES:
+ path = dialog.get_filename()
+ entry.set_text(path)
+ self.image_folder = path
+ self.fill_image_store()
+
+ dialog.destroy()
+
+ def fill_image_store(self):
+ self.image_list = []
+ self.image_store.clear()
+ imageset = set()
+ for root, dirs, files in os.walk(self.image_folder):
+ # ignore the sub directories
+ dirs[:] = []
+ for f in files:
+ for image_type in self.image_types:
+ if image_type in self.image_extension:
+ real_types = self.image_extension[image_type]
+ else:
+ real_types = [image_type]
+ for real_image_type in real_types:
+ if f.endswith('.' + real_image_type):
+ imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0])
+ self.image_list.append(f)
+
+ for image in imageset:
+ self.image_store.set(self.image_store.append(), 0, image, 1, False)
+
+ self.image_table.set_model(self.image_store)
+
+ def response_cb(self, dialog, response_id):
+ self.image_names = []
+ if response_id == gtk.RESPONSE_YES:
+ iter = self.image_store.get_iter_first()
+ while iter:
+ path = self.image_store.get_path(iter)
+ if self.image_store[path][1]:
+ for f in self.image_list:
+ if f.startswith(self.image_store[path][0] + '.'):
+ self.image_names.append(f)
+ break
+ iter = self.image_store.iter_next(iter)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
new file mode 100644
index 000000000..52d57b673
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
@@ -0,0 +1,298 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import os
+import tempfile
+from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CellRendererPixbufActivatable(gtk.CellRendererPixbuf):
+ """
+ A custom CellRenderer implementation which is activatable
+ so that we can handle user clicks
+ """
+ __gsignals__ = { 'clicked' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,)), }
+
+ def __init__(self):
+ gtk.CellRendererPixbuf.__init__(self)
+ self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
+ self.set_property('follow-state', True)
+
+ """
+ Respond to a user click on a cell
+ """
+ def do_activate(self, even, widget, path, background_area, cell_area, flags):
+ self.emit('clicked', path)
+
+#
+# LayerSelectionDialog
+#
+class LayerSelectionDialog (CrumbsDialog):
+
+ TARGETS = [
+ ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0),
+ ("text/plain", 0, 1),
+ ("TEXT", 0, 2),
+ ("STRING", 0, 3),
+ ]
+
+ def gen_label_widget(self, content):
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_markup(content)
+ label.show()
+ return label
+
+ def layer_widget_toggled_cb(self, cell, path, layer_store):
+ name = layer_store[path][0]
+ toggle = not layer_store[path][1]
+ layer_store[path][1] = toggle
+
+ def layer_widget_add_clicked_cb(self, action, layer_store, parent):
+ dialog = gtk.FileChooserDialog("Add new layer", parent,
+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+ HobAltButton.style_button(button)
+ button = dialog.add_button("Open", gtk.RESPONSE_YES)
+ HobButton.style_button(button)
+ label = gtk.Label("Select the layer you wish to add")
+ label.show()
+ dialog.set_extra_widget(label)
+ response = dialog.run()
+ path = dialog.get_filename()
+ dialog.destroy()
+
+ lbl = "<b>Error</b>"
+ msg = "Unable to load layer <i>%s</i> because " % path
+ if response == gtk.RESPONSE_YES:
+ import os
+ import os.path
+ layers = []
+ it = layer_store.get_iter_first()
+ while it:
+ layers.append(layer_store.get_value(it, 0))
+ it = layer_store.iter_next(it)
+
+ if not path:
+ msg += "it is an invalid path."
+ elif not os.path.exists(path+"/conf/layer.conf"):
+ msg += "there is no layer.conf inside the directory."
+ elif path in layers:
+ msg += "it is already in loaded layers."
+ else:
+ layer_store.append([path])
+ return
+ dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg)
+ dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
+ response = dialog.run()
+ dialog.destroy()
+
+ def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store):
+ model, iter = tree_selection.get_selected()
+ if iter:
+ layer_store.remove(iter)
+
+
+ def gen_layer_widget(self, layers, layers_avail, window, tooltip=""):
+ hbox = gtk.HBox(False, 6)
+
+ layer_tv = gtk.TreeView()
+ layer_tv.set_rules_hint(True)
+ layer_tv.set_headers_visible(False)
+ tree_selection = layer_tv.get_selection()
+ tree_selection.set_mode(gtk.SELECTION_SINGLE)
+
+ # Allow enable drag and drop of rows including row move
+ dnd_internal_target = ''
+ dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)]
+ layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
+ dnd_targets,
+ gtk.gdk.ACTION_MOVE)
+ layer_tv.enable_model_drag_dest(dnd_targets,
+ gtk.gdk.ACTION_MOVE)
+ layer_tv.connect("drag_data_get", self.drag_data_get_cb)
+ layer_tv.connect("drag_data_received", self.drag_data_received_cb)
+
+ col0= gtk.TreeViewColumn('Path')
+ cell0 = gtk.CellRendererText()
+ cell0.set_padding(5,2)
+ col0.pack_start(cell0, True)
+ col0.set_cell_data_func(cell0, self.draw_layer_path_cb)
+ layer_tv.append_column(col0)
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scroll.set_shadow_type(gtk.SHADOW_IN)
+ scroll.add(layer_tv)
+
+ table_layer = gtk.Table(2, 10, False)
+ hbox.pack_start(table_layer, expand=True, fill=True)
+
+ table_layer.attach(scroll, 0, 10, 0, 1)
+
+ layer_store = gtk.ListStore(gobject.TYPE_STRING)
+ for layer in layers:
+ layer_store.append([layer])
+
+ col1 = gtk.TreeViewColumn('Enabled')
+ layer_tv.append_column(col1)
+
+ cell1 = CellRendererPixbufActivatable()
+ cell1.set_fixed_size(-1,35)
+ cell1.connect("clicked", self.del_cell_clicked_cb, layer_store)
+ col1.pack_start(cell1, True)
+ col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv)
+
+ add_button = gtk.Button()
+ add_button.set_relief(gtk.RELIEF_NONE)
+ box = gtk.HBox(False, 6)
+ box.show()
+ add_button.add(box)
+ add_button.connect("enter-notify-event", self.add_hover_cb)
+ add_button.connect("leave-notify-event", self.add_leave_cb)
+ self.im = gtk.Image()
+ self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
+ self.im.show()
+ box.pack_start(self.im, expand=False, fill=False, padding=6)
+ lbl = gtk.Label("Add layer")
+ lbl.set_alignment(0.0, 0.5)
+ lbl.show()
+ box.pack_start(lbl, expand=True, fill=True, padding=6)
+ add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window)
+ table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6)
+ layer_tv.set_model(layer_store)
+
+ hbox.show_all()
+
+ return hbox, layer_store
+
+ def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
+ treeselection = treeview.get_selection()
+ model, iter = treeselection.get_selected()
+ data = model.get_value(iter, 0)
+ selection.set(selection.target, 8, data)
+
+ def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
+ model = treeview.get_model()
+ data = selection.data
+ drop_info = treeview.get_dest_row_at_pos(x, y)
+ if drop_info:
+ path, position = drop_info
+ iter = model.get_iter(path)
+ if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
+ model.insert_before(iter, [data])
+ else:
+ model.insert_after(iter, [data])
+ else:
+ model.append([data])
+ if context.action == gtk.gdk.ACTION_MOVE:
+ context.finish(True, True, etime)
+ return
+
+ def add_hover_cb(self, button, event):
+ self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE)
+
+ def add_leave_cb(self, button, event):
+ self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
+
+ def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None):
+ super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
+
+ # class members from other objects
+ self.layers = layers
+ self.layers_non_removable = layers_non_removable
+ self.all_layers = all_layers
+ self.layers_changed = False
+
+ # icon for remove button in TreeView
+ im = gtk.Image()
+ im.set_from_file(hic.ICON_INDI_REMOVE_FILE)
+ self.rem_icon = im.get_pixbuf()
+
+ # class members for internal use
+ self.layer_store = None
+
+ # create visual elements on the dialog
+ self.create_visual_elements()
+ self.connect("response", self.response_cb)
+
+ def create_visual_elements(self):
+ layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None)
+ layer_widget.set_size_request(450, 250)
+ self.vbox.pack_start(layer_widget, expand=True, fill=True)
+ self.show_all()
+
+ def response_cb(self, dialog, response_id):
+ model = self.layer_store
+ it = model.get_iter_first()
+ layers = []
+ while it:
+ layers.append(model.get_value(it, 0))
+ it = model.iter_next(it)
+
+ self.layers_changed = (self.layers != layers)
+ self.layers = layers
+
+ """
+ A custom cell_data_func to draw a delete 'button' in the TreeView for layers
+ other than the meta layer. The deletion of which is prevented so that the
+ user can't shoot themselves in the foot too badly.
+ """
+ def draw_delete_button_cb(self, col, cell, model, it, tv):
+ path = model.get_value(it, 0)
+ if path in self.layers_non_removable:
+ cell.set_sensitive(False)
+ cell.set_property('pixbuf', None)
+ cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
+ else:
+ cell.set_property('pixbuf', self.rem_icon)
+ cell.set_sensitive(True)
+ cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
+
+ return True
+
+ """
+ A custom cell_data_func to write an extra message into the layer path cell
+ for the meta layer. We should inform the user that they can't remove it for
+ their own safety.
+ """
+ def draw_layer_path_cb(self, col, cell, model, it):
+ path = model.get_value(it, 0)
+ if path in self.layers_non_removable:
+ cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path)
+ else:
+ cell.set_property('text', path)
+
+ def del_cell_clicked_cb(self, cell, path, model):
+ it = model.get_iter_from_string(path)
+ model.remove(it)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
new file mode 100644
index 000000000..09b9ce6de
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
@@ -0,0 +1,437 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2013 Intel Corporation
+#
+# Authored by Andrei Dinu <andrei.adrianx.dinu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import string
+import gtk
+import gobject
+import os
+import tempfile
+import glib
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class PropertyDialog(CrumbsDialog):
+
+ def __init__(self, title, parent, information, flags, buttons=None):
+
+ super(PropertyDialog, self).__init__(title, parent, flags, buttons)
+
+ self.properties = information
+
+ if len(self.properties) == 10:
+ self.create_recipe_visual_elements()
+ elif len(self.properties) == 5:
+ self.create_package_visual_elements()
+ else:
+ self.create_information_visual_elements()
+
+
+ def create_information_visual_elements(self):
+
+ HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("icons/"))
+ ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
+
+ self.set_resizable(False)
+
+ self.table = gtk.Table(1,1,False)
+ self.table.set_row_spacings(0)
+ self.table.set_col_spacings(0)
+
+ self.image = gtk.Image()
+ self.image.set_from_file(ICON_PACKAGES_DISPLAY_FILE)
+ self.image.set_property("xalign",0)
+ #self.vbox.add(self.image)
+
+ image_info = self.properties.split("*")[0]
+ info = self.properties.split("*")[1]
+
+ vbox = gtk.VBox(True, spacing=30)
+
+ self.label_short = gtk.Label()
+ self.label_short.set_line_wrap(False)
+ self.label_short.set_markup(image_info)
+ self.label_short.set_property("xalign", 0)
+
+ self.info_label = gtk.Label()
+ self.info_label.set_line_wrap(True)
+ self.info_label.set_markup(info)
+ self.info_label.set_property("yalign", 0.5)
+
+ self.table.attach(self.image, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=5,ypadding=5)
+ self.table.attach(self.label_short, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=5)
+ self.table.attach(self.info_label, 0,1,1,2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=10)
+
+ self.vbox.add(self.table)
+ self.connect('delete-event', lambda w, e: self.destroy() or True)
+
+ def treeViewTooltip( self, widget, e, tooltips, cell, emptyText="" ):
+ try:
+ (path,col,x,y) = widget.get_path_at_pos( int(e.x), int(e.y) )
+ it = widget.get_model().get_iter(path)
+ value = widget.get_model().get_value(it,cell)
+ if value in self.tooltip_items:
+ tooltips.set_tip(widget, self.tooltip_items[value])
+ tooltips.enable()
+ else:
+ tooltips.set_tip(widget, emptyText)
+ except:
+ tooltips.set_tip(widget, emptyText)
+
+
+ def create_package_visual_elements(self):
+
+ import json
+
+ name = self.properties['name']
+ binb = self.properties['binb']
+ size = self.properties['size']
+ recipe = self.properties['recipe']
+ file_list = json.loads(self.properties['files_list'])
+
+ files_temp = ''
+ paths_temp = ''
+ files_binb = []
+ paths_binb = []
+
+ self.tooltip_items = {}
+
+ self.set_resizable(False)
+
+ #cleaning out the recipe variable
+ recipe = recipe.split("+")[0]
+
+ vbox = gtk.VBox(True,spacing = 0)
+
+ ###################################### NAME ROW + COL #################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_size_request(300,-1)
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ###################################### SIZE ROW + COL ######################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_size_request(300,-1)
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Size: </span>" + size)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ##################################### RECIPE ROW + COL #########################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_size_request(300,-1)
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Recipe: </span>" + recipe)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ##################################### BINB ROW + COL #######################################
+
+ if binb != '':
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
+ self.label_short.set_property("xalign", 0)
+
+ self.label_info = gtk.Label()
+ self.label_info.set_size_request(300,-1)
+ self.label_info.set_selectable(True)
+ self.label_info.set_line_wrap(True)
+ self.label_info.set_markup(binb)
+ self.label_info.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+ self.vbox.add(self.label_info)
+
+ #################################### FILES BROUGHT BY PACKAGES ###################################
+
+ if file_list:
+
+ self.textWindow = gtk.ScrolledWindow()
+ self.textWindow.set_shadow_type(gtk.SHADOW_IN)
+ self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.textWindow.set_size_request(100, 170)
+
+ packagefiles_store = gtk.ListStore(str)
+
+ self.packagefiles_tv = gtk.TreeView()
+ self.packagefiles_tv.set_rules_hint(True)
+ self.packagefiles_tv.set_headers_visible(True)
+ self.textWindow.add(self.packagefiles_tv)
+
+ self.cell1 = gtk.CellRendererText()
+ col1 = gtk.TreeViewColumn('Package files', self.cell1)
+ col1.set_cell_data_func(self.cell1, self.regex_field)
+ self.packagefiles_tv.append_column(col1)
+
+ items = file_list.keys()
+ items.sort()
+ for item in items:
+ fullpath = item
+ while len(item) > 35:
+ item = item[:len(item)/2] + "" + item[len(item)/2+1:]
+ if len(item) == 35:
+ item = item[:len(item)/2] + "..." + item[len(item)/2+3:]
+ self.tooltip_items[item] = fullpath
+
+ packagefiles_store.append([str(item)])
+
+ self.packagefiles_tv.set_model(packagefiles_store)
+
+ tips = gtk.Tooltips()
+ tips.set_tip(self.packagefiles_tv, "")
+ self.packagefiles_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0)
+ self.packagefiles_tv.set_events(gtk.gdk.POINTER_MOTION_MASK)
+
+ self.vbox.add(self.textWindow)
+
+ self.vbox.show_all()
+
+
+ def regex_field(self, column, cell, model, iter):
+ cell.set_property('text', model.get_value(iter, 0))
+ return
+
+
+ def create_recipe_visual_elements(self):
+
+ summary = self.properties['summary']
+ name = self.properties['name']
+ version = self.properties['version']
+ revision = self.properties['revision']
+ binb = self.properties['binb']
+ group = self.properties['group']
+ license = self.properties['license']
+ homepage = self.properties['homepage']
+ bugtracker = self.properties['bugtracker']
+ description = self.properties['description']
+
+ self.set_resizable(False)
+
+ #cleaning out the version variable and also the summary
+ version = version.split(":")[1]
+ if len(version) > 30:
+ version = version.split("+")[0]
+ else:
+ version = version.split("-")[0]
+ license = license.replace("&" , "and")
+ if (homepage == ''):
+ homepage = 'unknown'
+ if (bugtracker == ''):
+ bugtracker = 'unknown'
+ summary = summary.split("+")[0]
+
+ #calculating the rows needed for the table
+ binb_items_count = len(binb.split(','))
+ binb_items = binb.split(',')
+
+ vbox = gtk.VBox(False,spacing = 0)
+
+ ######################################## SUMMARY LABEL #########################################
+
+ if summary != '':
+ self.label_short = gtk.Label()
+ self.label_short.set_width_chars(37)
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<b>" + summary + "</b>")
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ########################################## NAME ROW + COL #######################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ####################################### VERSION ROW + COL ####################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Version: </span>" + version)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ##################################### REVISION ROW + COL #####################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_selectable(True)
+ self.label_short.set_markup("<span weight=\"bold\">Revision: </span>" + revision)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ################################## GROUP ROW + COL ############################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Group: </span>" + group)
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+
+ ################################# HOMEPAGE ROW + COL ############################################
+
+ if homepage != 'unknown':
+ self.label_info = gtk.Label()
+ self.label_info.set_selectable(True)
+ self.label_info.set_line_wrap(True)
+ if len(homepage) > 35:
+ self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:35] + "..." + "</a>")
+ else:
+ self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:60] + "</a>")
+
+ self.label_info.set_property("xalign", 0)
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<b>Homepage: </b>")
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+ self.vbox.add(self.label_info)
+
+ ################################# BUGTRACKER ROW + COL ###########################################
+
+ if bugtracker != 'unknown':
+ self.label_info = gtk.Label()
+ self.label_info.set_selectable(True)
+ self.label_info.set_line_wrap(True)
+ if len(bugtracker) > 35:
+ self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:35] + "..." + "</a>")
+ else:
+ self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:60] + "</a>")
+ self.label_info.set_property("xalign", 0)
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<b>Bugtracker: </b>")
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+ self.vbox.add(self.label_info)
+
+ ################################# LICENSE ROW + COL ############################################
+
+ self.label_info = gtk.Label()
+ self.label_info.set_selectable(True)
+ self.label_info.set_line_wrap(True)
+ self.label_info.set_markup(license)
+ self.label_info.set_property("xalign", 0)
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">License: </span>")
+ self.label_short.set_property("xalign", 0)
+
+ self.vbox.add(self.label_short)
+ self.vbox.add(self.label_info)
+
+ ################################### BINB ROW+COL #############################################
+
+ if binb != '':
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
+ self.label_short.set_property("xalign", 0)
+ self.vbox.add(self.label_short)
+ self.label_info = gtk.Label()
+ self.label_info.set_selectable(True)
+ self.label_info.set_width_chars(36)
+ if len(binb) > 200:
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
+ scrolled_window.set_size_request(100,100)
+ self.label_info.set_markup(binb)
+ self.label_info.set_padding(6,6)
+ self.label_info.set_alignment(0,0)
+ self.label_info.set_line_wrap(True)
+ scrolled_window.add_with_viewport(self.label_info)
+ self.vbox.add(scrolled_window)
+ else:
+ self.label_info.set_markup(binb)
+ self.label_info.set_property("xalign", 0)
+ self.label_info.set_line_wrap(True)
+ self.vbox.add(self.label_info)
+
+ ################################ DESCRIPTION TAG ROW #################################################
+
+ self.label_short = gtk.Label()
+ self.label_short.set_line_wrap(True)
+ self.label_short.set_markup("<span weight=\"bold\">Description </span>")
+ self.label_short.set_property("xalign", 0)
+ self.vbox.add(self.label_short)
+
+ ################################ DESCRIPTION INFORMATION ROW ##########################################
+
+ hbox = gtk.HBox(True,spacing = 0)
+
+ self.label_short = gtk.Label()
+ self.label_short.set_selectable(True)
+ self.label_short.set_width_chars(36)
+ if len(description) > 200:
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
+ scrolled_window.set_size_request(100,100)
+ self.label_short.set_markup(description)
+ self.label_short.set_padding(6,6)
+ self.label_short.set_alignment(0,0)
+ self.label_short.set_line_wrap(True)
+ scrolled_window.add_with_viewport(self.label_short)
+ self.vbox.add(scrolled_window)
+ else:
+ self.label_short.set_markup(description)
+ self.label_short.set_property("xalign", 0)
+ self.label_short.set_line_wrap(True)
+ self.vbox.add(self.label_short)
+
+ self.vbox.show_all()
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
new file mode 100644
index 000000000..e0285c93c
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
@@ -0,0 +1,122 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import os
+from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton, HobAltButton
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class SettingsUIHelper():
+
+ def gen_label_widget(self, content):
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_markup(content)
+ label.show()
+ return label
+
+ def gen_label_info_widget(self, content, tooltip):
+ table = gtk.Table(1, 10, False)
+ label = self.gen_label_widget(content)
+ info = HobInfoButton(tooltip, self)
+ table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL)
+ table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10)
+ return table
+
+ def gen_spinner_widget(self, content, lower, upper, tooltip=""):
+ hbox = gtk.HBox(False, 12)
+ adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1)
+ spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0)
+
+ spinner.set_value(content)
+ hbox.pack_start(spinner, expand=False, fill=False)
+
+ info = HobInfoButton(tooltip, self)
+ hbox.pack_start(info, expand=False, fill=False)
+
+ hbox.show_all()
+ return hbox, spinner
+
+ def gen_combo_widget(self, curr_item, all_item, tooltip=""):
+ hbox = gtk.HBox(False, 12)
+ combo = gtk.combo_box_new_text()
+ hbox.pack_start(combo, expand=False, fill=False)
+
+ index = 0
+ for item in all_item or []:
+ combo.append_text(item)
+ if item == curr_item:
+ combo.set_active(index)
+ index += 1
+
+ info = HobInfoButton(tooltip, self)
+ hbox.pack_start(info, expand=False, fill=False)
+
+ hbox.show_all()
+ return hbox, combo
+
+ def entry_widget_select_path_cb(self, action, parent, entry):
+ dialog = gtk.FileChooserDialog("", parent,
+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ text = entry.get_text()
+ dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
+ button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+ HobAltButton.style_button(button)
+ button = dialog.add_button("Open", gtk.RESPONSE_YES)
+ HobButton.style_button(button)
+ response = dialog.run()
+ if response == gtk.RESPONSE_YES:
+ path = dialog.get_filename()
+ entry.set_text(path)
+
+ dialog.destroy()
+
+ def gen_entry_widget(self, content, parent, tooltip="", need_button=True):
+ hbox = gtk.HBox(False, 12)
+ entry = gtk.Entry()
+ entry.set_text(content)
+ entry.set_size_request(350,30)
+
+ if need_button:
+ table = gtk.Table(1, 10, False)
+ hbox.pack_start(table, expand=True, fill=True)
+ table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK)
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
+ open_button = gtk.Button()
+ open_button.set_image(image)
+ open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry)
+ table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK)
+ else:
+ hbox.pack_start(entry, expand=True, fill=True)
+
+ if tooltip != "":
+ info = HobInfoButton(tooltip, self)
+ hbox.pack_start(info, expand=False, fill=False)
+
+ hbox.show_all()
+ return hbox, entry
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py
new file mode 100644
index 000000000..3316542a2
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py
@@ -0,0 +1,38 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012 Intel Corporation
+#
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+class HobColors:
+ WHITE = "#ffffff"
+ PALE_GREEN = "#aaffaa"
+ ORANGE = "#eb8e68"
+ PALE_RED = "#ffaaaa"
+ GRAY = "#aaaaaa"
+ LIGHT_GRAY = "#dddddd"
+ SLIGHT_DARK = "#5f5f5f"
+ DARK = "#3c3b37"
+ BLACK = "#000000"
+ PALE_BLUE = "#53b8ff"
+ DEEP_RED = "#aa3e3e"
+ KHAKI = "#fff68f"
+
+ OK = WHITE
+ RUNNING = PALE_GREEN
+ WARNING = ORANGE
+ ERROR = PALE_RED
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py
new file mode 100644
index 000000000..2b969c146
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py
@@ -0,0 +1,904 @@
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012 Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import gtk
+import gobject
+import os
+import os.path
+import sys
+import pango, pangocairo
+import cairo
+import math
+
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.persistenttooltip import PersistentTooltip
+
+class hwc:
+
+ MAIN_WIN_WIDTH = 1024
+ MAIN_WIN_HEIGHT = 700
+
+class hic:
+
+ HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/"))
+
+ ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png'))
+ ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png'))
+ ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png'))
+ ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png'))
+ ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png'))
+ ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png'))
+ ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png'))
+ ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png'))
+ ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png'))
+ ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png'))
+ ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
+ ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png'))
+ ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png'))
+ ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/denied.png'))
+ ICON_INDI_REMOVE_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove.png'))
+ ICON_INDI_REMOVE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove-hover.png'))
+ ICON_INDI_ADD_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add.png'))
+ ICON_INDI_ADD_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add-hover.png'))
+ ICON_INDI_REFRESH_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/refresh.png'))
+ ICON_INDI_ALERT_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/alert.png'))
+ ICON_INDI_TICK_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/tick.png'))
+ ICON_INDI_INFO_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/info.png'))
+
+class HobViewTable (gtk.VBox):
+ """
+ A VBox to contain the table for different recipe views and package view
+ """
+ __gsignals__ = {
+ "toggled" : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,
+ gobject.TYPE_STRING,
+ gobject.TYPE_INT,
+ gobject.TYPE_PYOBJECT,)),
+ "row-activated" : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT,)),
+ "cell-fadeinout-stopped" : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT,)),
+ }
+
+ def __init__(self, columns, name):
+ gtk.VBox.__init__(self, False, 6)
+ self.table_tree = gtk.TreeView()
+ self.table_tree.set_headers_visible(True)
+ self.table_tree.set_headers_clickable(True)
+ self.table_tree.set_rules_hint(True)
+ self.table_tree.set_enable_tree_lines(True)
+ self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
+ self.toggle_columns = []
+ self.table_tree.connect("row-activated", self.row_activated_cb)
+ self.top_bar = None
+ self.tab_name = name
+
+ for i, column in enumerate(columns):
+ col_name = column['col_name']
+ col = gtk.TreeViewColumn(col_name)
+ col.set_clickable(True)
+ col.set_resizable(True)
+ if self.tab_name.startswith('Included'):
+ if col_name!='Included':
+ col.set_sort_column_id(column['col_id'])
+ else:
+ col.set_sort_column_id(column['col_id'])
+ if 'col_min' in column.keys():
+ col.set_min_width(column['col_min'])
+ if 'col_max' in column.keys():
+ col.set_max_width(column['col_max'])
+ if 'expand' in column.keys():
+ col.set_expand(True)
+ self.table_tree.append_column(col)
+
+ if (not 'col_style' in column.keys()) or column['col_style'] == 'text':
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, True)
+ col.set_attributes(cell, text=column['col_id'])
+ if 'col_t_id' in column.keys():
+ col.add_attribute(cell, 'font', column['col_t_id'])
+ elif column['col_style'] == 'check toggle':
+ cell = HobCellRendererToggle()
+ cell.set_property('activatable', True)
+ cell.connect("toggled", self.toggled_cb, i, self.table_tree)
+ cell.connect_render_state_changed(self.stop_cell_fadeinout_cb, self.table_tree)
+ self.toggle_id = i
+ col.pack_end(cell, True)
+ col.set_attributes(cell, active=column['col_id'])
+ self.toggle_columns.append(col_name)
+ if 'col_group' in column.keys():
+ col.set_cell_data_func(cell, self.set_group_number_cb)
+ elif column['col_style'] == 'radio toggle':
+ cell = gtk.CellRendererToggle()
+ cell.set_property('activatable', True)
+ cell.set_radio(True)
+ cell.connect("toggled", self.toggled_cb, i, self.table_tree)
+ self.toggle_id = i
+ col.pack_end(cell, True)
+ col.set_attributes(cell, active=column['col_id'])
+ self.toggle_columns.append(col_name)
+ elif column['col_style'] == 'binb':
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, True)
+ col.set_cell_data_func(cell, self.display_binb_cb, column['col_id'])
+ if 'col_t_id' in column.keys():
+ col.add_attribute(cell, 'font', column['col_t_id'])
+
+ self.scroll = gtk.ScrolledWindow()
+ self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.scroll.add(self.table_tree)
+
+ self.pack_end(self.scroll, True, True, 0)
+
+ def add_no_result_bar(self, entry):
+ color = HobColors.KHAKI
+ self.top_bar = gtk.EventBox()
+ self.top_bar.set_size_request(-1, 70)
+ self.top_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+ self.top_bar.set_flags(gtk.CAN_DEFAULT)
+ self.top_bar.grab_default()
+
+ no_result_tab = gtk.Table(5, 20, True)
+ self.top_bar.add(no_result_tab)
+
+ label = gtk.Label()
+ label.set_alignment(0.0, 0.5)
+ title = "No results matching your search"
+ label.set_markup("<span size='x-large'><b>%s</b></span>" % title)
+ no_result_tab.attach(label, 1, 14, 1, 4)
+
+ clear_button = HobButton("Clear search")
+ clear_button.set_tooltip_text("Clear search query")
+ clear_button.connect('clicked', self.set_search_entry_clear_cb, entry)
+ no_result_tab.attach(clear_button, 16, 19, 1, 4)
+
+ self.pack_start(self.top_bar, False, True, 12)
+ self.top_bar.show_all()
+
+ def set_search_entry_clear_cb(self, button, search):
+ if search.get_editable() == True:
+ search.set_text("")
+ search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+ search.grab_focus()
+
+ def display_binb_cb(self, col, cell, model, it, col_id):
+ binb = model.get_value(it, col_id)
+ # Just display the first item
+ if binb:
+ bin = binb.split(', ')
+ total_no = len(bin)
+ if total_no > 1 and bin[0] == "User Selected":
+ if total_no > 2:
+ present_binb = bin[1] + ' (+' + str(total_no - 1) + ')'
+ else:
+ present_binb = bin[1]
+ else:
+ if total_no > 1:
+ present_binb = bin[0] + ' (+' + str(total_no - 1) + ')'
+ else:
+ present_binb = bin[0]
+ cell.set_property('text', present_binb)
+ else:
+ cell.set_property('text', "")
+ return True
+
+ def set_model(self, tree_model):
+ self.table_tree.set_model(tree_model)
+
+ def toggle_default(self):
+ model = self.table_tree.get_model()
+ if not model:
+ return
+ iter = model.get_iter_first()
+ if iter:
+ rowpath = model.get_path(iter)
+ model[rowpath][self.toggle_id] = True
+
+ def toggled_cb(self, cell, path, columnid, tree):
+ self.emit("toggled", cell, path, columnid, tree)
+
+ def row_activated_cb(self, tree, path, view_column):
+ if not view_column.get_title() in self.toggle_columns:
+ self.emit("row-activated", tree.get_model(), path)
+
+ def stop_cell_fadeinout_cb(self, ctrl, cell, tree):
+ self.emit("cell-fadeinout-stopped", ctrl, cell, tree)
+
+ def set_group_number_cb(self, col, cell, model, iter):
+ if model and (model.iter_parent(iter) == None):
+ cell.cell_attr["number_of_children"] = model.iter_n_children(iter)
+ else:
+ cell.cell_attr["number_of_children"] = 0
+
+ def connect_group_selection(self, cb_func):
+ self.table_tree.get_selection().connect("changed", cb_func)
+
+"""
+A method to calculate a softened value for the colour of widget when in the
+provided state.
+
+widget: the widget whose style to use
+state: the state of the widget to use the style for
+
+Returns a string value representing the softened colour
+"""
+def soften_color(widget, state=gtk.STATE_NORMAL):
+ # this colour munging routine is heavily inspired bu gdu_util_get_mix_color()
+ # from gnome-disk-utility:
+ # http://git.gnome.org/browse/gnome-disk-utility/tree/src/gdu-gtk/gdu-gtk.c?h=gnome-3-0
+ blend = 0.7
+ style = widget.get_style()
+ color = style.text[state]
+ color.red = color.red * blend + style.base[state].red * (1.0 - blend)
+ color.green = color.green * blend + style.base[state].green * (1.0 - blend)
+ color.blue = color.blue * blend + style.base[state].blue * (1.0 - blend)
+ return color.to_string()
+
+class BaseHobButton(gtk.Button):
+ """
+ A gtk.Button subclass which follows the visual design of Hob for primary
+ action buttons
+
+ label: the text to display as the button's label
+ """
+ def __init__(self, label):
+ gtk.Button.__init__(self, label)
+ HobButton.style_button(self)
+
+ @staticmethod
+ def style_button(button):
+ style = button.get_style()
+ style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), 'gtk-button', 'gtk-button', gobject.TYPE_NONE)
+
+ button.set_flags(gtk.CAN_DEFAULT)
+ button.grab_default()
+
+# label = "<span size='x-large'><b>%s</b></span>" % gobject.markup_escape_text(button.get_label())
+ label = button.get_label()
+ button.set_label(label)
+ button.child.set_use_markup(True)
+
+class HobButton(BaseHobButton):
+ """
+ A gtk.Button subclass which follows the visual design of Hob for primary
+ action buttons
+
+ label: the text to display as the button's label
+ """
+ def __init__(self, label):
+ BaseHobButton.__init__(self, label)
+ HobButton.style_button(self)
+
+class HobAltButton(BaseHobButton):
+ """
+ A gtk.Button subclass which has no relief, and so is more discrete
+ """
+ def __init__(self, label):
+ BaseHobButton.__init__(self, label)
+ HobAltButton.style_button(self)
+
+ """
+ A callback for the state-changed event to ensure the text is displayed
+ differently when the widget is not sensitive
+ """
+ @staticmethod
+ def desensitise_on_state_change_cb(button, state):
+ if not button.get_property("sensitive"):
+ HobAltButton.set_text(button, False)
+ else:
+ HobAltButton.set_text(button, True)
+
+ """
+ Set the button label with an appropriate colour for the current widget state
+ """
+ @staticmethod
+ def set_text(button, sensitive=True):
+ if sensitive:
+ colour = HobColors.PALE_BLUE
+ else:
+ colour = HobColors.LIGHT_GRAY
+ button.set_label("<span size='large' color='%s'><b>%s</b></span>" % (colour, gobject.markup_escape_text(button.text)))
+ button.child.set_use_markup(True)
+
+class HobImageButton(gtk.Button):
+ """
+ A gtk.Button with an icon and two rows of text, the second of which is
+ displayed in a blended colour.
+
+ primary_text: the main button label
+ secondary_text: optional second line of text
+ icon_path: path to the icon file to display on the button
+ """
+ def __init__(self, primary_text, secondary_text="", icon_path="", hover_icon_path=""):
+ gtk.Button.__init__(self)
+ self.set_relief(gtk.RELIEF_NONE)
+
+ self.icon_path = icon_path
+ self.hover_icon_path = hover_icon_path
+
+ hbox = gtk.HBox(False, 10)
+ hbox.show()
+ self.add(hbox)
+ self.icon = gtk.Image()
+ self.icon.set_from_file(self.icon_path)
+ self.icon.set_alignment(0.5, 0.0)
+ self.icon.show()
+ if self.hover_icon_path and len(self.hover_icon_path):
+ self.connect("enter-notify-event", self.set_hover_icon_cb)
+ self.connect("leave-notify-event", self.set_icon_cb)
+ hbox.pack_start(self.icon, False, False, 0)
+ label = gtk.Label()
+ label.set_alignment(0.0, 0.5)
+ colour = soften_color(label)
+ mark = "<span size='x-large'>%s</span>\n<span size='medium' fgcolor='%s' weight='ultralight'>%s</span>" % (primary_text, colour, secondary_text)
+ label.set_markup(mark)
+ label.show()
+ hbox.pack_start(label, True, True, 0)
+
+ def set_hover_icon_cb(self, widget, event):
+ self.icon.set_from_file(self.hover_icon_path)
+
+ def set_icon_cb(self, widget, event):
+ self.icon.set_from_file(self.icon_path)
+
+class HobInfoButton(gtk.EventBox):
+ """
+ This class implements a button-like widget per the Hob visual and UX designs
+ which will display a persistent tooltip, with the contents of tip_markup, when
+ clicked.
+
+ tip_markup: the Pango Markup to be displayed in the persistent tooltip
+ """
+ def __init__(self, tip_markup, parent=None):
+ gtk.EventBox.__init__(self)
+ self.image = gtk.Image()
+ self.image.set_from_file(
+ hic.ICON_INFO_DISPLAY_FILE)
+ self.image.show()
+ self.add(self.image)
+ self.tip_markup = tip_markup
+ self.my_parent = parent
+
+ self.set_events(gtk.gdk.BUTTON_RELEASE |
+ gtk.gdk.ENTER_NOTIFY_MASK |
+ gtk.gdk.LEAVE_NOTIFY_MASK)
+
+ self.connect("button-release-event", self.button_release_cb)
+ self.connect("enter-notify-event", self.mouse_in_cb)
+ self.connect("leave-notify-event", self.mouse_out_cb)
+
+ """
+ When the mouse click is released emulate a button-click and show the associated
+ PersistentTooltip
+ """
+ def button_release_cb(self, widget, event):
+ from bb.ui.crumbs.hig.propertydialog import PropertyDialog
+ self.dialog = PropertyDialog(title = '',
+ parent = self.my_parent,
+ information = self.tip_markup,
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT
+ | gtk.DIALOG_NO_SEPARATOR)
+
+ button = self.dialog.add_button("Close", gtk.RESPONSE_CANCEL)
+ HobAltButton.style_button(button)
+ button.connect("clicked", lambda w: self.dialog.destroy())
+ self.dialog.show_all()
+ self.dialog.run()
+
+ """
+ Change to the prelight image when the mouse enters the widget
+ """
+ def mouse_in_cb(self, widget, event):
+ self.image.set_from_file(hic.ICON_INFO_HOVER_FILE)
+
+ """
+ Change to the stock image when the mouse enters the widget
+ """
+ def mouse_out_cb(self, widget, event):
+ self.image.set_from_file(hic.ICON_INFO_DISPLAY_FILE)
+
+class HobIndicator(gtk.DrawingArea):
+ def __init__(self, count):
+ gtk.DrawingArea.__init__(self)
+ # Set no window for transparent background
+ self.set_has_window(False)
+ self.set_size_request(38,38)
+ # We need to pass through button clicks
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
+
+ self.connect('expose-event', self.expose)
+
+ self.count = count
+ self.color = HobColors.GRAY
+
+ def expose(self, widget, event):
+ if self.count and self.count > 0:
+ ctx = widget.window.cairo_create()
+
+ x, y, w, h = self.allocation
+
+ ctx.set_operator(cairo.OPERATOR_OVER)
+ ctx.set_source_color(gtk.gdk.color_parse(self.color))
+ ctx.translate(w/2, h/2)
+ ctx.arc(x, y, min(w,h)/2 - 2, 0, 2*math.pi)
+ ctx.fill_preserve()
+
+ layout = self.create_pango_layout(str(self.count))
+ textw, texth = layout.get_pixel_size()
+ x = (w/2)-(textw/2) + x
+ y = (h/2) - (texth/2) + y
+ ctx.move_to(x, y)
+ self.window.draw_layout(self.style.light_gc[gtk.STATE_NORMAL], int(x), int(y), layout)
+
+ def set_count(self, count):
+ self.count = count
+
+ def set_active(self, active):
+ if active:
+ self.color = HobColors.DEEP_RED
+ else:
+ self.color = HobColors.GRAY
+
+class HobTabLabel(gtk.HBox):
+ def __init__(self, text, count=0):
+ gtk.HBox.__init__(self, False, 0)
+ self.indicator = HobIndicator(count)
+ self.indicator.show()
+ self.pack_end(self.indicator, False, False)
+ self.lbl = gtk.Label(text)
+ self.lbl.set_alignment(0.0, 0.5)
+ self.lbl.show()
+ self.pack_end(self.lbl, True, True, 6)
+
+ def set_count(self, count):
+ self.indicator.set_count(count)
+
+ def set_active(self, active=True):
+ self.indicator.set_active(active)
+
+class HobNotebook(gtk.Notebook):
+ def __init__(self):
+ gtk.Notebook.__init__(self)
+ self.set_property('homogeneous', True)
+
+ self.pages = []
+
+ self.search = None
+ self.search_focus = False
+ self.page_changed = False
+
+ self.connect("switch-page", self.page_changed_cb)
+
+ self.show_all()
+
+ def page_changed_cb(self, nb, page, page_num):
+ for p, lbl in enumerate(self.pages):
+ if p == page_num:
+ lbl.set_active()
+ else:
+ lbl.set_active(False)
+
+ if self.search:
+ self.page_changed = True
+ self.reset_entry(self.search, page_num)
+
+ def append_page(self, child, tab_label, tab_tooltip=None):
+ label = HobTabLabel(tab_label)
+ if tab_tooltip:
+ label.set_tooltip_text(tab_tooltip)
+ label.set_active(False)
+ self.pages.append(label)
+ gtk.Notebook.append_page(self, child, label)
+
+ def set_entry(self, names, tips):
+ self.search = gtk.Entry()
+ self.search_names = names
+ self.search_tips = tips
+ style = self.search.get_style()
+ style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
+ self.search.set_style(style)
+ self.search.set_text(names[0])
+ self.search.set_tooltip_text(self.search_tips[0])
+ self.search.props.has_tooltip = True
+
+ self.search.set_editable(False)
+ self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR)
+ self.search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+ self.search.connect("icon-release", self.set_search_entry_clear_cb)
+ self.search.set_width_chars(30)
+ self.search.show()
+
+ self.search.connect("focus-in-event", self.set_search_entry_editable_cb)
+ self.search.connect("focus-out-event", self.set_search_entry_reset_cb)
+ self.set_action_widget(self.search, gtk.PACK_END)
+
+ def show_indicator_icon(self, title, number):
+ for child in self.pages:
+ if child.lbl.get_label() == title:
+ child.set_count(number)
+
+ def hide_indicator_icon(self, title):
+ for child in self.pages:
+ if child.lbl.get_label() == title:
+ child.set_count(0)
+
+ def set_search_entry_editable_cb(self, search, event):
+ self.search_focus = True
+ search.set_editable(True)
+ text = search.get_text()
+ if text in self.search_names:
+ search.set_text("")
+ style = self.search.get_style()
+ style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.BLACK, False, False)
+ search.set_style(style)
+
+ def set_search_entry_reset_cb(self, search, event):
+ page_num = self.get_current_page()
+ text = search.get_text()
+ if not text:
+ self.reset_entry(search, page_num)
+
+ def reset_entry(self, entry, page_num):
+ style = entry.get_style()
+ style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
+ entry.set_style(style)
+ entry.set_text(self.search_names[page_num])
+ entry.set_tooltip_text(self.search_tips[page_num])
+ entry.set_editable(False)
+ entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+
+ def set_search_entry_clear_cb(self, search, icon_pos, event):
+ if search.get_editable() == True:
+ search.set_text("")
+ search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+ search.grab_focus()
+
+ def set_page(self, title):
+ for child in self.pages:
+ if child.lbl.get_label() == title:
+ child.grab_focus()
+ self.set_current_page(self.pages.index(child))
+ return
+
+class HobWarpCellRendererText(gtk.CellRendererText):
+ def __init__(self, col_number):
+ gtk.CellRendererText.__init__(self)
+ self.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
+ self.set_property("wrap-width", 300) # default value wrap width is 300
+ self.col_n = col_number
+
+ def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
+ if widget:
+ self.props.wrap_width = self.get_resized_wrap_width(widget, widget.get_column(self.col_n))
+ return gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
+
+ def get_resized_wrap_width(self, treeview, column):
+ otherCols = []
+ for col in treeview.get_columns():
+ if col != column:
+ otherCols.append(col)
+ adjwidth = treeview.allocation.width - sum(c.get_width() for c in otherCols)
+ adjwidth -= treeview.style_get_property("horizontal-separator") * 4
+ if self.props.wrap_width == adjwidth or adjwidth <= 0:
+ adjwidth = self.props.wrap_width
+ return adjwidth
+
+gobject.type_register(HobWarpCellRendererText)
+
+class HobIconChecker(hic):
+ def set_hob_icon_to_stock_icon(self, file_path, stock_id=""):
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(file_path)
+ except Exception, e:
+ return None
+
+ if stock_id and (gtk.icon_factory_lookup_default(stock_id) == None):
+ icon_factory = gtk.IconFactory()
+ icon_factory.add_default()
+ icon_factory.add(stock_id, gtk.IconSet(pixbuf))
+ gtk.stock_add([(stock_id, '_label', 0, 0, '')])
+
+ return icon_factory.lookup(stock_id)
+
+ return None
+
+ """
+ For make hob icon consistently by request, and avoid icon view diff by system or gtk version, we use some 'hob icon' to replace the 'gtk icon'.
+ this function check the stock_id and make hob_id to replaced the gtk_id then return it or ""
+ """
+ def check_stock_icon(self, stock_name=""):
+ HOB_CHECK_STOCK_NAME = {
+ ('hic-dialog-info', 'gtk-dialog-info', 'dialog-info') : self.ICON_INDI_INFO_FILE,
+ ('hic-ok', 'gtk-ok', 'ok') : self.ICON_INDI_TICK_FILE,
+ ('hic-dialog-error', 'gtk-dialog-error', 'dialog-error') : self.ICON_INDI_ERROR_FILE,
+ ('hic-dialog-warning', 'gtk-dialog-warning', 'dialog-warning') : self.ICON_INDI_ALERT_FILE,
+ ('hic-task-refresh', 'gtk-execute', 'execute') : self.ICON_INDI_REFRESH_FILE,
+ }
+ valid_stock_id = stock_name
+ if stock_name:
+ for names, path in HOB_CHECK_STOCK_NAME.iteritems():
+ if stock_name in names:
+ valid_stock_id = names[0]
+ if not gtk.icon_factory_lookup_default(valid_stock_id):
+ self.set_hob_icon_to_stock_icon(path, valid_stock_id)
+
+ return valid_stock_id
+
+class HobCellRendererController(gobject.GObject):
+ (MODE_CYCLE_RUNNING, MODE_ONE_SHORT) = range(2)
+ __gsignals__ = {
+ "run-timer-stopped" : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ }
+ def __init__(self, runningmode=MODE_CYCLE_RUNNING, is_draw_row=False):
+ gobject.GObject.__init__(self)
+ self.timeout_id = None
+ self.current_angle_pos = 0.0
+ self.step_angle = 0.0
+ self.tree_headers_height = 0
+ self.running_cell_areas = []
+ self.running_mode = runningmode
+ self.is_queue_draw_row_area = is_draw_row
+ self.force_stop_enable = False
+
+ def is_active(self):
+ if self.timeout_id:
+ return True
+ else:
+ return False
+
+ def reset_run(self):
+ self.force_stop()
+ self.running_cell_areas = []
+ self.current_angle_pos = 0.0
+ self.step_angle = 0.0
+
+ ''' time_iterval: (1~1000)ms, which will be as the basic interval count for timer
+ init_usrdata: the current data which related the progress-bar will be at
+ min_usrdata: the range of min of user data
+ max_usrdata: the range of max of user data
+ step: each step which you want to progress
+ Note: the init_usrdata should in the range of from min to max, and max should > min
+ step should < (max - min)
+ '''
+ def start_run(self, time_iterval, init_usrdata, min_usrdata, max_usrdata, step, tree):
+ if (not time_iterval) or (not max_usrdata):
+ return
+ usr_range = (max_usrdata - min_usrdata) * 1.0
+ self.current_angle_pos = (init_usrdata * 1.0) / usr_range
+ self.step_angle = (step * 1) / usr_range
+ self.timeout_id = gobject.timeout_add(int(time_iterval),
+ self.make_image_on_progressing_cb, tree)
+ self.tree_headers_height = self.get_treeview_headers_height(tree)
+ self.force_stop_enable = False
+
+ def force_stop(self):
+ self.emit("run-timer-stopped")
+ self.force_stop_enable = True
+ if self.timeout_id:
+ if gobject.source_remove(self.timeout_id):
+ self.timeout_id = None
+
+ def on_draw_pixbuf_cb(self, pixbuf, cr, x, y, img_width, img_height, do_refresh=True):
+ if pixbuf:
+ r = max(img_width/2, img_height/2)
+ cr.translate(x + r, y + r)
+ if do_refresh:
+ cr.rotate(2 * math.pi * self.current_angle_pos)
+
+ cr.set_source_pixbuf(pixbuf, -img_width/2, -img_height/2)
+ cr.paint()
+
+ def on_draw_fadeinout_cb(self, cr, color, x, y, width, height, do_fadeout=True):
+ if do_fadeout:
+ alpha = self.current_angle_pos * 0.8
+ else:
+ alpha = (1.0 - self.current_angle_pos) * 0.8
+
+ cr.set_source_rgba(color.red, color.green, color.blue, alpha)
+ cr.rectangle(x, y, width, height)
+ cr.fill()
+
+ def get_treeview_headers_height(self, tree):
+ if tree and (tree.get_property("headers-visible") == True):
+ height = tree.get_allocation().height - tree.get_bin_window().get_size()[1]
+ return height
+
+ return 0
+
+ def make_image_on_progressing_cb(self, tree):
+ self.current_angle_pos += self.step_angle
+ if self.running_mode == self.MODE_CYCLE_RUNNING:
+ if (self.current_angle_pos >= 1):
+ self.current_angle_pos = 0
+ else:
+ if self.current_angle_pos > 1:
+ self.force_stop()
+ return False
+
+ if self.is_queue_draw_row_area:
+ for path in self.running_cell_areas:
+ rect = tree.get_cell_area(path, tree.get_column(0))
+ row_x, _, row_width, _ = tree.get_visible_rect()
+ tree.queue_draw_area(row_x, rect.y + self.tree_headers_height, row_width, rect.height)
+ else:
+ for rect in self.running_cell_areas:
+ tree.queue_draw_area(rect.x, rect.y + self.tree_headers_height, rect.width, rect.height)
+
+ return (not self.force_stop_enable)
+
+ def append_running_cell_area(self, cell_area):
+ if cell_area and (cell_area not in self.running_cell_areas):
+ self.running_cell_areas.append(cell_area)
+
+ def remove_running_cell_area(self, cell_area):
+ if cell_area in self.running_cell_areas:
+ self.running_cell_areas.remove(cell_area)
+ if not self.running_cell_areas:
+ self.reset_run()
+
+gobject.type_register(HobCellRendererController)
+
+class HobCellRendererPixbuf(gtk.CellRendererPixbuf):
+ def __init__(self):
+ gtk.CellRendererPixbuf.__init__(self)
+ self.control = HobCellRendererController()
+ # add icon checker for make the gtk-icon transfer to hob-icon
+ self.checker = HobIconChecker()
+ self.set_property("stock-size", gtk.ICON_SIZE_DND)
+
+ def get_pixbuf_from_stock_icon(self, widget, stock_id="", size=gtk.ICON_SIZE_DIALOG):
+ if widget and stock_id and gtk.icon_factory_lookup_default(stock_id):
+ return widget.render_icon(stock_id, size)
+
+ return None
+
+ def set_icon_name_to_id(self, new_name):
+ if new_name and type(new_name) == str:
+ # check the name is need to transfer to hob icon or not
+ name = self.checker.check_stock_icon(new_name)
+ if name.startswith("hic") or name.startswith("gtk"):
+ stock_id = name
+ else:
+ stock_id = 'gtk-' + name
+
+ return stock_id
+
+ ''' render cell exactly, "icon-name" is priority
+ if use the 'hic-task-refresh' will make the pix animation
+ if 'pix' will change the pixbuf for it from the pixbuf or image.
+ '''
+ def do_render(self, window, tree, background_area,cell_area, expose_area, flags):
+ if (not self.control) or (not tree):
+ return
+
+ x, y, w, h = self.on_get_size(tree, cell_area)
+ x += cell_area.x
+ y += cell_area.y
+ w -= 2 * self.get_property("xpad")
+ h -= 2 * self.get_property("ypad")
+
+ stock_id = ""
+ if self.props.icon_name:
+ stock_id = self.set_icon_name_to_id(self.props.icon_name)
+ elif self.props.stock_id:
+ stock_id = self.props.stock_id
+ elif self.props.pixbuf:
+ pix = self.props.pixbuf
+ else:
+ return
+
+ if stock_id:
+ pix = self.get_pixbuf_from_stock_icon(tree, stock_id, self.props.stock_size)
+ if stock_id == 'hic-task-refresh':
+ self.control.append_running_cell_area(cell_area)
+ if self.control.is_active():
+ self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, True)
+ else:
+ self.control.start_run(200, 0, 0, 1000, 150, tree)
+ else:
+ self.control.remove_running_cell_area(cell_area)
+ self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, False)
+
+ def on_get_size(self, widget, cell_area):
+ if self.props.icon_name or self.props.pixbuf or self.props.stock_id:
+ w, h = gtk.icon_size_lookup(self.props.stock_size)
+ calc_width = self.get_property("xpad") * 2 + w
+ calc_height = self.get_property("ypad") * 2 + h
+ x_offset = 0
+ y_offset = 0
+ if cell_area and w > 0 and h > 0:
+ x_offset = self.get_property("xalign") * (cell_area.width - calc_width - self.get_property("xpad"))
+ y_offset = self.get_property("yalign") * (cell_area.height - calc_height - self.get_property("ypad"))
+
+ return x_offset, y_offset, w, h
+
+ return 0, 0, 0, 0
+
+gobject.type_register(HobCellRendererPixbuf)
+
+class HobCellRendererToggle(gtk.CellRendererToggle):
+ def __init__(self):
+ gtk.CellRendererToggle.__init__(self)
+ self.ctrl = HobCellRendererController(is_draw_row=True)
+ self.ctrl.running_mode = self.ctrl.MODE_ONE_SHORT
+ self.cell_attr = {"fadeout": False, "number_of_children": 0}
+
+ def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
+ if (not self.ctrl) or (not widget):
+ return
+
+ if flags & gtk.CELL_RENDERER_SELECTED:
+ state = gtk.STATE_SELECTED
+ else:
+ state = gtk.STATE_NORMAL
+
+ if self.ctrl.is_active():
+ path = widget.get_path_at_pos(cell_area.x + cell_area.width/2, cell_area.y + cell_area.height/2)
+ # sometimes the parameters of cell_area will be a negative number,such as pull up down the scroll bar
+ # it's over the tree container range, so the path will be bad
+ if not path: return
+ path = path[0]
+ if path in self.ctrl.running_cell_areas:
+ cr = window.cairo_create()
+ color = widget.get_style().base[state]
+
+ row_x, _, row_width, _ = widget.get_visible_rect()
+ border_y = self.get_property("ypad")
+ self.ctrl.on_draw_fadeinout_cb(cr, color, row_x, cell_area.y - border_y, row_width, \
+ cell_area.height + border_y * 2, self.cell_attr["fadeout"])
+ # draw number of a group
+ if self.cell_attr["number_of_children"]:
+ text = "%d pkg" % self.cell_attr["number_of_children"]
+ pangolayout = widget.create_pango_layout(text)
+ textw, texth = pangolayout.get_pixel_size()
+ x = cell_area.x + (cell_area.width/2) - (textw/2)
+ y = cell_area.y + (cell_area.height/2) - (texth/2)
+
+ widget.style.paint_layout(window, state, True, cell_area, widget, "checkbox", x, y, pangolayout)
+ else:
+ return gtk.CellRendererToggle.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
+
+ '''delay: normally delay time is 1000ms
+ cell_list: whilch cells need to be render
+ '''
+ def fadeout(self, tree, delay, cell_list=None):
+ if (delay < 200) or (not tree):
+ return
+ self.cell_attr["fadeout"] = True
+ self.ctrl.running_cell_areas = cell_list
+ self.ctrl.start_run(200, 0, 0, delay, (delay * 200 / 1000), tree)
+
+ def connect_render_state_changed(self, func, usrdata=None):
+ if not func:
+ return
+ if usrdata:
+ self.ctrl.connect("run-timer-stopped", func, self, usrdata)
+ else:
+ self.ctrl.connect("run-timer-stopped", func, self)
+
+gobject.type_register(HobCellRendererToggle)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
new file mode 100644
index 000000000..927c19429
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
@@ -0,0 +1,186 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012 Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+try:
+ import gconf
+except:
+ pass
+
+class PersistentTooltip(gtk.Window):
+ """
+ A tooltip which persists once shown until the user dismisses it with the Esc
+ key or by clicking the close button.
+
+ # FIXME: the PersistentTooltip should be disabled when the user clicks anywhere off
+ # it. We can't do this with focus-out-event becuase modal ensures we have focus?
+
+ markup: some Pango text markup to display in the tooltip
+ """
+ def __init__(self, markup, parent_win=None):
+ gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+
+ # Inherit the system theme for a tooltip
+ style = gtk.rc_get_style_by_paths(gtk.settings_get_default(),
+ 'gtk-tooltip', 'gtk-tooltip', gobject.TYPE_NONE)
+ self.set_style(style)
+
+ # The placement of the close button on the tip should reflect how the
+ # window manager of the users system places close buttons. Try to read
+ # the metacity gconf key to determine whether the close button is on the
+ # left or the right.
+ # In the case that we can't determine the users configuration we default
+ # to close buttons being on the right.
+ __button_right = True
+ try:
+ client = gconf.client_get_default()
+ order = client.get_string("/apps/metacity/general/button_layout")
+ if order and order.endswith(":"):
+ __button_right = False
+ except NameError:
+ pass
+
+ # We need to ensure we're only shown once
+ self.shown = False
+
+ # We don't want any WM decorations
+ self.set_decorated(False)
+ # We don't want to show in the taskbar or window switcher
+ self.set_skip_pager_hint(True)
+ self.set_skip_taskbar_hint(True)
+ # We must be modal to ensure we grab focus when presented from a gtk.Dialog
+ self.set_modal(True)
+
+ self.set_border_width(0)
+ self.set_position(gtk.WIN_POS_MOUSE)
+ self.set_opacity(0.95)
+
+ # Ensure a reasonable minimum size
+ self.set_geometry_hints(self, 100, 50)
+
+ # Set this window as a transient window for parent(main window)
+ if parent_win:
+ self.set_transient_for(parent_win)
+ self.set_destroy_with_parent(True)
+ # Draw our label and close buttons
+ hbox = gtk.HBox(False, 0)
+ hbox.show()
+ self.add(hbox)
+
+ img = gtk.Image()
+ img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
+
+ self.button = gtk.Button()
+ self.button.set_image(img)
+ self.button.connect("clicked", self._dismiss_cb)
+ self.button.set_flags(gtk.CAN_DEFAULT)
+ self.button.grab_focus()
+ self.button.show()
+ vbox = gtk.VBox(False, 0)
+ vbox.show()
+ vbox.pack_start(self.button, False, False, 0)
+ if __button_right:
+ hbox.pack_end(vbox, True, True, 0)
+ else:
+ hbox.pack_start(vbox, True, True, 0)
+
+ self.set_default(self.button)
+
+ bin = gtk.HBox(True, 6)
+ bin.set_border_width(6)
+ bin.show()
+ self.label = gtk.Label()
+ self.label.set_line_wrap(True)
+ # We want to match the colours of the normal tooltips, as dictated by
+ # the users gtk+-2.0 theme, wherever possible - on some systems this
+ # requires explicitly setting a fg_color for the label which matches the
+ # tooltip_fg_color
+ settings = gtk.settings_get_default()
+ colours = settings.get_property('gtk-color-scheme').split('\n')
+ # remove any empty lines, there's likely to be a trailing one after
+ # calling split on a dictionary-like string
+ colours = filter(None, colours)
+ for col in colours:
+ item, val = col.split(': ')
+ if item == 'tooltip_fg_color':
+ style = self.label.get_style()
+ style.fg[gtk.STATE_NORMAL] = gtk.gdk.color_parse(val)
+ self.label.set_style(style)
+ break # we only care for the tooltip_fg_color
+
+ self.label.set_markup(markup)
+ self.label.show()
+ bin.add(self.label)
+ hbox.pack_end(bin, True, True, 6)
+
+ # add the original URL display for user reference
+ if 'a href' in markup:
+ hbox.set_tooltip_text(self.get_markup_url(markup))
+ hbox.show()
+
+ self.connect("key-press-event", self._catch_esc_cb)
+
+ """
+ Callback when the PersistentTooltip's close button is clicked.
+ Hides the PersistentTooltip.
+ """
+ def _dismiss_cb(self, button):
+ self.hide()
+ return True
+
+ """
+ Callback when the Esc key is detected. Hides the PersistentTooltip.
+ """
+ def _catch_esc_cb(self, widget, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ if keyname == "Escape":
+ self.hide()
+ return True
+
+ """
+ Called to present the PersistentTooltip.
+ Overrides the superclasses show() method to include state tracking.
+ """
+ def show(self):
+ if not self.shown:
+ self.shown = True
+ gtk.Window.show(self)
+
+ """
+ Called to hide the PersistentTooltip.
+ Overrides the superclasses hide() method to include state tracking.
+ """
+ def hide(self):
+ self.shown = False
+ gtk.Window.hide(self)
+
+ """
+ Called to get the hyperlink URL from markup text.
+ """
+ def get_markup_url(self, markup):
+ url = "http:"
+ if markup and type(markup) == str:
+ s = markup
+ if 'http:' in s:
+ import re
+ url = re.search('(http:[^,\\ "]+)', s).group(0)
+
+ return url
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py
new file mode 100644
index 000000000..1d28a111b
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py
@@ -0,0 +1,23 @@
+import gtk
+
+class ProgressBar(gtk.Dialog):
+ def __init__(self, parent):
+
+ gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT))
+ self.set_title("Parsing metadata, please wait...")
+ self.set_default_size(500, 0)
+ self.set_transient_for(parent)
+ self.progress = gtk.ProgressBar()
+ self.vbox.pack_start(self.progress)
+ self.show_all()
+
+ def set_text(self, msg):
+ self.progress.set_text(msg)
+
+ def update(self, x, y):
+ self.progress.set_fraction(float(x)/float(y))
+ self.progress.set_text("%2d %%" % (x*100/y))
+
+ def pulse(self):
+ self.progress.set_text("Loading...")
+ self.progress.pulse()
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py
new file mode 100644
index 000000000..3e2c660e4
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py
@@ -0,0 +1,59 @@
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011 Intel Corporation
+#
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+from bb.ui.crumbs.hobcolor import HobColors
+
+class HobProgressBar (gtk.ProgressBar):
+ def __init__(self):
+ gtk.ProgressBar.__init__(self)
+ self.set_rcstyle(True)
+ self.percentage = 0
+
+ def set_rcstyle(self, status):
+ rcstyle = gtk.RcStyle()
+ rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK)
+ if status == "stop":
+ rcstyle.bg[3] = gtk.gdk.Color(HobColors.WARNING)
+ elif status == "fail":
+ rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR)
+ else:
+ rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING)
+ self.modify_style(rcstyle)
+
+ def set_title(self, text=None):
+ if not text:
+ text = ""
+ text += " %.0f%%" % self.percentage
+ self.set_text(text)
+
+ def set_stop_title(self, text=None):
+ if not text:
+ text = ""
+ self.set_text(text)
+
+ def reset(self):
+ self.set_fraction(0)
+ self.set_text("")
+ self.set_rcstyle(True)
+ self.percentage = 0
+
+ def update(self, fraction):
+ self.percentage = int(fraction * 100)
+ self.set_fraction(fraction)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade
new file mode 100644
index 000000000..d7553a6e1
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade
@@ -0,0 +1,606 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
+<glade-interface>
+ <widget class="GtkDialog" id="build_dialog">
+ <property name="title" translatable="yes">Start a build</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="build_table">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkAlignment" id="status_alignment">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkHBox" id="status_hbox">
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="status_image">
+ <property name="visible">True</property>
+ <property name="no_show_all">True</property>
+ <property name="xalign">0</property>
+ <property name="stock">gtk-dialog-error</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">If you see this text something is wrong...</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="image_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="image_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Image:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="distribution_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="distribution_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Distribution:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="machine_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="machine_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Machine:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="refresh_button">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-refresh</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="location_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">32</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="dialog2">
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location: </property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <widget class="GtkButton" id="button7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-edit</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Search:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="main_window">
+ <child>
+ <widget class="GtkVBox" id="main_window_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolbar" id="main_toolbar">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolButton" id="main_toolbutton_build">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Build</property>
+ <property name="stock_id">gtk-execute</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="results_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 000000000..16a955d2b
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,551 @@
+
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import logging
+import time
+import urllib
+import urllib2
+import pango
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf
+
+class RunningBuildModel (gtk.TreeStore):
+ (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
+
+ def __init__ (self):
+ gtk.TreeStore.__init__ (self,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_INT)
+
+ def failure_model_filter(self, model, it):
+ color = model.get(it, self.COL_COLOR)[0]
+ if not color:
+ return False
+ if color == HobColors.ERROR or color == HobColors.WARNING:
+ return True
+ return False
+
+ def failure_model(self):
+ model = self.filter_new()
+ model.set_visible_func(self.failure_model_filter)
+ return model
+
+ def foreach_cell_func(self, model, path, iter, usr_data=None):
+ if model.get_value(iter, self.COL_ICON) == "gtk-execute":
+ model.set(iter, self.COL_ICON, "")
+
+ def close_task_refresh(self):
+ self.foreach(self.foreach_cell_func, None)
+
+class RunningBuild (gobject.GObject):
+ __gsignals__ = {
+ 'build-started' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-failed' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-complete' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-aborted' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'task-started' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'log-error' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'log-warning' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'disk-full' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'no-provider' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'log' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
+ }
+ pids_to_task = {}
+ tasks_to_iter = {}
+
+ def __init__ (self, sequential=False):
+ gobject.GObject.__init__ (self)
+ self.model = RunningBuildModel()
+ self.sequential = sequential
+ self.buildaborted = False
+
+ def reset (self):
+ self.pids_to_task.clear()
+ self.tasks_to_iter.clear()
+ self.model.clear()
+
+ def handle_event (self, event, pbar=None):
+ # Handle an event from the event queue, this may result in updating
+ # the model and thus the UI. Or it may be to tell us that the build
+ # has finished successfully (or not, as the case may be.)
+
+ parent = None
+ pid = 0
+ package = None
+ task = None
+
+ # If we have a pid attached to this message/event try and get the
+ # (package, task) pair for it. If we get that then get the parent iter
+ # for the message.
+ if hasattr(event, 'pid'):
+ pid = event.pid
+ if hasattr(event, 'process'):
+ pid = event.process
+
+ if pid and pid in self.pids_to_task:
+ (package, task) = self.pids_to_task[pid]
+ parent = self.tasks_to_iter[(package, task)]
+
+ if(isinstance(event, logging.LogRecord)):
+ if event.taskpid == 0 or event.levelno > logging.INFO:
+ self.emit("log", "handle", event)
+ # FIXME: this is a hack! More info in Yocto #1433
+ # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
+ # mask the error message as it's not informative for the user.
+ if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
+ return
+
+ if (event.levelno < logging.INFO or
+ event.msg.startswith("Running task")):
+ return # don't add these to the list
+
+ if event.levelno >= logging.ERROR:
+ icon = "dialog-error"
+ color = HobColors.ERROR
+ self.emit("log-error")
+ elif event.levelno >= logging.WARNING:
+ icon = "dialog-warning"
+ color = HobColors.WARNING
+ self.emit("log-warning")
+ else:
+ icon = None
+ color = HobColors.OK
+
+ # if we know which package we belong to, we'll append onto its list.
+ # otherwise, we'll jump to the top of the master list
+ if self.sequential or not parent:
+ tree_add = self.model.append
+ else:
+ tree_add = self.model.prepend
+ tree_add(parent,
+ (None,
+ package,
+ task,
+ event.getMessage(),
+ icon,
+ color,
+ 0))
+
+ # if there are warnings while processing a package
+ # (parent), mark the task with warning color;
+ # in case there are errors, the updates will be
+ # handled on TaskFailed.
+ if color == HobColors.WARNING and parent:
+ self.model.set(parent, self.model.COL_COLOR, color)
+ if task: #then we have a parent (package), and update it's color
+ self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color)
+
+ elif isinstance(event, bb.build.TaskStarted):
+ (package, task) = (event._package, event._task)
+
+ # Save out this PID.
+ self.pids_to_task[pid] = (package, task)
+
+ # Check if we already have this package in our model. If so then
+ # that can be the parent for the task. Otherwise we create a new
+ # top level for the package.
+ if ((package, None) in self.tasks_to_iter):
+ parent = self.tasks_to_iter[(package, None)]
+ else:
+ if self.sequential:
+ add = self.model.append
+ else:
+ add = self.model.prepend
+ parent = add(None, (None,
+ package,
+ None,
+ "Package: %s" % (package),
+ None,
+ HobColors.OK,
+ 0))
+ self.tasks_to_iter[(package, None)] = parent
+
+ # Because this parent package now has an active child mark it as
+ # such.
+ self.model.set(parent, self.model.COL_ICON, "gtk-execute")
+ parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
+ if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING:
+ self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING)
+
+ # Add an entry in the model for this task
+ i = self.model.append (parent, (None,
+ package,
+ task,
+ "Task: %s" % (task),
+ "gtk-execute",
+ HobColors.RUNNING,
+ 0))
+
+ # update the parent's active task count
+ num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
+ self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
+
+ # Save out the iter so that we can find it when we have a message
+ # that we need to attach to a task.
+ self.tasks_to_iter[(package, task)] = i
+
+ elif isinstance(event, bb.build.TaskBase):
+ self.emit("log", "info", event._message)
+ current = self.tasks_to_iter[(package, task)]
+ parent = self.tasks_to_iter[(package, None)]
+
+ # remove this task from the parent's active count
+ num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
+ self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
+
+ if isinstance(event, bb.build.TaskFailed):
+ # Mark the task and parent as failed
+ icon = "dialog-error"
+ color = HobColors.ERROR
+
+ logfile = event.logfile
+ if logfile and os.path.exists(logfile):
+ with open(logfile) as f:
+ logdata = f.read()
+ self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
+
+ for i in (current, parent):
+ self.model.set(i, self.model.COL_ICON, icon,
+ self.model.COL_COLOR, color)
+ else:
+ # Mark the parent package and the task as inactive,
+ # but make sure to preserve error, warnings and active
+ # states
+ parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
+ task_color = self.model.get(current, self.model.COL_COLOR)[0]
+
+ # Mark the task as inactive
+ self.model.set(current, self.model.COL_ICON, None)
+ if task_color != HobColors.ERROR:
+ if task_color == HobColors.WARNING:
+ self.model.set(current, self.model.COL_ICON, 'dialog-warning')
+ else:
+ self.model.set(current, self.model.COL_COLOR, HobColors.OK)
+
+ # Mark the parent as inactive
+ if parent_color != HobColors.ERROR:
+ if parent_color == HobColors.WARNING:
+ self.model.set(parent, self.model.COL_ICON, "dialog-warning")
+ else:
+ self.model.set(parent, self.model.COL_ICON, None)
+ if num_active == 0:
+ self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
+
+ # Clear the iters and the pids since when the task goes away the
+ # pid will no longer be used for messages
+ del self.tasks_to_iter[(package, task)]
+ del self.pids_to_task[pid]
+
+ elif isinstance(event, bb.event.BuildStarted):
+
+ self.emit("build-started")
+ self.model.prepend(None, (None,
+ None,
+ None,
+ "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
+ None,
+ HobColors.OK,
+ 0))
+ if pbar:
+ pbar.update(0, self.progress_total)
+ pbar.set_title(bb.event.getName(event))
+
+ elif isinstance(event, bb.event.BuildCompleted):
+ failures = int (event._failures)
+ self.model.prepend(None, (None,
+ None,
+ None,
+ "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
+ None,
+ HobColors.OK,
+ 0))
+
+ # Emit the appropriate signal depending on the number of failures
+ if self.buildaborted:
+ self.emit ("build-aborted")
+ self.buildaborted = False
+ elif (failures >= 1):
+ self.emit ("build-failed")
+ else:
+ self.emit ("build-succeeded")
+ # Emit a generic "build-complete" signal for things wishing to
+ # handle when the build is finished
+ self.emit("build-complete")
+ # reset the all cell's icon indicator
+ self.model.close_task_refresh()
+ if pbar:
+ pbar.set_text(event.msg)
+
+ elif isinstance(event, bb.event.DiskFull):
+ self.buildaborted = True
+ self.emit("disk-full")
+
+ elif isinstance(event, bb.command.CommandFailed):
+ self.emit("log", "error", "Command execution failed: %s" % (event.error))
+ if event.error.startswith("Exited with"):
+ # If the command fails with an exit code we're done, emit the
+ # generic signal for the UI to notify the user
+ self.emit("build-complete")
+ # reset the all cell's icon indicator
+ self.model.close_task_refresh()
+
+ elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
+ pbar.set_title("Loading cache")
+ self.progress_total = event.total
+ pbar.update(0, self.progress_total)
+ elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
+ pbar.update(event.current, self.progress_total)
+ elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
+ pbar.update(self.progress_total, self.progress_total)
+ pbar.hide()
+ elif isinstance(event, bb.event.ParseStarted) and pbar:
+ if event.total == 0:
+ return
+ pbar.set_title("Processing recipes")
+ self.progress_total = event.total
+ pbar.update(0, self.progress_total)
+ elif isinstance(event, bb.event.ParseProgress) and pbar:
+ pbar.update(event.current, self.progress_total)
+ elif isinstance(event, bb.event.ParseCompleted) and pbar:
+ pbar.hide()
+ #using runqueue events as many as possible to update the progress bar
+ elif isinstance(event, bb.runqueue.runQueueTaskFailed):
+ self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
+ elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+ self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
+ % (event.taskid, event.taskstring, event.exitcode))
+ elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
+ if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+ self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
+ (event.stats.completed + event.stats.active + event.stats.failed + 1,
+ event.stats.total, event.taskstring))
+ else:
+ if event.noexec:
+ tasktype = 'noexec task'
+ else:
+ tasktype = 'task'
+ self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
+ (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
+ event.stats.total, event.taskid, event.taskstring))
+ message = {}
+ message["eventname"] = bb.event.getName(event)
+ num_of_completed = event.stats.completed + event.stats.failed
+ message["current"] = num_of_completed
+ message["total"] = event.stats.total
+ message["title"] = ""
+ message["task"] = event.taskstring
+ self.emit("task-started", message)
+ elif isinstance(event, bb.event.MultipleProviders):
+ self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
+ % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
+ self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
+ elif isinstance(event, bb.event.NoProvider):
+ msg = ""
+ if event._runtime:
+ r = "R"
+ else:
+ r = ""
+
+ extra = ''
+ if not event._reasons:
+ if event._close_matches:
+ extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
+
+ if event._dependees:
+ msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
+ else:
+ msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
+ if event._reasons:
+ for reason in event._reasons:
+ msg += ("%s\n" % reason)
+ self.emit("no-provider", msg)
+ self.emit("log", "error", msg)
+ elif isinstance(event, bb.event.LogExecTTY):
+ icon = "dialog-warning"
+ color = HobColors.WARNING
+ if self.sequential or not parent:
+ tree_add = self.model.append
+ else:
+ tree_add = self.model.prepend
+ tree_add(parent,
+ (None,
+ package,
+ task,
+ event.msg,
+ icon,
+ color,
+ 0))
+ else:
+ if not isinstance(event, (bb.event.BuildBase,
+ bb.event.StampUpdate,
+ bb.event.ConfigParsed,
+ bb.event.RecipeParsed,
+ bb.event.RecipePreFinalise,
+ bb.runqueue.runQueueEvent,
+ bb.runqueue.runQueueExitWait,
+ bb.event.OperationStarted,
+ bb.event.OperationCompleted,
+ bb.event.OperationProgress)):
+ self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
+
+ return
+
+
+def do_pastebin(text):
+ url = 'http://pastebin.com/api_public.php'
+ params = {'paste_code': text, 'paste_format': 'text'}
+
+ req = urllib2.Request(url, urllib.urlencode(params))
+ response = urllib2.urlopen(req)
+ paste_url = response.read()
+
+ return paste_url
+
+
+class RunningBuildTreeView (gtk.TreeView):
+ __gsignals__ = {
+ "button_press_event" : "override"
+ }
+ def __init__ (self, readonly=False, hob=False):
+ gtk.TreeView.__init__ (self)
+ self.readonly = readonly
+
+ # The icon that indicates whether we're building or failed.
+ # add 'hob' flag because there has not only hob to share this code
+ if hob:
+ renderer = HobCellRendererPixbuf ()
+ else:
+ renderer = gtk.CellRendererPixbuf()
+ col = gtk.TreeViewColumn ("Status", renderer)
+ col.add_attribute (renderer, "icon-name", 4)
+ self.append_column (col)
+
+ # The message of the build.
+ # add 'hob' flag because there has not only hob to share this code
+ if hob:
+ self.message_renderer = HobWarpCellRendererText (col_number=1)
+ else:
+ self.message_renderer = gtk.CellRendererText ()
+ self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
+ self.message_column.add_attribute(self.message_renderer, 'background', 5)
+ self.message_renderer.set_property('editable', (not self.readonly))
+ self.append_column (self.message_column)
+
+ def do_button_press_event(self, event):
+ gtk.TreeView.do_button_press_event(self, event)
+
+ if event.button == 3:
+ selection = super(RunningBuildTreeView, self).get_selection()
+ (model, it) = selection.get_selected()
+ if it is not None:
+ can_paste = model.get(it, model.COL_LOG)[0]
+ if can_paste == 'pastebin':
+ # build a simple menu with a pastebin option
+ menu = gtk.Menu()
+ menuitem = gtk.MenuItem("Copy")
+ menu.append(menuitem)
+ menuitem.connect("activate", self.clipboard_handler, (model, it))
+ menuitem.show()
+ menuitem = gtk.MenuItem("Send log to pastebin")
+ menu.append(menuitem)
+ menuitem.connect("activate", self.pastebin_handler, (model, it))
+ menuitem.show()
+ menu.show()
+ menu.popup(None, None, None, event.button, event.time)
+
+ def _add_to_clipboard(self, clipping):
+ """
+ Add the contents of clipping to the system clipboard.
+ """
+ clipboard = gtk.clipboard_get()
+ clipboard.set_text(clipping)
+ clipboard.store()
+
+ def pastebin_handler(self, widget, data):
+ """
+ Send the log data to pastebin, then add the new paste url to the
+ clipboard.
+ """
+ (model, it) = data
+ paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
+
+ # @todo Provide visual feedback to the user that it is done and that
+ # it worked.
+ print paste_url
+
+ self._add_to_clipboard(paste_url)
+
+ def clipboard_handler(self, widget, data):
+ """
+ """
+ (model, it) = data
+ message = model.get(it, model.COL_MESSAGE)[0]
+
+ self._add_to_clipboard(message)
+
+class BuildFailureTreeView(gtk.TreeView):
+
+ def __init__ (self):
+ gtk.TreeView.__init__(self)
+ self.set_rules_hint(False)
+ self.set_headers_visible(False)
+ self.get_selection().set_mode(gtk.SELECTION_SINGLE)
+
+ # The icon that indicates whether we're building or failed.
+ renderer = HobCellRendererPixbuf ()
+ col = gtk.TreeViewColumn ("Status", renderer)
+ col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
+ self.append_column (col)
+
+ # The message of the build.
+ self.message_renderer = HobWarpCellRendererText (col_number=1)
+ self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
+ self.append_column (self.message_column)
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py
new file mode 100644
index 000000000..939864fa6
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py
@@ -0,0 +1,34 @@
+#
+# BitBake UI Utils
+#
+# Copyright (C) 2012 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This utility method looks for xterm or vte and return the
+# frist to exist, currently we are keeping this simple, but
+# we will likely move the oe.terminal implementation into
+# bitbake which will allow more flexibility.
+
+import os
+import bb
+
+def which_terminal():
+ term = bb.utils.which(os.environ["PATH"], "xterm")
+ if term:
+ return term + " -e "
+ term = bb.utils.which(os.environ["PATH"], "vte")
+ if term:
+ return term + " -c "
+ return None
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py
new file mode 100644
index 000000000..240aafc3e
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py
@@ -0,0 +1,333 @@
+#
+# BitBake Graphical GTK based Dependency Explorer
+#
+# Copyright (C) 2007 Ross Burton
+# Copyright (C) 2007 - 2008 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import gobject
+import gtk
+import Queue
+import threading
+import xmlrpclib
+import bb
+import bb.event
+from bb.ui.crumbs.progressbar import HobProgressBar
+
+# Package Model
+(COL_PKG_NAME) = (0)
+
+# Dependency Model
+(TYPE_DEP, TYPE_RDEP) = (0, 1)
+(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
+
+
+class PackageDepView(gtk.TreeView):
+ def __init__(self, model, dep_type, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.dep_type = dep_type
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
+
+ def _filter(self, model, iter):
+ (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
+ if this_type != self.dep_type: return False
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+
+class PackageReverseDepView(gtk.TreeView):
+ def __init__(self, model, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
+
+ def _filter(self, model, iter):
+ package = model.get_value(iter, COL_DEP_PACKAGE)
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+
+class DepExplorer(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+ self.set_title("Dependency Explorer")
+ self.set_default_size(500, 500)
+ self.connect("delete-event", gtk.main_quit)
+
+ # Create the data models
+ self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
+ self.pkg_model.set_sort_column_id(COL_PKG_NAME, gtk.SORT_ASCENDING)
+ self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, gtk.SORT_ASCENDING)
+
+ pane = gtk.HPaned()
+ pane.set_position(250)
+ self.add(pane)
+
+ # The master list of packages
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+
+ self.pkg_treeview = gtk.TreeView(self.pkg_model)
+ self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
+ column = gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME)
+ self.pkg_treeview.append_column(column)
+ pane.add1(scrolled)
+ scrolled.add(self.pkg_treeview)
+
+ box = gtk.VBox(homogeneous=True, spacing=4)
+
+ # Runtime Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
+ self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.rdep_treeview)
+ box.add(scrolled)
+
+ # Build Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
+ self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.dep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ # Reverse Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
+ self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
+ scrolled.add(self.revdep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ self.show_all()
+
+ def on_package_activated(self, treeview, path, column, data_col):
+ model = treeview.get_model()
+ package = model.get_value(model.get_iter(path), data_col)
+
+ pkg_path = []
+ def finder(model, path, iter, needle):
+ package = model.get_value(iter, COL_PKG_NAME)
+ if package == needle:
+ pkg_path.append(path)
+ return True
+ else:
+ return False
+ self.pkg_model.foreach(finder, package)
+ if pkg_path:
+ self.pkg_treeview.get_selection().select_path(pkg_path[0])
+ self.pkg_treeview.scroll_to_cell(pkg_path[0])
+
+ def on_cursor_changed(self, selection):
+ (model, it) = selection.get_selected()
+ if it is None:
+ current_package = None
+ else:
+ current_package = model.get_value(it, COL_PKG_NAME)
+ self.rdep_treeview.set_current_package(current_package)
+ self.dep_treeview.set_current_package(current_package)
+ self.revdep_treeview.set_current_package(current_package)
+
+
+ def parse(self, depgraph):
+ for package in depgraph["pn"]:
+ self.pkg_model.insert(0, (package,))
+
+ for package in depgraph["depends"]:
+ for depend in depgraph["depends"][package]:
+ self.depends_model.insert (0, (TYPE_DEP, package, depend))
+
+ for package in depgraph["rdepends-pn"]:
+ for rdepend in depgraph["rdepends-pn"][package]:
+ self.depends_model.insert (0, (TYPE_RDEP, package, rdepend))
+
+
+class gtkthread(threading.Thread):
+ quit = threading.Event()
+ def __init__(self, shutdown):
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.shutdown = shutdown
+
+ def run(self):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+ gtk.main()
+ gtkthread.quit.set()
+
+
+def main(server, eventHandler, params):
+ try:
+ params.updateFromServer(server)
+ cmdline = params.parseActions()
+ if not cmdline:
+ print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+ return 1
+ if 'msg' in cmdline and cmdline['msg']:
+ print(cmdline['msg'])
+ return 1
+ cmdline = cmdline['action']
+ if not cmdline or cmdline[0] != "generateDotGraph":
+ print("This UI requires the -g option")
+ return 1
+ ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
+ if error:
+ print("Error running command '%s': %s" % (cmdline, error))
+ return 1
+ elif ret != True:
+ print("Error running command '%s': returned %s" % (cmdline, ret))
+ return 1
+ except xmlrpclib.Fault as x:
+ print("XMLRPC Fault getting commandline:\n %s" % x)
+ return
+
+ try:
+ gtk.init_check()
+ except RuntimeError:
+ sys.stderr.write("Please set DISPLAY variable before running this command \n")
+ return
+
+ shutdown = 0
+
+ gtkgui = gtkthread(shutdown)
+ gtkgui.start()
+
+ gtk.gdk.threads_enter()
+ dep = DepExplorer()
+ bardialog = gtk.Dialog(parent=dep,
+ flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
+ bardialog.set_default_size(400, 50)
+ pbar = HobProgressBar()
+ bardialog.vbox.pack_start(pbar)
+ bardialog.show_all()
+ bardialog.connect("delete-event", gtk.main_quit)
+ gtk.gdk.threads_leave()
+
+ progress_total = 0
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if gtkthread.quit.isSet():
+ _, error = server.runCommand(["stateForceShutdown"])
+ if error:
+ print('Unable to cleanly stop: %s' % error)
+ break
+
+ if event is None:
+ continue
+
+ if isinstance(event, bb.event.CacheLoadStarted):
+ progress_total = event.total
+ gtk.gdk.threads_enter()
+ bardialog.set_title("Loading Cache")
+ pbar.update(0)
+ gtk.gdk.threads_leave()
+
+ if isinstance(event, bb.event.CacheLoadProgress):
+ x = event.current
+ gtk.gdk.threads_enter()
+ pbar.update(x * 1.0 / progress_total)
+ pbar.set_title('')
+ gtk.gdk.threads_leave()
+ continue
+
+ if isinstance(event, bb.event.CacheLoadCompleted):
+ bardialog.hide()
+ continue
+
+ if isinstance(event, bb.event.ParseStarted):
+ progress_total = event.total
+ if progress_total == 0:
+ continue
+ gtk.gdk.threads_enter()
+ pbar.update(0)
+ bardialog.set_title("Processing recipes")
+
+ gtk.gdk.threads_leave()
+
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.current
+ gtk.gdk.threads_enter()
+ pbar.update(x * 1.0 / progress_total)
+ pbar.set_title('')
+ gtk.gdk.threads_leave()
+ continue
+
+ if isinstance(event, bb.event.ParseCompleted):
+ bardialog.hide()
+ continue
+
+ if isinstance(event, bb.event.DepTreeGenerated):
+ gtk.gdk.threads_enter()
+ dep.parse(event._depgraph)
+ gtk.gdk.threads_leave()
+
+ if isinstance(event, bb.command.CommandCompleted):
+ continue
+
+ if isinstance(event, bb.command.CommandFailed):
+ print("Command execution failed: %s" % event.error)
+ return event.exitcode
+
+ if isinstance(event, bb.command.CommandExit):
+ return event.exitcode
+
+ if isinstance(event, bb.cooker.CookerExit):
+ break
+
+ continue
+ except EnvironmentError as ioerror:
+ # ignore interrupted io
+ if ioerror.args[0] == 4:
+ pass
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ print("\nThird Keyboard Interrupt, exit.\n")
+ break
+ if shutdown == 1:
+ print("\nSecond Keyboard Interrupt, stopping...\n")
+ _, error = server.runCommand(["stateForceShutdown"])
+ if error:
+ print('Unable to cleanly stop: %s' % error)
+ if shutdown == 0:
+ print("\nKeyboard Interrupt, closing down...\n")
+ _, error = server.runCommand(["stateShutdown"])
+ if error:
+ print('Unable to cleanly shutdown: %s' % error)
+ shutdown = shutdown + 1
+ pass
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py
new file mode 100644
index 000000000..f4ee7b41a
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py
@@ -0,0 +1,121 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import xmlrpclib
+from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
+from bb.ui.crumbs.progress import ProgressBar
+
+import Queue
+
+
+def event_handle_idle_func (eventHandler, build, pbar):
+
+ # Consume as many messages as we can in the time available to us
+ event = eventHandler.getEvent()
+ while event:
+ build.handle_event (event, pbar)
+ event = eventHandler.getEvent()
+
+ return True
+
+def scroll_tv_cb (model, path, iter, view):
+ view.scroll_to_cell (path)
+
+
+# @todo hook these into the GUI so the user has feedback...
+def running_build_failed_cb (running_build):
+ pass
+
+
+def running_build_succeeded_cb (running_build):
+ pass
+
+
+class MainWindow (gtk.Window):
+ def __init__ (self):
+ gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
+
+ # Setup tree view and the scrolled window
+ scrolled_window = gtk.ScrolledWindow ()
+ self.add (scrolled_window)
+ self.cur_build_tv = RunningBuildTreeView()
+ self.connect("delete-event", gtk.main_quit)
+ self.set_default_size(640, 480)
+ scrolled_window.add (self.cur_build_tv)
+
+
+def main (server, eventHandler, params):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+
+ window = MainWindow ()
+ window.show_all ()
+ pbar = ProgressBar(window)
+ pbar.connect("delete-event", gtk.main_quit)
+
+ # Create the object for the current build
+ running_build = RunningBuild ()
+ window.cur_build_tv.set_model (running_build.model)
+ running_build.model.connect("row-inserted", scroll_tv_cb, window.cur_build_tv)
+ running_build.connect ("build-succeeded", running_build_succeeded_cb)
+ running_build.connect ("build-failed", running_build_failed_cb)
+
+ try:
+ params.updateFromServer(server)
+ cmdline = params.parseActions()
+ if not cmdline:
+ print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+ return 1
+ if 'msg' in cmdline and cmdline['msg']:
+ logger.error(cmdline['msg'])
+ return 1
+ cmdline = cmdline['action']
+ ret, error = server.runCommand(cmdline)
+ if error:
+ print("Error running command '%s': %s" % (cmdline, error))
+ return 1
+ elif ret != True:
+ print("Error running command '%s': returned %s" % (cmdline, ret))
+ return 1
+ except xmlrpclib.Fault as x:
+ print("XMLRPC Fault getting commandline:\n %s" % x)
+ return 1
+
+ # Use a timeout function for probing the event queue to find out if we
+ # have a message waiting for us.
+ gobject.timeout_add (100,
+ event_handle_idle_func,
+ eventHandler,
+ running_build,
+ pbar)
+
+ try:
+ gtk.main()
+ except EnvironmentError as ioerror:
+ # ignore interrupted io
+ if ioerror.args[0] == 4:
+ pass
+ except KeyboardInterrupt:
+ pass
+ finally:
+ server.runCommand(["stateForceShutdown"])
+
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_display.png
new file mode 100644
index 000000000..a7f87101a
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_hover.png
new file mode 100644
index 000000000..2d9cd99b8
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/images/images_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add-hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add-hover.png
new file mode 100644
index 000000000..526df770d
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add-hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add.png
new file mode 100644
index 000000000..31e7090d6
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/add.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/alert.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/alert.png
new file mode 100644
index 000000000..d1c6f55a2
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/alert.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/confirmation.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/confirmation.png
new file mode 100644
index 000000000..3a5402d1e
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/confirmation.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/denied.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/denied.png
new file mode 100644
index 000000000..ee35c7def
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/denied.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/error.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/error.png
new file mode 100644
index 000000000..d06a8c151
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/error.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/info.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/info.png
new file mode 100644
index 000000000..ee8e8d846
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/info.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/issues.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/issues.png
new file mode 100644
index 000000000..b0c746133
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/issues.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/refresh.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/refresh.png
new file mode 100644
index 000000000..eb6c419db
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/refresh.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove-hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove-hover.png
new file mode 100644
index 000000000..aa57c6998
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove-hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove.png
new file mode 100644
index 000000000..05c3c293d
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/remove.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/tick.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/tick.png
new file mode 100644
index 000000000..beaad361c
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/indicators/tick.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_display.png
new file mode 100644
index 000000000..5afbba29f
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_hover.png
new file mode 100644
index 000000000..f9d294dfa
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/info/info_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_display.png
new file mode 100644
index 000000000..b7f9053a9
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_hover.png
new file mode 100644
index 000000000..0bf3ce0db
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/layers/layers_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_display.png
new file mode 100644
index 000000000..f5d0a5064
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_hover.png
new file mode 100644
index 000000000..c081165f3
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/packages/packages_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
new file mode 100644
index 000000000..e9809bc7d
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
new file mode 100644
index 000000000..7e48da9af
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_display.png
new file mode 100644
index 000000000..88c464db0
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_hover.png
new file mode 100644
index 000000000..d92a0bf2c
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/settings/settings_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_display.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_display.png
new file mode 100644
index 000000000..153c7afb6
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_display.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_hover.png b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_hover.png
new file mode 100644
index 000000000..afb7165fe
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/icons/templates/templates_hover.png
Binary files differ
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py
new file mode 100644
index 000000000..268562770
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py
@@ -0,0 +1,594 @@
+#
+# BitBake (No)TTY UI Implementation
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2006-2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division
+
+import os
+import sys
+import xmlrpclib
+import logging
+import progressbar
+import signal
+import bb.msg
+import time
+import fcntl
+import struct
+import copy
+import atexit
+from bb.ui import uihelper
+
+featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
+
+logger = logging.getLogger("BitBake")
+interactive = sys.stdout.isatty()
+
+class BBProgress(progressbar.ProgressBar):
+ def __init__(self, msg, maxval):
+ self.msg = msg
+ widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',
+ progressbar.ETA()]
+
+ try:
+ self._resize_default = signal.getsignal(signal.SIGWINCH)
+ except:
+ self._resize_default = None
+ progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets, fd=sys.stdout)
+
+ def _handle_resize(self, signum, frame):
+ progressbar.ProgressBar._handle_resize(self, signum, frame)
+ if self._resize_default:
+ self._resize_default(signum, frame)
+ def finish(self):
+ progressbar.ProgressBar.finish(self)
+ if self._resize_default:
+ signal.signal(signal.SIGWINCH, self._resize_default)
+
+class NonInteractiveProgress(object):
+ fobj = sys.stdout
+
+ def __init__(self, msg, maxval):
+ self.msg = msg
+ self.maxval = maxval
+
+ def start(self):
+ self.fobj.write("%s..." % self.msg)
+ self.fobj.flush()
+ return self
+
+ def update(self, value):
+ pass
+
+ def finish(self):
+ self.fobj.write("done.\n")
+ self.fobj.flush()
+
+def new_progress(msg, maxval):
+ if interactive:
+ return BBProgress(msg, maxval)
+ else:
+ return NonInteractiveProgress(msg, maxval)
+
+def pluralise(singular, plural, qty):
+ if(qty == 1):
+ return singular % qty
+ else:
+ return plural % qty
+
+
+class InteractConsoleLogFilter(logging.Filter):
+ def __init__(self, tf, format):
+ self.tf = tf
+ self.format = format
+
+ def filter(self, record):
+ if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
+ return False
+ self.tf.clearFooter()
+ return True
+
+class TerminalFilter(object):
+ rows = 25
+ columns = 80
+
+ def sigwinch_handle(self, signum, frame):
+ self.rows, self.columns = self.getTerminalColumns()
+ if self._sigwinch_default:
+ self._sigwinch_default(signum, frame)
+
+ def getTerminalColumns(self):
+ def ioctl_GWINSZ(fd):
+ try:
+ cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
+ except:
+ return None
+ return cr
+ cr = ioctl_GWINSZ(sys.stdout.fileno())
+ if not cr:
+ try:
+ fd = os.open(os.ctermid(), os.O_RDONLY)
+ cr = ioctl_GWINSZ(fd)
+ os.close(fd)
+ except:
+ pass
+ if not cr:
+ try:
+ cr = (env['LINES'], env['COLUMNS'])
+ except:
+ cr = (25, 80)
+ return cr
+
+ def __init__(self, main, helper, console, errconsole, format):
+ self.main = main
+ self.helper = helper
+ self.cuu = None
+ self.stdinbackup = None
+ self.interactive = sys.stdout.isatty()
+ self.footer_present = False
+ self.lastpids = []
+
+ if not self.interactive:
+ return
+
+ try:
+ import curses
+ except ImportError:
+ sys.exit("FATAL: The knotty ui could not load the required curses python module.")
+
+ import termios
+ self.curses = curses
+ self.termios = termios
+ try:
+ fd = sys.stdin.fileno()
+ self.stdinbackup = termios.tcgetattr(fd)
+ new = copy.deepcopy(self.stdinbackup)
+ new[3] = new[3] & ~termios.ECHO
+ termios.tcsetattr(fd, termios.TCSADRAIN, new)
+ curses.setupterm()
+ if curses.tigetnum("colors") > 2:
+ format.enable_color()
+ self.ed = curses.tigetstr("ed")
+ if self.ed:
+ self.cuu = curses.tigetstr("cuu")
+ try:
+ self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
+ signal.signal(signal.SIGWINCH, self.sigwinch_handle)
+ except:
+ pass
+ self.rows, self.columns = self.getTerminalColumns()
+ except:
+ self.cuu = None
+ if not self.cuu:
+ self.interactive = False
+ bb.note("Unable to use interactive mode for this terminal, using fallback")
+ return
+ console.addFilter(InteractConsoleLogFilter(self, format))
+ errconsole.addFilter(InteractConsoleLogFilter(self, format))
+
+ def clearFooter(self):
+ if self.footer_present:
+ lines = self.footer_present
+ sys.stdout.write(self.curses.tparm(self.cuu, lines))
+ sys.stdout.write(self.curses.tparm(self.ed))
+ self.footer_present = False
+
+ def updateFooter(self):
+ if not self.cuu:
+ return
+ activetasks = self.helper.running_tasks
+ failedtasks = self.helper.failed_tasks
+ runningpids = self.helper.running_pids
+ if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids):
+ return
+ if self.footer_present:
+ self.clearFooter()
+ if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
+ return
+ tasks = []
+ for t in runningpids:
+ tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
+
+ if self.main.shutdown:
+ content = "Waiting for %s running tasks to finish:" % len(activetasks)
+ elif not len(activetasks):
+ content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
+ else:
+ content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total)
+ print(content)
+ lines = 1 + int(len(content) / (self.columns + 1))
+ for tasknum, task in enumerate(tasks[:(self.rows - 2)]):
+ content = "%s: %s" % (tasknum, task)
+ print(content)
+ lines = lines + 1 + int(len(content) / (self.columns + 1))
+ self.footer_present = lines
+ self.lastpids = runningpids[:]
+ self.lastcount = self.helper.tasknumber_current
+
+ def finish(self):
+ if self.stdinbackup:
+ fd = sys.stdin.fileno()
+ self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
+
+def _log_settings_from_server(server):
+ # Get values of variables which control our output
+ includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+ if error:
+ logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
+ raise BaseException(error)
+ loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+ if error:
+ logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
+ raise BaseException(error)
+ consolelogfile, error = server.runCommand(["getSetVariable", "BB_CONSOLELOG"])
+ if error:
+ logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
+ raise BaseException(error)
+ return includelogs, loglines, consolelogfile
+
+_evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
+ "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
+ "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
+ "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
+ "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit",
+ "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
+ "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
+ "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"]
+
+def main(server, eventHandler, params, tf = TerminalFilter):
+
+ includelogs, loglines, consolelogfile = _log_settings_from_server(server)
+
+ if sys.stdin.isatty() and sys.stdout.isatty():
+ log_exec_tty = True
+ else:
+ log_exec_tty = False
+
+ helper = uihelper.BBUIHelper()
+
+ console = logging.StreamHandler(sys.stdout)
+ errconsole = logging.StreamHandler(sys.stderr)
+ format_str = "%(levelname)s: %(message)s"
+ format = bb.msg.BBLogFormatter(format_str)
+ bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut)
+ bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr)
+ console.setFormatter(format)
+ errconsole.setFormatter(format)
+ logger.addHandler(console)
+ logger.addHandler(errconsole)
+
+ bb.utils.set_process_name("KnottyUI")
+
+ if params.options.remote_server and params.options.kill_server:
+ server.terminateServer()
+ return
+
+ if consolelogfile and not params.options.show_environment and not params.options.show_versions:
+ bb.utils.mkdirhier(os.path.dirname(consolelogfile))
+ conlogformat = bb.msg.BBLogFormatter(format_str)
+ consolelog = logging.FileHandler(consolelogfile)
+ bb.msg.addDefaultlogFilter(consolelog)
+ consolelog.setFormatter(conlogformat)
+ logger.addHandler(consolelog)
+
+ llevel, debug_domains = bb.msg.constructLogOptions()
+ server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
+
+ universe = False
+ if not params.observe_only:
+ params.updateFromServer(server)
+ params.updateToServer(server, os.environ.copy())
+ cmdline = params.parseActions()
+ if not cmdline:
+ print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+ return 1
+ if 'msg' in cmdline and cmdline['msg']:
+ logger.error(cmdline['msg'])
+ return 1
+ if cmdline['action'][0] == "buildTargets" and "universe" in cmdline['action'][1]:
+ universe = True
+
+ ret, error = server.runCommand(cmdline['action'])
+ if error:
+ logger.error("Command '%s' failed: %s" % (cmdline, error))
+ return 1
+ elif ret != True:
+ logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
+ return 1
+
+
+ parseprogress = None
+ cacheprogress = None
+ main.shutdown = 0
+ interrupted = False
+ return_value = 0
+ errors = 0
+ warnings = 0
+ taskfailures = []
+
+ termfilter = tf(main, helper, console, errconsole, format)
+ atexit.register(termfilter.finish)
+
+ while True:
+ try:
+ event = eventHandler.waitEvent(0)
+ if event is None:
+ if main.shutdown > 1:
+ break
+ termfilter.updateFooter()
+ event = eventHandler.waitEvent(0.25)
+ if event is None:
+ continue
+ helper.eventHandler(event)
+ if isinstance(event, bb.runqueue.runQueueExitWait):
+ if not main.shutdown:
+ main.shutdown = 1
+ continue
+ if isinstance(event, bb.event.LogExecTTY):
+ if log_exec_tty:
+ tries = event.retries
+ while tries:
+ print("Trying to run: %s" % event.prog)
+ if os.system(event.prog) == 0:
+ break
+ time.sleep(event.sleep_delay)
+ tries -= 1
+ if tries:
+ continue
+ logger.warn(event.msg)
+ continue
+
+ if isinstance(event, logging.LogRecord):
+ if event.levelno >= format.ERROR:
+ errors = errors + 1
+ return_value = 1
+ elif event.levelno == format.WARNING:
+ warnings = warnings + 1
+
+ if event.taskpid != 0:
+ # For "normal" logging conditions, don't show note logs from tasks
+ # but do show them if the user has changed the default log level to
+ # include verbose/debug messages
+ if event.levelno <= format.NOTE and (event.levelno < llevel or (event.levelno == format.NOTE and llevel != format.VERBOSE)):
+ continue
+
+ # Prefix task messages with recipe/task
+ if event.taskpid in helper.running_tasks:
+ taskinfo = helper.running_tasks[event.taskpid]
+ event.msg = taskinfo['title'] + ': ' + event.msg
+ if hasattr(event, 'fn'):
+ event.msg = event.fn + ': ' + event.msg
+ logger.handle(event)
+ continue
+
+ if isinstance(event, bb.build.TaskFailedSilent):
+ logger.warn("Logfile for failed setscene task is %s" % event.logfile)
+ continue
+ if isinstance(event, bb.build.TaskFailed):
+ return_value = 1
+ logfile = event.logfile
+ if logfile and os.path.exists(logfile):
+ termfilter.clearFooter()
+ bb.error("Logfile of failure stored in: %s" % logfile)
+ if includelogs and not event.errprinted:
+ print("Log data follows:")
+ f = open(logfile, "r")
+ lines = []
+ while True:
+ l = f.readline()
+ if l == '':
+ break
+ l = l.rstrip()
+ if loglines:
+ lines.append(' | %s' % l)
+ if len(lines) > int(loglines):
+ lines.pop(0)
+ else:
+ print('| %s' % l)
+ f.close()
+ if lines:
+ for line in lines:
+ print(line)
+ if isinstance(event, bb.build.TaskBase):
+ logger.info(event._message)
+ continue
+ if isinstance(event, bb.event.ParseStarted):
+ if event.total == 0:
+ continue
+ parseprogress = new_progress("Parsing recipes", event.total).start()
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ parseprogress.update(event.current)
+ continue
+ if isinstance(event, bb.event.ParseCompleted):
+ if not parseprogress:
+ continue
+
+ parseprogress.finish()
+ print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
+ % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
+ continue
+
+ if isinstance(event, bb.event.CacheLoadStarted):
+ cacheprogress = new_progress("Loading cache", event.total).start()
+ continue
+ if isinstance(event, bb.event.CacheLoadProgress):
+ cacheprogress.update(event.current)
+ continue
+ if isinstance(event, bb.event.CacheLoadCompleted):
+ cacheprogress.finish()
+ print("Loaded %d entries from dependency cache." % event.num_entries)
+ continue
+
+ if isinstance(event, bb.command.CommandFailed):
+ return_value = event.exitcode
+ if event.error:
+ errors = errors + 1
+ logger.error("Command execution failed: %s", event.error)
+ main.shutdown = 2
+ continue
+ if isinstance(event, bb.command.CommandExit):
+ if not return_value:
+ return_value = event.exitcode
+ continue
+ if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
+ main.shutdown = 2
+ continue
+ if isinstance(event, bb.event.MultipleProviders):
+ logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
+ event._item,
+ ", ".join(event._candidates))
+ rtime = ""
+ if event._is_runtime:
+ rtime = "R"
+ logger.info("consider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, event._item))
+ continue
+ if isinstance(event, bb.event.NoProvider):
+ if event._runtime:
+ r = "R"
+ else:
+ r = ""
+
+ extra = ''
+ if not event._reasons:
+ if event._close_matches:
+ extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
+
+ # For universe builds, only show these as warnings, not errors
+ h = logger.warning
+ if not universe:
+ return_value = 1
+ errors = errors + 1
+ h = logger.error
+
+ if event._dependees:
+ h("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s", r, event._item, ", ".join(event._dependees), r, extra)
+ else:
+ h("Nothing %sPROVIDES '%s'%s", r, event._item, extra)
+ if event._reasons:
+ for reason in event._reasons:
+ h("%s", reason)
+ continue
+
+ if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+ logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
+ continue
+
+ if isinstance(event, bb.runqueue.runQueueTaskStarted):
+ if event.noexec:
+ tasktype = 'noexec task'
+ else:
+ tasktype = 'task'
+ logger.info("Running %s %s of %s (ID: %s, %s)",
+ tasktype,
+ event.stats.completed + event.stats.active +
+ event.stats.failed + 1,
+ event.stats.total, event.taskid, event.taskstring)
+ continue
+
+ if isinstance(event, bb.runqueue.runQueueTaskFailed):
+ return_value = 1
+ taskfailures.append(event.taskstring)
+ logger.error("Task %s (%s) failed with exit code '%s'",
+ event.taskid, event.taskstring, event.exitcode)
+ continue
+
+ if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+ logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
+ event.taskid, event.taskstring, event.exitcode)
+ continue
+
+ if isinstance(event, bb.event.DepTreeGenerated):
+ continue
+
+ # ignore
+ if isinstance(event, (bb.event.BuildBase,
+ bb.event.MetadataEvent,
+ bb.event.StampUpdate,
+ bb.event.ConfigParsed,
+ bb.event.RecipeParsed,
+ bb.event.RecipePreFinalise,
+ bb.runqueue.runQueueEvent,
+ bb.event.OperationStarted,
+ bb.event.OperationCompleted,
+ bb.event.OperationProgress,
+ bb.event.DiskFull)):
+ continue
+
+ logger.error("Unknown event: %s", event)
+
+ except EnvironmentError as ioerror:
+ termfilter.clearFooter()
+ # ignore interrupted io
+ if ioerror.args[0] == 4:
+ continue
+ sys.stderr.write(str(ioerror))
+ if not params.observe_only:
+ _, error = server.runCommand(["stateForceShutdown"])
+ main.shutdown = 2
+ except KeyboardInterrupt:
+ termfilter.clearFooter()
+ if params.observe_only:
+ print("\nKeyboard Interrupt, exiting observer...")
+ main.shutdown = 2
+ if not params.observe_only and main.shutdown == 1:
+ print("\nSecond Keyboard Interrupt, stopping...\n")
+ _, error = server.runCommand(["stateForceShutdown"])
+ if error:
+ logger.error("Unable to cleanly stop: %s" % error)
+ if not params.observe_only and main.shutdown == 0:
+ print("\nKeyboard Interrupt, closing down...\n")
+ interrupted = True
+ _, error = server.runCommand(["stateShutdown"])
+ if error:
+ logger.error("Unable to cleanly shutdown: %s" % error)
+ main.shutdown = main.shutdown + 1
+ pass
+ except Exception as e:
+ import traceback
+ sys.stderr.write(traceback.format_exc())
+ if not params.observe_only:
+ _, error = server.runCommand(["stateForceShutdown"])
+ main.shutdown = 2
+ return_value = 1
+ try:
+ summary = ""
+ if taskfailures:
+ summary += pluralise("\nSummary: %s task failed:",
+ "\nSummary: %s tasks failed:", len(taskfailures))
+ for failure in taskfailures:
+ summary += "\n %s" % failure
+ if warnings:
+ summary += pluralise("\nSummary: There was %s WARNING message shown.",
+ "\nSummary: There were %s WARNING messages shown.", warnings)
+ if return_value and errors:
+ summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.",
+ "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors)
+ if summary:
+ print(summary)
+
+ if interrupted:
+ print("Execution was interrupted, returning a non-zero exit code.")
+ if return_value == 0:
+ return_value = 1
+ except IOError as e:
+ import errno
+ if e.errno == errno.EPIPE:
+ pass
+
+ return return_value
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py
new file mode 100644
index 000000000..9589a77d7
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py
@@ -0,0 +1,373 @@
+#
+# BitBake Curses UI Implementation
+#
+# Implements an ncurses frontend for the BitBake utility.
+#
+# Copyright (C) 2006 Michael 'Mickey' Lauer
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ We have the following windows:
+
+ 1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
+ 2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
+ 3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
+
+ Basic window layout is like that:
+
+ |---------------------------------------------------------|
+ | <Main Window> | <Thread Activity Window> |
+ | | 0: foo do_compile complete|
+ | Building Gtk+-2.6.10 | 1: bar do_patch complete |
+ | Status: 60% | ... |
+ | | ... |
+ | | ... |
+ |---------------------------------------------------------|
+ |<Command Line Window> |
+ |>>> which virtual/kernel |
+ |openzaurus-kernel |
+ |>>> _ |
+ |---------------------------------------------------------|
+
+"""
+
+
+from __future__ import division
+import logging
+import os, sys, itertools, time, subprocess
+
+try:
+ import curses
+except ImportError:
+ sys.exit("FATAL: The ncurses ui could not load the required curses python module.")
+
+import bb
+import xmlrpclib
+from bb import ui
+from bb.ui import uihelper
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+X = 0
+Y = 1
+WIDTH = 2
+HEIGHT = 3
+
+MAXSTATUSLENGTH = 32
+
+class NCursesUI:
+ """
+ NCurses UI Class
+ """
+ class Window:
+ """Base Window Class"""
+ def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ self.win = curses.newwin( height, width, y, x )
+ self.dimensions = ( x, y, width, height )
+ """
+ if curses.has_colors():
+ color = 1
+ curses.init_pair( color, fg, bg )
+ self.win.bkgdset( ord(' '), curses.color_pair(color) )
+ else:
+ self.win.bkgdset( ord(' '), curses.A_BOLD )
+ """
+ self.erase()
+ self.setScrolling()
+ self.win.noutrefresh()
+
+ def erase( self ):
+ self.win.erase()
+
+ def setScrolling( self, b = True ):
+ self.win.scrollok( b )
+ self.win.idlok( b )
+
+ def setBoxed( self ):
+ self.boxed = True
+ self.win.box()
+ self.win.noutrefresh()
+
+ def setText( self, x, y, text, *args ):
+ self.win.addstr( y, x, text, *args )
+ self.win.noutrefresh()
+
+ def appendText( self, text, *args ):
+ self.win.addstr( text, *args )
+ self.win.noutrefresh()
+
+ def drawHline( self, y ):
+ self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
+ self.win.noutrefresh()
+
+ class DecoratedWindow( Window ):
+ """Base class for windows with a box and a title bar"""
+ def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
+ self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
+ self.decoration.setBoxed()
+ self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+ self.setTitle( title )
+
+ def setTitle( self, title ):
+ self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+# class TitleWindow( Window ):
+ #-------------------------------------------------------------------------#
+# """Title Window"""
+# def __init__( self, x, y, width, height ):
+# NCursesUI.Window.__init__( self, x, y, width, height )
+# version = bb.__version__
+# title = "BitBake %s" % version
+# credit = "(C) 2003-2007 Team BitBake"
+# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+# self.win.border()
+# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+ class ThreadActivityWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Thread Activity Window"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
+
+ def setStatus( self, thread, text ):
+ line = "%02d: %s" % ( thread, text )
+ width = self.dimensions[WIDTH]
+ if ( len(line) > width ):
+ line = line[:width-3] + "..."
+ else:
+ line = line.ljust( width )
+ self.setText( 0, thread, line )
+
+ #-------------------------------------------------------------------------#
+ class MainWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Main Window"""
+ def __init__( self, x, y, width, height ):
+ self.StatusPosition = width - MAXSTATUSLENGTH
+ NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
+ curses.nl()
+
+ def setTitle( self, title ):
+ title = "BitBake %s" % bb.__version__
+ self.decoration.setText( 2, 1, title, curses.A_BOLD )
+ self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
+
+ def setStatus(self, status):
+ while len(status) < MAXSTATUSLENGTH:
+ status = status + " "
+ self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
+
+
+ #-------------------------------------------------------------------------#
+ class ShellOutputWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Output"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
+
+ #-------------------------------------------------------------------------#
+ class ShellInputWindow( Window ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Input"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.Window.__init__( self, x, y, width, height )
+
+# put that to the top again from curses.textpad import Textbox
+# self.textbox = Textbox( self.win )
+# t = threading.Thread()
+# t.run = self.textbox.edit
+# t.start()
+
+ #-------------------------------------------------------------------------#
+ def main(self, stdscr, server, eventHandler, params):
+ #-------------------------------------------------------------------------#
+ height, width = stdscr.getmaxyx()
+
+ # for now split it like that:
+ # MAIN_y + THREAD_y = 2/3 screen at the top
+ # MAIN_x = 2/3 left, THREAD_y = 1/3 right
+ # CLI_y = 1/3 of screen at the bottom
+ # CLI_x = full
+
+ main_left = 0
+ main_top = 0
+ main_height = ( height // 3 * 2 )
+ main_width = ( width // 3 ) * 2
+ clo_left = main_left
+ clo_top = main_top + main_height
+ clo_height = height - main_height - main_top - 1
+ clo_width = width
+ cli_left = main_left
+ cli_top = clo_top + clo_height
+ cli_height = 1
+ cli_width = width
+ thread_left = main_left + main_width
+ thread_top = main_top
+ thread_height = main_height
+ thread_width = width - main_width
+
+ #tw = self.TitleWindow( 0, 0, width, main_top )
+ mw = self.MainWindow( main_left, main_top, main_width, main_height )
+ taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
+ clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
+ cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
+ cli.setText( 0, 0, "BB>" )
+
+ mw.setStatus("Idle")
+
+ helper = uihelper.BBUIHelper()
+ shutdown = 0
+
+ try:
+ params.updateFromServer(server)
+ cmdline = params.parseActions()
+ if not cmdline:
+ print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+ return 1
+ if 'msg' in cmdline and cmdline['msg']:
+ logger.error(cmdline['msg'])
+ return 1
+ cmdline = cmdline['action']
+ ret, error = server.runCommand(cmdline)
+ if error:
+ print("Error running command '%s': %s" % (cmdline, error))
+ return
+ elif ret != True:
+ print("Couldn't get default commandlind! %s" % ret)
+ return
+ except xmlrpclib.Fault as x:
+ print("XMLRPC Fault getting commandline:\n %s" % x)
+ return
+
+ exitflag = False
+ while not exitflag:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if not event:
+ continue
+
+ helper.eventHandler(event)
+ if isinstance(event, bb.build.TaskBase):
+ mw.appendText("NOTE: %s\n" % event._message)
+ if isinstance(event, logging.LogRecord):
+ mw.appendText(logging.getLevelName(event.levelno) + ': ' + event.getMessage() + '\n')
+
+ if isinstance(event, bb.event.CacheLoadStarted):
+ self.parse_total = event.total
+ if isinstance(event, bb.event.CacheLoadProgress):
+ x = event.current
+ y = self.parse_total
+ mw.setStatus("Loading Cache: %s [%2d %%]" % ( next(parsespin), x*100/y ) )
+ if isinstance(event, bb.event.CacheLoadCompleted):
+ mw.setStatus("Idle")
+ mw.appendText("Loaded %d entries from dependency cache.\n"
+ % ( event.num_entries))
+
+ if isinstance(event, bb.event.ParseStarted):
+ self.parse_total = event.total
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.current
+ y = self.parse_total
+ mw.setStatus("Parsing Recipes: %s [%2d %%]" % ( next(parsespin), x*100/y ) )
+ if isinstance(event, bb.event.ParseCompleted):
+ mw.setStatus("Idle")
+ mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked.\n"
+ % ( event.cached, event.parsed, event.skipped, event.masked ))
+
+# if isinstance(event, bb.build.TaskFailed):
+# if event.logfile:
+# if data.getVar("BBINCLUDELOGS", d):
+# bb.error("log data follows (%s)" % logfile)
+# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
+# if number_of_lines:
+# subprocess.call('tail -n%s %s' % (number_of_lines, logfile), shell=True)
+# else:
+# f = open(logfile, "r")
+# while True:
+# l = f.readline()
+# if l == '':
+# break
+# l = l.rstrip()
+# print '| %s' % l
+# f.close()
+# else:
+# bb.error("see log in %s" % logfile)
+
+ if isinstance(event, bb.command.CommandCompleted):
+ # stop so the user can see the result of the build, but
+ # also allow them to now exit with a single ^C
+ shutdown = 2
+ if isinstance(event, bb.command.CommandFailed):
+ mw.appendText("Command execution failed: %s" % event.error)
+ time.sleep(2)
+ exitflag = True
+ if isinstance(event, bb.command.CommandExit):
+ exitflag = True
+ if isinstance(event, bb.cooker.CookerExit):
+ exitflag = True
+
+ if isinstance(event, bb.event.LogExecTTY):
+ mw.appendText('WARN: ' + event.msg + '\n')
+ if helper.needUpdate:
+ activetasks, failedtasks = helper.getTasks()
+ taw.erase()
+ taw.setText(0, 0, "")
+ if activetasks:
+ taw.appendText("Active Tasks:\n")
+ for task in activetasks.itervalues():
+ taw.appendText(task["title"] + '\n')
+ if failedtasks:
+ taw.appendText("Failed Tasks:\n")
+ for task in failedtasks:
+ taw.appendText(task["title"] + '\n')
+
+ curses.doupdate()
+ except EnvironmentError as ioerror:
+ # ignore interrupted io
+ if ioerror.args[0] == 4:
+ pass
+
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ mw.appendText("Third Keyboard Interrupt, exit.\n")
+ exitflag = True
+ if shutdown == 1:
+ mw.appendText("Second Keyboard Interrupt, stopping...\n")
+ _, error = server.runCommand(["stateForceShutdown"])
+ if error:
+ print("Unable to cleanly stop: %s" % error)
+ if shutdown == 0:
+ mw.appendText("Keyboard Interrupt, closing down...\n")
+ _, error = server.runCommand(["stateShutdown"])
+ if error:
+ print("Unable to cleanly shutdown: %s" % error)
+ shutdown = shutdown + 1
+ pass
+
+def main(server, eventHandler, params):
+ if not os.isatty(sys.stdout.fileno()):
+ print("FATAL: Unable to run 'ncurses' UI without a TTY.")
+ return
+ ui = NCursesUI()
+ try:
+ curses.wrapper(ui.main, server, eventHandler, params)
+ except:
+ import traceback
+ traceback.print_exc()
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py
new file mode 100644
index 000000000..6bf4c1f03
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py
@@ -0,0 +1,465 @@
+#
+# BitBake ToasterUI Implementation
+# based on (No)TTY UI Implementation by Richard Purdie
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2006-2012 Richard Purdie
+# Copyright (C) 2013 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division
+import time
+import sys
+try:
+ import bb
+except RuntimeError as exc:
+ sys.exit(str(exc))
+
+from bb.ui import uihelper
+from bb.ui.buildinfohelper import BuildInfoHelper
+
+import bb.msg
+import logging
+import os
+
+# pylint: disable=invalid-name
+# module properties for UI modules are read by bitbake and the contract should not be broken
+
+
+featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
+
+logger = logging.getLogger("ToasterLogger")
+interactive = sys.stdout.isatty()
+
+def _log_settings_from_server(server):
+ # Get values of variables which control our output
+ includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+ if error:
+ logger.error("Unable to get the value of BBINCLUDELOGS variable: %s", error)
+ raise BaseException(error)
+ loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+ if error:
+ logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s", error)
+ raise BaseException(error)
+ consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
+ if error:
+ logger.error("Unable to get the value of BB_CONSOLELOG variable: %s", error)
+ raise BaseException(error)
+ return consolelogfile
+
+# create a log file for a single build and direct the logger at it;
+# log file name is timestamped to the millisecond (depending
+# on system clock accuracy) to ensure it doesn't overlap with
+# other log file names
+#
+# returns (log file, path to log file) for a build
+def _open_build_log(log_dir):
+ format_str = "%(levelname)s: %(message)s"
+
+ now = time.time()
+ now_ms = int((now - int(now)) * 1000)
+ time_str = time.strftime('build_%Y%m%d_%H%M%S', time.localtime(now))
+ log_file_name = time_str + ('.%d.log' % now_ms)
+ build_log_file_path = os.path.join(log_dir, log_file_name)
+
+ build_log = logging.FileHandler(build_log_file_path)
+
+ logformat = bb.msg.BBLogFormatter(format_str)
+ build_log.setFormatter(logformat)
+
+ bb.msg.addDefaultlogFilter(build_log)
+ logger.addHandler(build_log)
+
+ return (build_log, build_log_file_path)
+
+# stop logging to the build log if it exists
+def _close_build_log(build_log):
+ if build_log:
+ build_log.flush()
+ build_log.close()
+ logger.removeHandler(build_log)
+
+_evt_list = [
+ "bb.build.TaskBase",
+ "bb.build.TaskFailed",
+ "bb.build.TaskFailedSilent",
+ "bb.build.TaskStarted",
+ "bb.build.TaskSucceeded",
+ "bb.command.CommandCompleted",
+ "bb.command.CommandExit",
+ "bb.command.CommandFailed",
+ "bb.cooker.CookerExit",
+ "bb.event.BuildCompleted",
+ "bb.event.BuildStarted",
+ "bb.event.CacheLoadCompleted",
+ "bb.event.CacheLoadProgress",
+ "bb.event.CacheLoadStarted",
+ "bb.event.ConfigParsed",
+ "bb.event.DepTreeGenerated",
+ "bb.event.LogExecTTY",
+ "bb.event.MetadataEvent",
+ "bb.event.MultipleProviders",
+ "bb.event.NoProvider",
+ "bb.event.ParseCompleted",
+ "bb.event.ParseProgress",
+ "bb.event.RecipeParsed",
+ "bb.event.SanityCheck",
+ "bb.event.SanityCheckPassed",
+ "bb.event.TreeDataPreparationCompleted",
+ "bb.event.TreeDataPreparationStarted",
+ "bb.runqueue.runQueueTaskCompleted",
+ "bb.runqueue.runQueueTaskFailed",
+ "bb.runqueue.runQueueTaskSkipped",
+ "bb.runqueue.runQueueTaskStarted",
+ "bb.runqueue.sceneQueueTaskCompleted",
+ "bb.runqueue.sceneQueueTaskFailed",
+ "bb.runqueue.sceneQueueTaskStarted",
+ "logging.LogRecord"]
+
+def main(server, eventHandler, params):
+ # set to a logging.FileHandler instance when a build starts;
+ # see _open_build_log()
+ build_log = None
+
+ # set to the log path when a build starts
+ build_log_file_path = None
+
+ helper = uihelper.BBUIHelper()
+
+ # TODO don't use log output to determine when bitbake has started
+ #
+ # WARNING: this log handler cannot be removed, as localhostbecontroller
+ # relies on output in the toaster_ui.log file to determine whether
+ # the bitbake server has started, which only happens if
+ # this logger is setup here (see the TODO in the loop below)
+ console = logging.StreamHandler(sys.stdout)
+ format_str = "%(levelname)s: %(message)s"
+ formatter = bb.msg.BBLogFormatter(format_str)
+ bb.msg.addDefaultlogFilter(console)
+ console.setFormatter(formatter)
+ logger.addHandler(console)
+ logger.setLevel(logging.INFO)
+ llevel, debug_domains = bb.msg.constructLogOptions()
+ result, error = server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
+ if not result or error:
+ logger.error("can't set event mask: %s", error)
+ return 1
+
+ # verify and warn
+ build_history_enabled = True
+ inheritlist, _ = server.runCommand(["getVariable", "INHERIT"])
+
+ if not "buildhistory" in inheritlist.split(" "):
+ logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
+ build_history_enabled = False
+
+ if not params.observe_only:
+ params.updateFromServer(server)
+ params.updateToServer(server, os.environ.copy())
+ cmdline = params.parseActions()
+ if not cmdline:
+ print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+ return 1
+ if 'msg' in cmdline and cmdline['msg']:
+ logger.error(cmdline['msg'])
+ return 1
+
+ ret, error = server.runCommand(cmdline['action'])
+ if error:
+ logger.error("Command '%s' failed: %s" % (cmdline, error))
+ return 1
+ elif ret != True:
+ logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
+ return 1
+
+ # set to 1 when toasterui needs to shut down
+ main.shutdown = 0
+
+ interrupted = False
+ return_value = 0
+ errors = 0
+ warnings = 0
+ taskfailures = []
+ first = True
+
+ buildinfohelper = BuildInfoHelper(server, build_history_enabled,
+ os.getenv('TOASTER_BRBE'))
+
+ # write our own log files into bitbake's log directory;
+ # we're only interested in the path to the parent directory of
+ # this file, as we're writing our own logs into the same directory
+ consolelogfile = _log_settings_from_server(server)
+ log_dir = os.path.dirname(consolelogfile)
+ bb.utils.mkdirhier(log_dir)
+
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if first:
+ first = False
+
+ # TODO don't use log output to determine when bitbake has started
+ #
+ # this is the line localhostbecontroller needs to
+ # see in toaster_ui.log which it uses to decide whether
+ # the bitbake server has started...
+ logger.info("ToasterUI waiting for events")
+
+ if event is None:
+ if main.shutdown > 0:
+ # if shutting down, close any open build log first
+ _close_build_log(build_log)
+
+ break
+ continue
+
+ helper.eventHandler(event)
+
+ # pylint: disable=protected-access
+ # the code will look into the protected variables of the event; no easy way around this
+
+ # we treat ParseStarted as the first event of toaster-triggered
+ # builds; that way we get the Build Configuration included in the log
+ # and any errors that occur before BuildStarted is fired
+ if isinstance(event, bb.event.ParseStarted):
+ if not (build_log and build_log_file_path):
+ build_log, build_log_file_path = _open_build_log(log_dir)
+ continue
+
+ if isinstance(event, bb.event.BuildStarted):
+ if not (build_log and build_log_file_path):
+ build_log, build_log_file_path = _open_build_log(log_dir)
+
+ buildinfohelper.store_started_build(event, build_log_file_path)
+ continue
+
+ if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
+ buildinfohelper.update_and_store_task(event)
+ logger.info("Logfile for task %s", event.logfile)
+ continue
+
+ if isinstance(event, bb.build.TaskBase):
+ logger.info(event._message)
+
+ if isinstance(event, bb.event.LogExecTTY):
+ logger.info(event.msg)
+ continue
+
+ if isinstance(event, logging.LogRecord):
+ if event.levelno == -1:
+ event.levelno = formatter.ERROR
+
+ buildinfohelper.store_log_event(event)
+
+ if event.levelno >= formatter.ERROR:
+ errors = errors + 1
+ elif event.levelno == formatter.WARNING:
+ warnings = warnings + 1
+
+ # For "normal" logging conditions, don't show note logs from tasks
+ # but do show them if the user has changed the default log level to
+ # include verbose/debug messages
+ if event.taskpid != 0 and event.levelno <= formatter.NOTE:
+ continue
+
+ logger.handle(event)
+ continue
+
+ if isinstance(event, bb.build.TaskFailed):
+ buildinfohelper.update_and_store_task(event)
+ logfile = event.logfile
+ if logfile and os.path.exists(logfile):
+ bb.error("Logfile of failure stored in: %s" % logfile)
+ continue
+
+ # these events are unprocessed now, but may be used in the future to log
+ # timing and error informations from the parsing phase in Toaster
+ if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)):
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ continue
+ if isinstance(event, bb.event.ParseCompleted):
+ continue
+ if isinstance(event, bb.event.CacheLoadStarted):
+ continue
+ if isinstance(event, bb.event.CacheLoadProgress):
+ continue
+ if isinstance(event, bb.event.CacheLoadCompleted):
+ continue
+ if isinstance(event, bb.event.MultipleProviders):
+ logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
+ event._item,
+ ", ".join(event._candidates))
+ logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
+ continue
+
+ if isinstance(event, bb.event.NoProvider):
+ errors = errors + 1
+ if event._runtime:
+ r = "R"
+ else:
+ r = ""
+
+ if event._dependees:
+ text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
+ else:
+ text = "Nothing %sPROVIDES '%s'" % (r, event._item)
+
+ logger.error(text)
+ if event._reasons:
+ for reason in event._reasons:
+ logger.error("%s", reason)
+ text += reason
+ buildinfohelper.store_log_error(text)
+ continue
+
+ if isinstance(event, bb.event.ConfigParsed):
+ continue
+ if isinstance(event, bb.event.RecipeParsed):
+ continue
+
+ # end of saved events
+
+ if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
+ buildinfohelper.store_started_task(event)
+ continue
+
+ if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+ buildinfohelper.update_and_store_task(event)
+ continue
+
+ if isinstance(event, bb.runqueue.runQueueTaskFailed):
+ buildinfohelper.update_and_store_task(event)
+ taskfailures.append(event.taskstring)
+ logger.error("Task %s (%s) failed with exit code '%s'",
+ event.taskid, event.taskstring, event.exitcode)
+ continue
+
+ if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
+ buildinfohelper.update_and_store_task(event)
+ continue
+
+
+ if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
+ continue
+
+ if isinstance(event, (bb.event.BuildCompleted, bb.command.CommandFailed)):
+
+ errorcode = 0
+ if isinstance(event, bb.command.CommandFailed):
+ errors += 1
+ errorcode = 1
+ logger.error("Command execution failed: %s", event.error)
+
+ # turn off logging to the current build log
+ _close_build_log(build_log)
+
+ # reset ready for next BuildStarted
+ build_log = None
+
+ # update the build info helper on BuildCompleted, not on CommandXXX
+ buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
+
+ brbe = buildinfohelper.brbe
+ buildinfohelper.close(errorcode)
+
+ # we start a new build info
+ if params.observe_only:
+ logger.debug("ToasterUI prepared for new build")
+ errors = 0
+ warnings = 0
+ taskfailures = []
+ buildinfohelper = BuildInfoHelper(server, build_history_enabled)
+ else:
+ main.shutdown = 1
+
+ logger.info("ToasterUI build done, brbe: %s", brbe)
+ continue
+
+ if isinstance(event, (bb.command.CommandCompleted,
+ bb.command.CommandFailed,
+ bb.command.CommandExit)):
+ if params.observe_only:
+ errorcode = 0
+ else:
+ main.shutdown = 1
+
+ continue
+
+ if isinstance(event, bb.event.MetadataEvent):
+ if event.type == "SinglePackageInfo":
+ buildinfohelper.store_build_package_information(event)
+ elif event.type == "LayerInfo":
+ buildinfohelper.store_layer_info(event)
+ elif event.type == "BuildStatsList":
+ buildinfohelper.store_tasks_stats(event)
+ elif event.type == "ImagePkgList":
+ buildinfohelper.store_target_package_data(event)
+ elif event.type == "MissedSstate":
+ buildinfohelper.store_missed_state_tasks(event)
+ elif event.type == "ImageFileSize":
+ buildinfohelper.update_target_image_file(event)
+ elif event.type == "ArtifactFileSize":
+ buildinfohelper.update_artifact_image_file(event)
+ elif event.type == "LicenseManifestPath":
+ buildinfohelper.store_license_manifest_path(event)
+ elif event.type == "SetBRBE":
+ buildinfohelper.brbe = buildinfohelper._get_data_from_event(event)
+ elif event.type == "OSErrorException":
+ logger.error(event)
+ else:
+ logger.error("Unprocessed MetadataEvent %s ", str(event))
+ continue
+
+ if isinstance(event, bb.cooker.CookerExit):
+ # shutdown when bitbake server shuts down
+ main.shutdown = 1
+ continue
+
+ if isinstance(event, bb.event.DepTreeGenerated):
+ buildinfohelper.store_dependency_information(event)
+ continue
+
+ logger.warn("Unknown event: %s", event)
+ return_value += 1
+
+ except EnvironmentError as ioerror:
+ # ignore interrupted io
+ if ioerror.args[0] == 4:
+ pass
+ except KeyboardInterrupt:
+ main.shutdown = 1
+ except Exception as e:
+ # print errors to log
+ import traceback
+ from pprint import pformat
+ exception_data = traceback.format_exc()
+ logger.error("%s\n%s" , e, exception_data)
+
+ # save them to database, if possible; if it fails, we already logged to console.
+ try:
+ buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
+ except Exception as ce:
+ logger.error("CRITICAL - Failed to to save toaster exception to the database: %s", str(ce))
+
+ # make sure we return with an error
+ return_value += 1
+
+ if interrupted and return_value == 0:
+ return_value += 1
+
+ logger.warn("Return value is %d", return_value)
+ return return_value
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py
new file mode 100644
index 000000000..df093c53c
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py
@@ -0,0 +1,161 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+"""
+Use this class to fork off a thread to recieve event callbacks from the bitbake
+server and queue them for the UI to process. This process must be used to avoid
+client/server deadlocks.
+"""
+
+import socket, threading, pickle, collections
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+
+class BBUIEventQueue:
+ def __init__(self, BBServer, clientinfo=("localhost, 0")):
+
+ self.eventQueue = []
+ self.eventQueueLock = threading.Lock()
+ self.eventQueueNotify = threading.Event()
+
+ self.BBServer = BBServer
+ self.clientinfo = clientinfo
+
+ server = UIXMLRPCServer(self.clientinfo)
+ self.host, self.port = server.socket.getsockname()
+
+ server.register_function( self.system_quit, "event.quit" )
+ server.register_function( self.send_event, "event.sendpickle" )
+ server.socket.settimeout(1)
+
+ self.EventHandle = None
+
+ # the event handler registration may fail here due to cooker being in invalid state
+ # this is a transient situation, and we should retry a couple of times before
+ # giving up
+
+ for count_tries in range(5):
+ ret = self.BBServer.registerEventHandler(self.host, self.port)
+
+ if isinstance(ret, collections.Iterable):
+ self.EventHandle, error = ret
+ else:
+ self.EventHandle = ret
+ error = ""
+
+ if self.EventHandle != None:
+ break
+
+ errmsg = "Could not register UI event handler. Error: %s, host %s, "\
+ "port %d" % (error, self.host, self.port)
+ bb.warn("%s, retry" % errmsg)
+
+ import time
+ time.sleep(1)
+ else:
+ raise Exception(errmsg)
+
+ self.server = server
+
+ self.t = threading.Thread()
+ self.t.setDaemon(True)
+ self.t.run = self.startCallbackHandler
+ self.t.start()
+
+ def getEvent(self):
+
+ self.eventQueueLock.acquire()
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueLock.release()
+ return None
+
+ item = self.eventQueue.pop(0)
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueNotify.clear()
+
+ self.eventQueueLock.release()
+ return item
+
+ def waitEvent(self, delay):
+ self.eventQueueNotify.wait(delay)
+ return self.getEvent()
+
+ def queue_event(self, event):
+ self.eventQueueLock.acquire()
+ self.eventQueue.append(event)
+ self.eventQueueNotify.set()
+ self.eventQueueLock.release()
+
+ def send_event(self, event):
+ self.queue_event(pickle.loads(event))
+
+ def startCallbackHandler(self):
+
+ self.server.timeout = 1
+ bb.utils.set_process_name("UIEventQueue")
+ while not self.server.quit:
+ try:
+ self.server.handle_request()
+ except Exception as e:
+ import traceback
+ logger.error("BBUIEventQueue.startCallbackHandler: Exception while trying to handle request: %s\n%s" % (e, traceback.format_exc(e)))
+
+ self.server.server_close()
+
+ def system_quit( self ):
+ """
+ Shut down the callback thread
+ """
+ try:
+ self.BBServer.unregisterEventHandler(self.EventHandle)
+ except:
+ pass
+ self.server.quit = True
+
+class UIXMLRPCServer (SimpleXMLRPCServer):
+
+ def __init__( self, interface ):
+ self.quit = False
+ SimpleXMLRPCServer.__init__( self,
+ interface,
+ requestHandler=SimpleXMLRPCRequestHandler,
+ logRequests=False, allow_none=True)
+
+ def get_request(self):
+ while not self.quit:
+ try:
+ sock, addr = self.socket.accept()
+ sock.settimeout(1)
+ return (sock, addr)
+ except socket.timeout:
+ pass
+ return (None, None)
+
+ def close_request(self, request):
+ if request is None:
+ return
+ SimpleXMLRPCServer.close_request(self, request)
+
+ def process_request(self, request, client_address):
+ if request is None:
+ return
+ SimpleXMLRPCServer.process_request(self, request, client_address)
+
diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py
new file mode 100644
index 000000000..db70b763f
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py
@@ -0,0 +1,59 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import bb.build
+
+class BBUIHelper:
+ def __init__(self):
+ self.needUpdate = False
+ self.running_tasks = {}
+ # Running PIDs preserves the order tasks were executed in
+ self.running_pids = []
+ self.failed_tasks = []
+ self.tasknumber_current = 0
+ self.tasknumber_total = 0
+
+ def eventHandler(self, event):
+ if isinstance(event, bb.build.TaskStarted):
+ self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task) }
+ self.running_pids.append(event.pid)
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskSucceeded):
+ del self.running_tasks[event.pid]
+ self.running_pids.remove(event.pid)
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskFailedSilent):
+ del self.running_tasks[event.pid]
+ self.running_pids.remove(event.pid)
+ # Don't add to the failed tasks list since this is e.g. a setscene task failure
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskFailed):
+ del self.running_tasks[event.pid]
+ self.running_pids.remove(event.pid)
+ self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)})
+ self.needUpdate = True
+ if isinstance(event, bb.runqueue.runQueueTaskStarted) or isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+ self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1
+ self.tasknumber_total = event.stats.total
+ self.needUpdate = True
+
+ def getTasks(self):
+ self.needUpdate = False
+ return (self.running_tasks, self.failed_tasks)
+
OpenPOWER on IntegriCloud