mirror of
https://git.yoctoproject.org/poky
synced 2026-06-11 19:53:48 +02:00
Highlights include: * Atempted GNOME HIG compliance * Simplified UI and interaction model * Sorting and type to find in tree views * Preferences dialog to modify local settings * Dialog to add and remove layers * Search in packages list * Save/Load image recipes The build model has been changed, hob will attempt to build all dependent packages of an image and then use the buildFile server method to build the created image. (Bitbake rev: 48e64acaae4a741b9f5630f426fb4e6142755c2c) Signed-off-by: Joshua Lock <josh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
517 lines
17 KiB
Python
517 lines
17 KiB
Python
#
|
|
# BitBake Graphical GTK User Interface
|
|
#
|
|
# Copyright (C) 2011 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 gtk
|
|
import gobject
|
|
import re
|
|
|
|
class BuildRep(gobject.GObject):
|
|
|
|
def __init__(self, userpkgs, allpkgs, base_image=None):
|
|
gobject.GObject.__init__(self)
|
|
self.base_image = base_image
|
|
self.allpkgs = allpkgs
|
|
self.userpkgs = userpkgs
|
|
|
|
def loadRecipe(self, pathname):
|
|
contents = []
|
|
packages = ""
|
|
base_image = ""
|
|
|
|
with open(pathname, 'r') as f:
|
|
contents = f.readlines()
|
|
|
|
pkg_pattern = "^\s*(IMAGE_INSTALL)\s*([+=.?]+)\s*(\"\S*\")"
|
|
img_pattern = "^\s*(require)\s+(\S+.bb)"
|
|
|
|
for line in contents:
|
|
matchpkg = re.search(pkg_pattern, line)
|
|
matchimg = re.search(img_pattern, line)
|
|
if matchpkg:
|
|
packages = packages + matchpkg.group(3).strip('"')
|
|
if matchimg:
|
|
base_image = os.path.basename(matchimg.group(2)).split(".")[0]
|
|
|
|
self.base_image = base_image
|
|
self.userpkgs = packages
|
|
|
|
def writeRecipe(self, writepath, model):
|
|
# FIXME: Need a better way to determine meta_path...
|
|
template = """
|
|
# Recipe generated by the HOB
|
|
|
|
require %s.bb
|
|
|
|
IMAGE_INSTALL += "%s"
|
|
"""
|
|
meta_path = model.find_image_path(self.base_image)
|
|
|
|
recipe = template % (meta_path, self.userpkgs)
|
|
|
|
if os.path.exists(writepath):
|
|
os.rename(writepath, "%s~" % writepath)
|
|
|
|
with open(writepath, 'w') as r:
|
|
r.write(recipe)
|
|
|
|
return writepath
|
|
|
|
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, COL_IMG, COL_PATH) = range(10)
|
|
|
|
__gsignals__ = {
|
|
"tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
()),
|
|
"contents-changed" : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
(gobject.TYPE_INT,)),
|
|
"image-changed" : (gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
(gobject.TYPE_STRING,)),
|
|
}
|
|
|
|
"""
|
|
"""
|
|
def __init__(self):
|
|
self.contents = None
|
|
self.tasks = None
|
|
self.packages = None
|
|
self.images = None
|
|
self.selected_image = 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,
|
|
gobject.TYPE_BOOLEAN,
|
|
gobject.TYPE_STRING)
|
|
|
|
def contents_changed_cb(self, tree_model, path, it=None):
|
|
pkg_cnt = self.contents.iter_n_children(None)
|
|
self.emit("contents-changed", pkg_cnt)
|
|
|
|
def contents_model_filter(self, model, it):
|
|
if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image':
|
|
return False
|
|
name = model.get_value(it, self.COL_NAME)
|
|
if name.endswith('-native') or name.endswith('-cross'):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
"""
|
|
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_func(self.contents_model_filter)
|
|
self.contents.connect("row-inserted", self.contents_changed_cb)
|
|
self.contents.connect("row-deleted", self.contents_changed_cb)
|
|
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 False
|
|
else:
|
|
return True
|
|
|
|
"""
|
|
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):
|
|
# First clear the model, in case repopulating
|
|
self.clear()
|
|
for item in event_model["pn"]:
|
|
atype = 'package'
|
|
name = item
|
|
summary = event_model["pn"][item]["summary"]
|
|
lic = event_model["pn"][item]["license"]
|
|
group = event_model["pn"][item]["section"]
|
|
filename = event_model["pn"][item]["filename"]
|
|
depends = event_model["depends"].get(item, "")
|
|
rdepends = event_model["rdepends-pn"].get(item, "")
|
|
if rdepends:
|
|
for rdep in rdepends:
|
|
if event_model["packages"].get(rdep, ""):
|
|
pn = event_model["packages"][rdep].get("pn", "")
|
|
if pn:
|
|
depends.append(pn)
|
|
|
|
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, lic, self.COL_GROUP, group,
|
|
self.COL_DEPS, deps, self.COL_BINB, "",
|
|
self.COL_TYPE, atype, self.COL_INC, False,
|
|
self.COL_IMG, False, self.COL_PATH, filename)
|
|
|
|
self.emit("tasklist-populated")
|
|
|
|
"""
|
|
Load a BuildRep into the model
|
|
"""
|
|
def load_image_rep(self, rep):
|
|
# Unset everything
|
|
it = self.get_iter_first()
|
|
while it:
|
|
path = self.get_path(it)
|
|
self[path][self.COL_INC] = False
|
|
self[path][self.COL_IMG] = False
|
|
it = self.iter_next(it)
|
|
|
|
# Iterate the images and disable them all
|
|
it = self.images.get_iter_first()
|
|
while it:
|
|
path = self.images.convert_path_to_child_path(self.images.get_path(it))
|
|
name = self[path][self.COL_NAME]
|
|
if name == rep.base_image:
|
|
self.include_item(path, image_contents=True)
|
|
else:
|
|
self[path][self.COL_INC] = False
|
|
it = self.images.iter_next(it)
|
|
|
|
# Mark all of the additional packages for inclusion
|
|
packages = rep.packages.split(" ")
|
|
it = self.get_iter_first()
|
|
while it:
|
|
path = self.get_path(it)
|
|
name = self[path][self.COL_NAME]
|
|
if name in packages:
|
|
self.include_item(path)
|
|
packages.remove(name)
|
|
it = self.iter_next(it)
|
|
|
|
self.emit("image-changed", rep.base_image)
|
|
|
|
"""
|
|
squish lst so that it doesn't contain any duplicate entries
|
|
"""
|
|
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
|
|
|
|
"""
|
|
recursively called to mark the item at opath and any package which
|
|
depends on it for removal
|
|
"""
|
|
def mark(self, opath):
|
|
removals = []
|
|
it = self.get_iter_first()
|
|
name = self[opath][self.COL_NAME]
|
|
|
|
self.remove_item_path(opath)
|
|
|
|
# Remove all dependent packages, update binb
|
|
while it:
|
|
path = self.get_path(it)
|
|
inc = self[path][self.COL_INC]
|
|
deps = self[path][self.COL_DEPS]
|
|
binb = self[path][self.COL_BINB]
|
|
|
|
# FIXME: need to ensure partial name matching doesn't happen
|
|
if inc and deps.count(name):
|
|
# found a dependency, remove it
|
|
self.mark(path)
|
|
if inc and binb.count(name):
|
|
bib = self.find_alt_dependency(name)
|
|
self[path][self.COL_BINB] = bib
|
|
|
|
it = self.iter_next(it)
|
|
|
|
"""
|
|
Remove items from contents if the have an empty COL_BINB (brought in by)
|
|
caused by all packages they are a dependency of being removed.
|
|
If the item isn't a package we leave it included.
|
|
"""
|
|
def sweep_up(self):
|
|
model = self.contents
|
|
removals = []
|
|
it = self.contents.get_iter_first()
|
|
|
|
while it:
|
|
binb = model.get_value(it, self.COL_BINB)
|
|
itype = model.get_value(it, self.COL_TYPE)
|
|
|
|
if itype == 'package' and not binb:
|
|
opath = model.convert_path_to_child_path(model.get_path(it))
|
|
if not opath in removals:
|
|
removals.extend(opath)
|
|
|
|
it = model.iter_next(it)
|
|
|
|
while removals:
|
|
path = removals.pop()
|
|
self.mark(path)
|
|
|
|
"""
|
|
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
|
|
return itname
|
|
it = self.iter_next(it)
|
|
return ""
|
|
|
|
"""
|
|
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="", image_contents=False):
|
|
name = self[item_path][self.COL_NAME]
|
|
deps = self[item_path][self.COL_DEPS]
|
|
cur_inc = self[item_path][self.COL_INC]
|
|
if not cur_inc:
|
|
self[item_path][self.COL_INC] = True
|
|
self[item_path][self.COL_BINB] = binb
|
|
# We want to do some magic with things which are brought in by the base
|
|
# image so tag them as so
|
|
if image_contents:
|
|
self[item_path][self.COL_IMG] = True
|
|
if self[item_path][self.COL_TYPE] == 'image':
|
|
self.selected_image = name
|
|
|
|
if deps:
|
|
# add all of the deps and set their binb to this item
|
|
for dep in deps.split(" "):
|
|
# If the contents model doesn't already contain dep, add it
|
|
# We only care to show things which will end up in the
|
|
# resultant image, so filter cross and native recipes
|
|
if not self.contents_includes_name(dep) and not dep.endswith("-native") and not dep.endswith("-cross"):
|
|
path = self.find_path_for_item(dep)
|
|
if path:
|
|
self.include_item(path, name, image_contents)
|
|
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 two lists. One of user selected packages and the other containing
|
|
all selected packages
|
|
"""
|
|
def get_selected_packages(self):
|
|
allpkgs = []
|
|
userpkgs = []
|
|
|
|
it = self.contents.get_iter_first()
|
|
while it:
|
|
sel = self.contents.get_value(it, self.COL_BINB) == "User Selected"
|
|
name = self.contents.get_value(it, self.COL_NAME)
|
|
allpkgs.append(name)
|
|
if sel:
|
|
userpkgs.append(name)
|
|
it = self.contents.iter_next(it)
|
|
return userpkgs, allpkgs
|
|
|
|
def get_build_rep(self):
|
|
userpkgs, allpkgs = self.get_selected_packages()
|
|
image = self.selected_image
|
|
|
|
return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image)
|
|
|
|
def find_reverse_depends(self, pn):
|
|
revdeps = []
|
|
it = self.contents.get_iter_first()
|
|
|
|
while it:
|
|
if self.contents.get_value(it, self.COL_DEPS).count(pn) != 0:
|
|
revdeps.append(self.contents.get_value(it, self.COL_NAME))
|
|
it = self.contents.iter_next(it)
|
|
|
|
if pn in revdeps:
|
|
revdeps.remove(pn)
|
|
return revdeps
|
|
|
|
def set_selected_image(self, img):
|
|
self.selected_image = img
|
|
path = self.find_path_for_item(img)
|
|
self.include_item(item_path=path,
|
|
binb="User Selected",
|
|
image_contents=True)
|
|
|
|
self.emit("image-changed", self.selected_image)
|
|
|
|
def set_selected_packages(self, pkglist):
|
|
selected = pkglist
|
|
it = self.get_iter_first()
|
|
|
|
while it:
|
|
name = self.get_value(it, self.COL_NAME)
|
|
if name in pkglist:
|
|
pkglist.remove(name)
|
|
path = self.get_path(it)
|
|
self.include_item(item_path=path,
|
|
binb="User Selected")
|
|
if len(pkglist) == 0:
|
|
return
|
|
it = self.iter_next(it)
|
|
|
|
def find_image_path(self, image):
|
|
it = self.images.get_iter_first()
|
|
|
|
while it:
|
|
image_name = self.images.get_value(it, self.COL_NAME)
|
|
if image_name == image:
|
|
path = self.images.get_value(it, self.COL_PATH)
|
|
meta_pattern = "(\S*)/(meta*/)(\S*)"
|
|
meta_match = re.search(meta_pattern, path)
|
|
if meta_match:
|
|
_, lyr, bbrel = path.partition(meta_match.group(2))
|
|
if bbrel:
|
|
path = bbrel
|
|
return path
|
|
it = self.images.iter_next(it)
|