Prototype GUI Image Creator
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
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
@@ -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
PATCH 2/13 bitbake: implement command to get all possible targets and their dependencies
In reply to Joshua LockFrom: 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
@@ -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
@@ -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
@@ -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
PATCH 3/13 bitbake: implement command to find configuration files for a config variable
In reply to Joshua LockFrom: 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
@@ -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
@@ -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
@@ -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
PATCH 4/13 bitbake/cooker: add generateTargetsTree method
In reply to Joshua LockFrom: 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
@@ -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
PATCH 5/13 bitbake/cooker: reduce code duplication
In reply to Joshua LockFrom: 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
@@ -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
PATCH 6/13 bitbake/[cooker|cache]: cache summary, license and group. Add to targets tree
In reply to Joshua LockFrom: 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
@@ -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
@@ -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
PATCH 7/13 bitbake: introduce crumbs.TaskListModel a gtk.ListStore subclass
In reply to Joshua LockFrom: 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
@@ -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
PATCH 8/13 bitbake/event: fix some whitespace issues
In reply to Joshua LockFrom: Joshua Lock <[email protected]>
--- bitbake/lib/bb/event.py | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-)
bitbake/lib/bb/event.py +1 -3
@@ -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
PATCH 9/13 bitbake/crumbs: update documentation header
In reply to Joshua LockFrom: 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
@@ -1,5 +1,5 @@ # -# BitBake UI Implementation +# Gtk+ UI pieces for BitBake # # Copyright (C) 2006-2007 Richard Purdie #
Signature · 1 line
1.7.4
PATCH 10/13 bitbake/progress: add method to pulse the progress bar
In reply to Joshua LockFrom: 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
@@ -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
PATCH 11/13 bitbake/cooker: don't drop possible_world ref count
In reply to Joshua LockFrom: 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
@@ -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
PATCH 12/13 bitbake: Add new UI hob, a prototype Gtk+ GUI for creating images
In reply to Joshua LockFrom: 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
@@ -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
PATCH 13/13 bitbake/progress: make progress dialog modal for parent window
In reply to Joshua LockFrom: 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
@@ -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