Skip to contentSkip to conversation

Prototype GUI Image Creator

RFC PATCH New Joshua Lock 1 participant 14 messages Feb 4, 2011 Arm STM32

lore
Share & tools
lore
#0 lore raw

cover Prototype GUI Image Creator

From: Joshua Lock <[email protected]>

This patch series introduces a very basic image creator for BitBake. It is far from complete and not very usable but begins to explore the possibilities of graphical tooling built on top of BitBake.

Note: this does *not* work with the default None server in Poky, you need to use the XML-RPC server (uncomment the two xmlrpc lines in bitbake/bin/bitbake and comment out the none lines).

The code is pretty ugly in places, though I hope it's not too Poky specific.
Currently it will just set the targets of the build command to all of the selected packages, this needs refinement - ideally it will 'squash' the list of packages to only include packages not brough in by a selected image (this will also be useful to provide a summary/confirmation dialog before the build)but I want to implement this in a generic way (i.e. not relying on Poky metadata conventions).

There are also various NOTE/FIXME/TODO comments - these all need adressing.
Several UI enhancements are planned, such as an 'Advanced' section of the GUI which will enable the tweaking of variables such as DISTRO, PACKAGE_CLASSES, BB_NUMBER_THREADS and others. There are also various usability niggles that need work such as: the treeview columns being sized better and the fact that the sort column in the 'Packages (by Group)' column needs a custom sort function otherwise sort order changes when packages are toggled for inclusion.

I'm hoping for feedback on the approach and code review.

Thanks,
Joshua

Pull URL: git://git.pokylinux.org/poky-contrib.git
Branch: josh/gui
Browse: http://git.pokylinux.org/cgit.cgi/poky-contrib/log/?h=josh/gui

Thanks,
Joshua Lock <[email protected]>
---

Joshua Lock (13):
bitbake/cache: store a list of inherited files in the cache
bitbake: implement command to get all possible targets and their
dependencies
bitbake: implement command to find configuration files for a config
variable
bitbake/cooker: add generateTargetsTree method bitbake/cooker: reduce code duplication bitbake/[cooker|cache]: cache summary, license and group. Add to
targets tree
bitbake: introduce crumbs.TaskListModel a gtk.ListStore subclass bitbake/event: fix some whitespace issues bitbake/crumbs: update documentation header bitbake/progress: add method to pulse the progress bar bitbake/cooker: don't drop possible_world ref count
bitbake: Add new UI hob, a prototype Gtk+ GUI for creating images bitbake/progress: make progress dialog modal for parent window

 bitbake/lib/bb/cache.py                     |   17 +
 bitbake/lib/bb/command.py                   |   21 +
 bitbake/lib/bb/cooker.py                    |  111 +++++-
 bitbake/lib/bb/event.py                     |   17 +
 bitbake/lib/bb/ui/crumbs/__init__.py        |    2 +-
 bitbake/lib/bb/ui/crumbs/hobeventhandler.py |  138 ++++++
 bitbake/lib/bb/ui/crumbs/progress.py        |    7 +-
 bitbake/lib/bb/ui/crumbs/tasklistmodel.py   |  346 ++++++++++++++++
 bitbake/lib/bb/ui/hob.py                    |  596 +++++++++++++++++++++++++++
 9 files changed, 1245 insertions(+), 10 deletions(-)
 create mode 100644 bitbake/lib/bb/ui/crumbs/hobeventhandler.py
 create mode 100644 bitbake/lib/bb/ui/crumbs/tasklistmodel.py
 create mode 100644 bitbake/lib/bb/ui/hob.py
Signature · 1 line
1.7.4
#1 lore raw

PATCH 1/13 bitbake/cache: store a list of inherited files in the cache

From: Joshua Lock <[email protected]>

---
 bitbake/lib/bb/cache.py |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)
bitbake/lib/bb/cache.py +5 -0
index ff42a37..8480f89 100644
@@ -74,6 +74,7 @@ recipe_fields = (
     'tasks',
     'basetaskhashes',
     'hashfilename',
+    'inherits',
 )
 
 
@@ -164,6 +165,7 @@ class RecipeInfo(namedtuple('RecipeInfo', recipe_fields)):
             rprovides_pkg    = cls.pkgvar('RPROVIDES', packages, metadata),
             rdepends_pkg     = cls.pkgvar('RDEPENDS', packages, metadata),
             rrecommends_pkg  = cls.pkgvar('RRECOMMENDS', packages, metadata),
+            inherits         = cls.getvar('__inherit_cache', metadata),
         )
 
 
@@ -574,6 +576,7 @@ class CacheData(object):
         self.tasks = {}
         self.basetaskhash = {}
         self.hashfn = {}
+        self.inherits = {}
 
         """
         Indirect Cache variables
@@ -635,3 +638,5 @@ class CacheData(object):
         for task, taskhash in info.basetaskhashes.iteritems():
             identifier = '%s.%s' % (fn, task)
             self.basetaskhash[identifier] = taskhash
+
+        self.inherits[fn] = info.inherits
Signature · 1 line
1.7.4
#2 lore raw

PATCH 2/13 bitbake: implement command to get all possible targets and their dependencies

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Add a new command generateTargetsTree() which returns a dependency tree of possible targets (tasks and recipes) as well as their dependency information.

Optional parameter 'klass' also ensures any recipes which inherit the specified class path (i.e. 'classes/image.bbclass') are included in the model

---
 bitbake/lib/bb/command.py |   10 ++++++++++
 bitbake/lib/bb/cooker.py  |   25 +++++++++++++++++++++++++
 bitbake/lib/bb/event.py   |    9 +++++++++
 3 files changed, 44 insertions(+), 0 deletions(-)
bitbake/lib/bb/command.py +10 -0
index b880892..42b5b06 100644
@@ -222,6 +222,16 @@ class CommandsAsync:
         command.finishAsyncCommand()
     generateDotGraph.needcache = True
 
+    def generateTargetsTree(self, command, params):
+        """
+        Generate a tree of all buildable targets.
+        """
+        klass = params[0]
+
+        command.cooker.generateTargetsTree(klass)
+        command.finishAsyncCommand()
+    generateTargetsTree.needcache = True
+
     def showVersions(self, command, params):
         """
         Show the currently selected versions
bitbake/lib/bb/cooker.py +25 -0
index e524db7..ccb67a1 100644
@@ -430,6 +430,31 @@ class BBCooker:
             if not regex in matched:
                 collectlog.warn("No bb files matched BBFILE_PATTERN_%s '%s'" % (collection, pattern))
 
+    def checkInheritsClass(self, klass):
+        pkg_list = []
+        for pfn in self.status.pkg_fn:
+            inherits = self.status.inherits.get(pfn, None)
+            if inherits and inherits.count(klass) > 0:
+                pkg_list.append(self.status.pkg_fn[pfn])
+
+        return pkg_list
+
+    def generateTargetsTree(self, klass):
+        """
+        Generate a dependency tree of buildable targets
+        Generate an event with the result
+        """
+        pkgs = ['world']
+        # if inherited_class passed ensure all recipes which inherit the
+        # specified class are included in pkgs
+        if klass:
+            extra_pkgs = self.checkInheritsClass(klass)
+            pkgs = pkgs + extra_pkgs
+
+        # generate a dependency tree for all our packages
+        tree = self.generateDepTreeData(pkgs, 'build')
+        bb.event.fire(bb.event.TargetsTreeGenerated(tree), self.configuration.data)
+
     def buildWorldTargetList(self):
         """
          Build package list for "bitbake world"
bitbake/lib/bb/event.py +9 -0
index bd2042a..0777b42 100644
@@ -352,6 +352,15 @@ class DepTreeGenerated(Event):
         Event.__init__(self)
         self._depgraph = depgraph
 
+class TargetsTreeGenerated(Event):
+    """
+    Event when a set of buildable targets has been generated
+    """
+
+    def __init__(self, model):
+        Event.__init__(self)
+        self._model = model
+
 class MsgBase(Event):
     """Base class for messages"""
 
Signature · 1 line
1.7.4
#3 lore raw

PATCH 3/13 bitbake: implement command to find configuration files for a config variable

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Some configuration variables (MACHINE, MACHINE-SDK and DISTRO) set which confguration files bitbake should use.
The added command , findConfigFiles, enables a UI to query which files are suitable values for a specified parameter.

---
 bitbake/lib/bb/command.py |   11 +++++++++++
 bitbake/lib/bb/cooker.py  |   24 ++++++++++++++++++++++++
 bitbake/lib/bb/event.py   |   10 ++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)
bitbake/lib/bb/command.py +11 -0
index 42b5b06..e83751a 100644
@@ -232,6 +232,17 @@ class CommandsAsync:
         command.finishAsyncCommand()
     generateTargetsTree.needcache = True
 
+    def findConfigFiles(self, command, params):
+        """
+        Find config files which provide appropriate values
+        for the passed configuration variable. i.e. MACHINE
+        """
+        varname = params[0]
+
+        command.cooker.findConfigFiles(varname)
+        command.finishAsyncCommand()
+    findConfigFiles.needcache = True
+
     def showVersions(self, command, params):
         """
         Show the currently selected versions
bitbake/lib/bb/cooker.py +24 -0
index ccb67a1..e6643ec 100644
@@ -430,8 +430,32 @@ class BBCooker:
             if not regex in matched:
                 collectlog.warn("No bb files matched BBFILE_PATTERN_%s '%s'" % (collection, pattern))
 
+    def findConfigFiles(self, varname):
+        """
+        Find config files which are appropriate values for varname.
+        i.e. MACHINE, DISTRO
+        """
+        possible = []
+        var = varname.lower()
+
+        data = self.configuration.data
+        # iterate configs
+        bbpaths = bb.data.getVar('BBPATH', data, True).split(':')
+        for path in bbpaths:
+            confpath = os.path.join(path, "conf", var)
+            if os.path.exists(confpath):
+                for root, dirs, files in os.walk(confpath):
+                    # get all child files, these are appropriate values
+                    for f in files:
+                        val, sep, end = f.rpartition('.')
+                        if end == 'conf':
+                            possible.append(val)
+
+        bb.event.fire(bb.event.ConfigFilesFound(var, possible), self.configuration.data)
+
     def checkInheritsClass(self, klass):
         pkg_list = []
+
         for pfn in self.status.pkg_fn:
             inherits = self.status.inherits.get(pfn, None)
             if inherits and inherits.count(klass) > 0:
bitbake/lib/bb/event.py +10 -0
index 0777b42..176eeb8 100644
@@ -361,6 +361,16 @@ class TargetsTreeGenerated(Event):
         Event.__init__(self)
         self._model = model
 
+class ConfigFilesFound(Event):
+    """
+    Event when a list of appropriate config files has been generated
+    """
+
+    def __init__(self, variable, values):
+        Event.__init__(self)
+        self._variable =  variable
+        self._values = values
+
 class MsgBase(Event):
     """Base class for messages"""
 
Signature · 1 line
1.7.4
#4 lore raw

PATCH 4/13 bitbake/cooker: add generateTargetsTree method

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

The generateTargetsTree() command needs to return a model which includes more metadata than the one generated by generateDepTree().

This patch adds a new method generateTargetsTreeData() to the cooker, based on generateDepData(), and switches generateTargetsTree() to use it.

---
 bitbake/lib/bb/cooker.py |   67 +++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 64 insertions(+), 3 deletions(-)
bitbake/lib/bb/cooker.py +64 -3
index e6643ec..c7c8ad6 100644
@@ -453,7 +453,10 @@ class BBCooker:
 
         bb.event.fire(bb.event.ConfigFilesFound(var, possible), self.configuration.data)
 
-    def checkInheritsClass(self, klass):
+    def findInheritsClass(self, klass):
+        """
+        Find all recipes which inherit the specified class
+        """
         pkg_list = []
 
         for pfn in self.status.pkg_fn:
@@ -463,6 +466,64 @@ class BBCooker:
 
         return pkg_list
 
+    def generateTargetsTreeData(self, pkgs_to_build, task):
+        """
+        Create a tree of pkgs_to_build metadata, returning the data.
+        """
+
+        # Need files parsed
+        self.updateCache()
+
+        # If we are told to do the None task then query the default task
+        if (task == None):
+            task = self.configuration.cmd
+
+        pkgs_to_build = self.checkPackages(pkgs_to_build)
+
+        localdata = data.createCopy(self.configuration.data)
+        bb.data.update_data(localdata)
+        bb.data.expandKeys(localdata)
+        taskdata = bb.taskdata.TaskData(self.configuration.abort)
+
+        runlist = []
+        for k in pkgs_to_build:
+            taskdata.add_provider(localdata, self.status, k)
+            runlist.append([k, "do_%s" % task])
+        taskdata.add_unresolved(localdata, self.status)
+
+        rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
+        rq.rqdata.prepare()
+
+        seen_fnids = []
+        target_tree = {}
+        target_tree["depends"] = {}
+        target_tree["pn"] = {}
+        target_tree["rdepends-pn"] = {}
+
+        for task in xrange(len(rq.rqdata.runq_fnid)):
+            taskname = rq.rqdata.runq_task[task]
+            fnid = rq.rqdata.runq_fnid[task]
+            fn = taskdata.fn_index[fnid]
+            pn = self.status.pkg_fn[fn]
+            version  = "%s:%s-%s" % self.status.pkg_pepvpr[fn]
+            if pn not in target_tree["pn"]:
+                target_tree["pn"][pn] = {}
+                target_tree["pn"][pn]["filename"] = fn
+                target_tree["pn"][pn]["version"] = version
+            if fnid not in seen_fnids:
+                seen_fnids.append(fnid)
+                packages = []
+
+                target_tree["depends"][pn] = []
+                for dep in taskdata.depids[fnid]:
+                    target_tree["depends"][pn].append(taskdata.build_names_index[dep])
+
+                target_tree["rdepends-pn"][pn] = []
+                for rdep in taskdata.rdepids[fnid]:
+                    target_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep])
+
+        return target_tree
+
     def generateTargetsTree(self, klass):
         """
         Generate a dependency tree of buildable targets
@@ -472,11 +533,11 @@ class BBCooker:
         # if inherited_class passed ensure all recipes which inherit the
         # specified class are included in pkgs
         if klass:
-            extra_pkgs = self.checkInheritsClass(klass)
+            extra_pkgs = self.findInheritsClass(klass)
             pkgs = pkgs + extra_pkgs
 
         # generate a dependency tree for all our packages
-        tree = self.generateDepTreeData(pkgs, 'build')
+        tree = self.generateTargetsTreeData(pkgs, 'build')
         bb.event.fire(bb.event.TargetsTreeGenerated(tree), self.configuration.data)
 
     def buildWorldTargetList(self):
Signature · 1 line
1.7.4
#5 lore raw

PATCH 5/13 bitbake/cooker: reduce code duplication

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Move runqueua and taskdata initialisation into a new function, prepareTreeData(), so that generateDepTreeData() and generateTargetsTreeData() are not duplicating the same logic.

---
 bitbake/lib/bb/cooker.py |   37 +++++++++++--------------------------
 1 files changed, 11 insertions(+), 26 deletions(-)
bitbake/lib/bb/cooker.py +11 -26
index c7c8ad6..b3158ff 100644
@@ -237,11 +237,10 @@ class BBCooker:
             if data.getVarFlag( e, 'python', envdata ):
                 logger.plain("\npython %s () {\n%s}\n", e, data.getVar(e, envdata, 1))
 
-    def generateDepTreeData(self, pkgs_to_build, task):
+    def prepareTreeData(self, pkgs_to_build, task):
         """
-        Create a dependency tree of pkgs_to_build, returning the data.
+        Prepare a runqueue and taskdata object for iteration over pkgs_to_build
         """
-
         # Need files parsed
         self.updateCache()
 
@@ -265,6 +264,14 @@ class BBCooker:
         rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
         rq.rqdata.prepare()
 
+        return taskdata, rq
+
+    def generateDepTreeData(self, pkgs_to_build, task):
+        """
+        Create a dependency tree of pkgs_to_build, returning the data.
+        """
+        taskdata, rq = self.prepareTreeData(pkgs_to_build, task)
+
         seen_fnids = []
         depend_tree = {}
         depend_tree["depends"] = {}
@@ -470,29 +477,7 @@ class BBCooker:
         """
         Create a tree of pkgs_to_build metadata, returning the data.
         """
-
-        # Need files parsed
-        self.updateCache()
-
-        # If we are told to do the None task then query the default task
-        if (task == None):
-            task = self.configuration.cmd
-
-        pkgs_to_build = self.checkPackages(pkgs_to_build)
-
-        localdata = data.createCopy(self.configuration.data)
-        bb.data.update_data(localdata)
-        bb.data.expandKeys(localdata)
-        taskdata = bb.taskdata.TaskData(self.configuration.abort)
-
-        runlist = []
-        for k in pkgs_to_build:
-            taskdata.add_provider(localdata, self.status, k)
-            runlist.append([k, "do_%s" % task])
-        taskdata.add_unresolved(localdata, self.status)
-
-        rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
-        rq.rqdata.prepare()
+        taskdata, rq = self.prepareTreeData(pkgs_to_build, task)
 
         seen_fnids = []
         target_tree = {}
Signature · 1 line
1.7.4
#6 lore raw

PATCH 6/13 bitbake/[cooker|cache]: cache summary, license and group. Add to targets tree

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Add summary, license and group metadata to RecipeInfo and the cache. Unfortunately this impacts parse speed but gives us a much richer set of metadata to expose through UI's which can be accessed via the generateTargetsTree command.

---
 bitbake/lib/bb/cache.py  |   12 ++++++++++++
 bitbake/lib/bb/cooker.py |    6 ++++++
 2 files changed, 18 insertions(+), 0 deletions(-)
bitbake/lib/bb/cache.py +12 -0
index 8480f89..0bfc5dc 100644
@@ -75,6 +75,9 @@ recipe_fields = (
     'basetaskhashes',
     'hashfilename',
     'inherits',
+    'summary',
+    'license',
+    'section',
 )
 
 
@@ -166,6 +169,9 @@ class RecipeInfo(namedtuple('RecipeInfo', recipe_fields)):
             rdepends_pkg     = cls.pkgvar('RDEPENDS', packages, metadata),
             rrecommends_pkg  = cls.pkgvar('RRECOMMENDS', packages, metadata),
             inherits         = cls.getvar('__inherit_cache', metadata),
+            summary          = cls.getvar('SUMMARY', metadata),
+            license          = cls.getvar('LICENSE', metadata),
+            section          = cls.getvar('SECTION', metadata),
         )
 
 
@@ -577,6 +583,9 @@ class CacheData(object):
         self.basetaskhash = {}
         self.hashfn = {}
         self.inherits = {}
+        self.summary = {}
+        self.license = {}
+        self.section = {}
 
         """
         Indirect Cache variables
@@ -640,3 +649,6 @@ class CacheData(object):
             self.basetaskhash[identifier] = taskhash
 
         self.inherits[fn] = info.inherits
+        self.summary[fn] = info.summary
+        self.license[fn] = info.license
+        self.section[fn] = info.section
bitbake/lib/bb/cooker.py +6 -0
index b3158ff..39cf011 100644
@@ -491,10 +491,16 @@ class BBCooker:
             fn = taskdata.fn_index[fnid]
             pn = self.status.pkg_fn[fn]
             version  = "%s:%s-%s" % self.status.pkg_pepvpr[fn]
+            summary = self.status.summary[fn]
+            license = self.status.license[fn]
+            section = self.status.section[fn]
             if pn not in target_tree["pn"]:
                 target_tree["pn"][pn] = {}
                 target_tree["pn"][pn]["filename"] = fn
                 target_tree["pn"][pn]["version"] = version
+                target_tree["pn"][pn]["summary"] = summary
+                target_tree["pn"][pn]["license"] = license
+                target_tree["pn"][pn]["section"] = section
             if fnid not in seen_fnids:
                 seen_fnids.append(fnid)
                 packages = []
Signature · 1 line
1.7.4
#7 lore raw

PATCH 7/13 bitbake: introduce crumbs.TaskListModel a gtk.ListStore subclass

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Provide a gtk.ListStore subclass which includes a function, populate(), which takes as input the data emitted by bb.event.TargetsTreeGenerated and fills the ListStore model appropriately.
Furthermore convenience functions are provided by which the caller can get gtk.TreeModel subclasses which provide filtered views of the data.

---
 bitbake/lib/bb/ui/crumbs/tasklistmodel.py |  346 +++++++++++++++++++++++++++++
 1 files changed, 346 insertions(+), 0 deletions(-)
 create mode 100644 bitbake/lib/bb/ui/crumbs/tasklistmodel.py
bitbake/lib/bb/ui/crumbs/tasklistmodel.py new file +346 -0
new file mode 100644
index 0000000..a83a176
@@ -0,0 +1,346 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Joshua Lock <[email protected]>
+#
+# 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
+
+class TaskListModel(gtk.ListStore):
+    """
+    This class defines an gtk.ListStore subclass which will convert the output
+    of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
+    providing convenience functions to access gtk.TreeModel subclasses which
+    provide filtered views of the data.
+    """
+    (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC) = range(8)
+
+    __gsignals__ = {
+        "tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                                ())
+        }
+
+    """
+    """
+    def __init__(self):
+        self.contents = None
+        self.tasks = None
+        self.packages = None
+        self.images = None
+        
+        gtk.ListStore.__init__ (self,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_BOOLEAN)
+
+    """
+    Create, if required, and return a filtered gtk.TreeModel
+    containing only the items which are to be included in the
+    image
+    """
+    def contents_model(self):
+        if not self.contents:
+            self.contents = self.filter_new()
+            self.contents.set_visible_column(self.COL_INC)
+        return self.contents
+    
+    """
+    Helper function to determine whether an item is a task
+    """
+    def task_model_filter(self, model, it):
+        if model.get_value(it, self.COL_TYPE) == 'task':
+            return True
+        else:
+            return False
+
+    """
+    Create, if required, and return a filtered gtk.TreeModel
+    containing only the items which are tasks
+    """
+    def tasks_model(self):
+        if not self.tasks:
+            self.tasks = self.filter_new()
+            self.tasks.set_visible_func(self.task_model_filter)
+        return self.tasks
+
+    """
+    Helper function to determine whether an item is an image
+    """
+    def image_model_filter(self, model, it):
+        if model.get_value(it, self.COL_TYPE) == 'image':
+            return True
+        else:
+            return False
+
+    """
+    Create, if required, and return a filtered gtk.TreeModel
+    containing only the items which are images
+    """
+    def images_model(self):
+        if not self.images:
+            self.images = self.filter_new()
+            self.images.set_visible_func(self.image_model_filter)
+        return self.images
+
+    """
+    Helper function to determine whether an item is a package
+    """
+    def package_model_filter(self, model, it):
+        if model.get_value(it, self.COL_TYPE) == 'package':
+            return True
+        else:
+            return False
+
+    """
+    Create, if required, and return a filtered gtk.TreeModel
+    containing only the items which are packages
+    """
+    def packages_model(self):
+        if not self.packages:
+            self.packages = self.filter_new()
+            self.packages.set_visible_func(self.package_model_filter)
+        return self.packages
+
+    """
+    The populate() function takes as input the data from a
+    bb.event.TargetsTreeGenerated event and populates the TaskList.
+    Once the population is done it emits gsignal tasklist-populated
+    to notify any listeners that the model is ready
+    """
+    def populate(self, event_model):
+        for item in event_model["pn"]:
+            atype = 'package'
+            name = item
+            summary = event_model["pn"][item]["summary"]
+            license = event_model["pn"][item]["license"]
+            group = event_model["pn"][item]["section"]
+            
+	    depends = event_model["depends"].get(item, "")
+            rdepends = event_model["rdepends-pn"].get(item, "")
+            depends = depends + rdepends
+            self.squish(depends)
+            deps = " ".join(depends)
+            
+            if name.count('task-') > 0:
+                atype = 'task'
+            elif name.count('-image-') > 0:
+                atype = 'image'
+
+            self.set(self.append(), self.COL_NAME, name, self.COL_DESC, summary,
+	             self.COL_LIC, license, self.COL_GROUP, group,
+		     self.COL_DEPS, deps, self.COL_BINB, "",
+		     self.COL_TYPE, atype, self.COL_INC, False)
+	
+	self.emit("tasklist-populated")
+
+    """
+    squish lst so that it doesn't contain any duplicates
+    """
+    def squish(self, lst):
+        seen = {}
+        for l in lst:
+            seen[l] = 1
+        return seen.keys()
+
+    """
+    Mark the item at path as not included
+    NOTE:
+    path should be a gtk.TreeModelPath into self (not a filtered model)
+    """
+    def remove_item_path(self, path):
+        self[path][self.COL_BINB] = ""
+        self[path][self.COL_INC] = False
+
+    """
+    """
+    def mark(self, path):
+        name = self[path][self.COL_NAME]
+        it = self.get_iter_first()
+        removals = []
+        #print("Removing %s" % name)
+
+        self.remove_item_path(path)
+
+        # Remove all dependent packages, update binb
+        while it:
+            path = self.get_path(it)
+            # FIXME: need to ensure partial name matching doesn't happen, regexp?
+            if self[path][self.COL_INC] and self[path][self.COL_DEPS].count(name):
+                #print("%s depended on %s, marking for removal" % (self[path][self.COL_NAME], name))
+                # found a dependency, remove it
+                self.mark(path)
+            if self[path][self.COL_INC] and self[path][self.COL_BINB].count(name):
+                binb = self.find_alt_dependency(self[path][self.COL_NAME])
+                #print("%s was brought in by %s, binb set to %s" % (self[path][self.COL_NAME], name, binb))
+                self[path][self.COL_BINB] = binb
+            it = self.iter_next(it)
+
+    """
+    """
+    def sweep_up(self):
+        removals = []
+        it = self.get_iter_first()
+
+	while it:
+	    path = self.get_path(it)
+	    binb = self[path][self.COL_BINB]
+	    if binb == "" or binb is None:
+                #print("Sweeping up %s" % self[path][self.COL_NAME])
+                if not path in removals:
+                    removals.extend(path)
+            it = self.iter_next(it)
+
+	while removals:
+	    path = removals.pop()
+	    self.mark(path)
+
+    """
+    Remove an item from the contents
+    """
+    def remove_item(self, path):
+        self.mark(path)
+        self.sweep_up()
+
+    """
+    Find the name of an item in the image contents which depends on the item
+    at contents_path returns either an item name (str) or None
+    NOTE:
+    contents_path must be a path in the self.contents gtk.TreeModel
+    """
+    def find_alt_dependency(self, name):
+        it = self.get_iter_first()
+        while it:
+            # iterate all items in the model
+            path = self.get_path(it)
+            deps = self[path][self.COL_DEPS]
+            itname = self[path][self.COL_NAME]
+            inc = self[path][self.COL_INC]
+            if itname != name and inc and deps.count(name) > 0:
+		# if this item depends on the item, return this items name
+		#print("%s depends on %s" % (itname, name))
+	        return itname
+	    it = self.iter_next(it)
+	return ""
+
+    """
+    Convert a path in self to a path in the filtered contents model
+    """
+    def contents_path_for_path(self, path):
+        return self.contents.convert_child_path_to_path(path)
+
+    """
+    Check the self.contents gtk.TreeModel for an item
+    where COL_NAME matches item_name
+    Returns True if a match is found, False otherwise
+    """
+    def contents_includes_name(self, item_name):
+        it = self.contents.get_iter_first()
+        while it:
+            path = self.contents.get_path(it)
+            if self.contents[path][self.COL_NAME] == item_name:
+                return True
+            it = self.contents.iter_next(it)
+        return False
+
+    """
+    Add this item, and any of its dependencies, to the image contents
+    """
+    def include_item(self, item_path, binb=""):
+        name = self[item_path][self.COL_NAME]
+        deps = self[item_path][self.COL_DEPS]
+        cur_inc = self[item_path][self.COL_INC]
+        #print("Adding %s for %s dependency" % (name, binb))
+        if not cur_inc:
+            self[item_path][self.COL_INC] = True
+            self[item_path][self.COL_BINB] = binb
+        if deps:
+            #print("Dependencies of %s are %s" % (name, deps))
+            # add all of the deps and set their binb to this item
+            for dep in deps.split(" "):
+                # FIXME: this skipping virtuals can't be right? Unless we choose only to show target
+                # packages? In which case we should handle this server side...
+                # If the contents model doesn't already contain dep, add it
+                if not dep.startswith("virtual") and not self.contents_includes_name(dep):
+                    path = self.find_path_for_item(dep)
+                    if path:
+                        self.include_item(path, name)
+                    else:
+                        pass
+
+    """
+    Find the model path for the item_name
+    Returns the path in the model or None
+    """
+    def find_path_for_item(self, item_name):
+        it = self.get_iter_first()
+        path = None
+        while it:
+            path = self.get_path(it)
+            if (self[path][self.COL_NAME] == item_name):
+                return path
+            else:
+                it = self.iter_next(it)
+        return None
+
+    """
+    Empty self.contents by setting the include of each entry to None
+    """
+    def reset(self):
+        it = self.contents.get_iter_first()
+        while it:
+            path = self.contents.get_path(it)
+            opath = self.contents.convert_path_to_child_path(path)
+            self[opath][self.COL_INC] = False
+            self[opath][self.COL_BINB] = ""
+            # As we've just removed the first item...
+            it = self.contents.get_iter_first()
+
+    """
+    Returns True if one of the selected tasks is an image, False otherwise
+    """
+    def targets_contains_image(self):
+        it = self.images.get_iter_first()
+        while it:
+            path = self.images.get_path(it)
+            inc = self.images[path][self.COL_INC]
+            if inc:
+                return True
+            it = self.images.iter_next(it)
+        return False
+
+    """
+    Return a list of all selected items which are not -native or -cross
+    """
+    def get_targets(self):
+        tasks = []
+
+        it = self.contents.get_iter_first()
+        while it:
+            path = self.contents.get_path(it)
+            name = self.contents[path][self.COL_NAME]
+            stype = self.contents[path][self.COL_TYPE]
+            if not name.count('-native') and not name.count('-cross'):
+                tasks.append(name)
+            it = self.contents.iter_next(it)
+        return tasks
Signature · 1 line
1.7.4
#8 lore raw

PATCH 8/13 bitbake/event: fix some whitespace issues

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

---
 bitbake/lib/bb/event.py |    4 +---
 1 files changed, 1 insertions(+), 3 deletions(-)
bitbake/lib/bb/event.py +1 -3
index 176eeb8..b726b63 100644
@@ -356,7 +356,6 @@ class TargetsTreeGenerated(Event):
     """
     Event when a set of buildable targets has been generated
     """
-
     def __init__(self, model):
         Event.__init__(self)
         self._model = model
@@ -365,10 +364,9 @@ class ConfigFilesFound(Event):
     """
     Event when a list of appropriate config files has been generated
     """
-
     def __init__(self, variable, values):
         Event.__init__(self)
-        self._variable =  variable
+        self._variable = variable
         self._values = values
 
 class MsgBase(Event):
Signature · 1 line
1.7.4
#9 lore raw

PATCH 9/13 bitbake/crumbs: update documentation header

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

---
 bitbake/lib/bb/ui/crumbs/__init__.py |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
bitbake/lib/bb/ui/crumbs/__init__.py +1 -1
index a4805ed..b7cbe1a 100644
@@ -1,5 +1,5 @@
 #
-# BitBake UI Implementation
+# Gtk+ UI pieces for BitBake
 #
 # Copyright (C) 2006-2007 Richard Purdie
 #
Signature · 1 line
1.7.4
#10 lore raw

PATCH 10/13 bitbake/progress: add method to pulse the progress bar

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

When we're running a long operation with indeterminate duration it's useful to use the gtk.ProgressBar's pulse method to show that something is happening but we don't know how long it will take.

---
 bitbake/lib/bb/ui/crumbs/progress.py |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)
bitbake/lib/bb/ui/crumbs/progress.py +4 -0
index 36eca38..ce3a6c3 100644
@@ -15,3 +15,7 @@ class ProgressBar(gtk.Dialog):
     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()
Signature · 1 line
1.7.4
#11 lore raw

PATCH 11/13 bitbake/cooker: don't drop possible_world ref count

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

We need this if we want to run the buildWorldTargetList function more than once, for example in a UI where we can change the MACHINE and DISTRO as much as we like before triggering a build.

---
 bitbake/lib/bb/cooker.py |    4 ----
 1 files changed, 0 insertions(+), 4 deletions(-)
bitbake/lib/bb/cooker.py +0 -4
index 39cf011..1d84998 100644
@@ -555,10 +555,6 @@ class BBCooker:
             if terminal:
                 self.status.world_target.add(pn)
 
-            # drop reference count now
-            self.status.possible_world = None
-            self.status.all_depends    = None
-
     def interactiveMode( self ):
         """Drop off into a shell"""
         try:
Signature · 1 line
1.7.4
#12 lore raw

PATCH 12/13 bitbake: Add new UI hob, a prototype Gtk+ GUI for creating images

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

Hob is a first stab at implementing an interactive GUI for BitBake.

---
 bitbake/lib/bb/ui/crumbs/hobeventhandler.py |  138 ++++++
 bitbake/lib/bb/ui/hob.py                    |  596 +++++++++++++++++++++++++++
 2 files changed, 734 insertions(+), 0 deletions(-)
 create mode 100644 bitbake/lib/bb/ui/crumbs/hobeventhandler.py
 create mode 100644 bitbake/lib/bb/ui/hob.py
bitbake/lib/bb/ui/crumbs/hobeventhandler.py new file +138 -0
new file mode 100644
index 0000000..699354c
@@ -0,0 +1,138 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Joshua Lock <[email protected]>
+#
+# 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
+from bb.ui.crumbs.progress import ProgressBar
+
+progress_total = 0
+
+class HobHandler(gobject.GObject):
+
+    """
+    This object does BitBake event handling for the hob gui.
+    """
+    __gsignals__ = {
+         "machines-updated" : (gobject.SIGNAL_RUN_LAST,
+	                       gobject.TYPE_NONE,
+			       (gobject.TYPE_PYOBJECT,)),
+	 "distros-updated" : (gobject.SIGNAL_RUN_LAST,
+	 		      gobject.TYPE_NONE,
+			      (gobject.TYPE_PYOBJECT,)),
+         "generating-data" : (gobject.SIGNAL_RUN_LAST,
+                              gobject.TYPE_NONE,
+                              ()),
+         "data-generated" : (gobject.SIGNAL_RUN_LAST,
+                             gobject.TYPE_NONE,
+                             ())
+    }
+
+    def __init__(self, taskmodel, server):
+        gobject.GObject.__init__(self)
+
+        self.model = taskmodel
+        self.server = server
+        self.current_command = None
+        self.building = False
+
+        self.command_map = {
+            "findConfigFilesDistro" : ("findConfigFiles", "MACHINE", "findConfigFilesMachine"),
+            "findConfigFilesMachine" : ("generateTargetsTree", "classes/image.bbclass", None),
+            "generateTargetsTree"  : (None, None, None),
+            }
+
+    def run_next_command(self):
+        # FIXME: this is ugly and I *will* replace it
+        if self.current_command:
+            next_cmd = self.command_map[self.current_command]
+            command = next_cmd[0]
+            argument = next_cmd[1]
+            self.current_command = next_cmd[2]
+            if command == "generateTargetsTree":
+                self.emit("generating-data")
+            self.server.runCommand([command, argument])
+
+    def handle_event(self, event, running_build, pbar=None):
+        if not event:
+	    return
+
+        # If we're running a build, use the RunningBuild event handler
+        if self.building:
+            running_build.handle_event(event)
+        elif isinstance(event, bb.event.TargetsTreeGenerated):
+            self.emit("data-generated")
+            if event._model:
+                self.model.populate(event._model)
+                
+        elif isinstance(event, bb.event.ConfigFilesFound):
+            var = event._variable
+	    if var == "distro":
+		distros = event._values
+		distros.sort()
+		self.emit("distros-updated", distros)
+	    elif var == "machine":
+	        machines = event._values
+		machines.sort()
+		self.emit("machines-updated", machines)
+
+        elif isinstance(event, bb.command.CommandCompleted):
+            self.run_next_command()
+        elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
+            pbar.set_title("Loading cache")
+            bb.ui.crumbs.hobeventhandler.progress_total = event.total
+            pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total)
+        elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
+            pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total)
+        elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
+            pbar.update(bb.ui.crumbs.hobeventhandler.progress_total, bb.ui.crumbs.hobeventhandler.progress_total)
+        elif isinstance(event, bb.event.ParseStarted) and pbar:
+            pbar.set_title("Processing recipes")
+            bb.ui.crumbs.hobeventhandler.progress_total = event.total
+            pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total)
+        elif isinstance(event, bb.event.ParseProgress) and pbar:
+            pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total)
+        elif isinstance(event, bb.event.ParseCompleted) and pbar:
+            pbar.hide()
+            
+        return
+
+    def event_handle_idle_func (self, eventHandler, running_build, pbar):
+        # Consume as many messages as we can in the time available to us
+        event = eventHandler.getEvent()
+        while event:
+            self.handle_event(event, running_build, pbar)
+            event = eventHandler.getEvent()
+        return True
+
+    def set_machine(self, machine):
+        self.server.runCommand(["setVariable", "MACHINE", machine])
+        self.current_command = "findConfigFilesMachine"
+        self.emit("generating-data")
+        self.run_next_command()
+
+    def set_distro(self, distro):
+        self.server.runCommand(["setVariable", "DISTRO", distro])
+
+    def run_build(self, targets):
+        self.building = True
+        self.server.runCommand(["buildTargets", targets, "build"])
+
+    def cancel_build(self):
+        # Note: this may not be the right way to stop an in-progress build
+        self.server.runCommand(["stateStop"])
bitbake/lib/bb/ui/hob.py new file +596 -0

596 lines not on this page · raw message

Signature · 1 line
1.7.4
#13 lore raw

PATCH 13/13 bitbake/progress: make progress dialog modal for parent window

In reply to Joshua Lock

From: Joshua Lock <[email protected]>

---
 bitbake/lib/bb/ui/crumbs/progress.py |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
bitbake/lib/bb/ui/crumbs/progress.py +1 -2
index ce3a6c3..0c7ad96 100644
@@ -3,11 +3,10 @@ import gtk
 class ProgressBar(gtk.Dialog):
     def __init__(self, parent):
 
-        gtk.Dialog.__init__(self)
+        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.set_destroy_with_parent(True)
         self.progress = gtk.ProgressBar()
         self.vbox.pack_start(self.progress)
         self.show_all()
Signature · 1 line
1.7.4