mirror of
https://git.yoctoproject.org/poky
synced 2026-04-27 21:32:13 +02:00
The mark() method, which removes dependent and rdependent items, is overly aggressive removing items which are actually required by user selected items and then causing a removal of those items. Because the data structures used are not fine grained enough to do more intelligent dependency tracking the simplest "fix" is to track removals which are marked as "User Selected" and re-add those (and therefore their dependencies) once the aggressive removal is completed. Because the aggressive removal already ignores images and tasks this should make the removal behave as expected though certainly leaves area for improvement in future. Fixes [YOCTO #1280]. (Bitbake rev: 1e1055262450de994202fc3e5943b8b19f628681) Signed-off-by: Joshua Lock <josh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
596 lines
21 KiB
Python
596 lines
21 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):
|
|
template = """
|
|
# Recipe generated by the HOB
|
|
|
|
require %s
|
|
|
|
IMAGE_INSTALL += "%s"
|
|
"""
|
|
|
|
empty_template = """
|
|
# Recipe generated by the HOB
|
|
|
|
inherit core-image
|
|
|
|
IMAGE_INSTALL = "%s"
|
|
"""
|
|
if self.base_image and not self.base_image == "empty":
|
|
meta_path = model.find_image_path(self.base_image)
|
|
recipe = template % (meta_path, self.userpkgs)
|
|
else:
|
|
recipe = empty_template % self.allpkgs
|
|
|
|
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:
|
|
name = model.get_value(it, self.COL_NAME)
|
|
if name.count('-native') or name.count('cross'):
|
|
return False
|
|
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.userpkgs.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.
|
|
NOTE: This method dumbly removes user selected packages and since we don't
|
|
do significant reverse dependency tracking it's easier and simpler to save
|
|
the items marked as user selected and re-add them once the removal sweep is
|
|
complete.
|
|
"""
|
|
def mark(self, opath):
|
|
usersel = {}
|
|
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)
|
|
it = self.iter_next(it)
|
|
|
|
inc = self[path][self.COL_INC]
|
|
deps = self[path][self.COL_DEPS]
|
|
binb = self[path][self.COL_BINB]
|
|
itype = self[path][self.COL_TYPE]
|
|
iname = self[path][self.COL_NAME]
|
|
|
|
# We ignore anything that isn't a package
|
|
if not itype == "package":
|
|
continue
|
|
|
|
# If the user added this item and it's not the item we're removing
|
|
# we should keep it and its dependencies, the easiest way to do so
|
|
# is to save its name and re-mark it for inclusion once dependency
|
|
# processing is complete
|
|
if binb == "User Selected":
|
|
usersel[iname] = self[path][self.COL_IMG]
|
|
|
|
# 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
|
|
|
|
# Re-add any removed user selected items
|
|
for u in usersel:
|
|
npath = self.find_path_for_item(u)
|
|
self.include_item(item_path=npath,
|
|
binb="User Selected",
|
|
image_contents=usersel[u])
|
|
"""
|
|
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):
|
|
it = self.contents.get_iter_first()
|
|
while it:
|
|
binb = self.contents.get_value(it, self.COL_BINB)
|
|
itype = self.contents.get_value(it, self.COL_TYPE)
|
|
remove = False
|
|
|
|
if itype == 'package' and not binb:
|
|
oit = self.contents.convert_iter_to_child_iter(it)
|
|
opath = self.get_path(oit)
|
|
self.mark(opath)
|
|
remove = True
|
|
|
|
# When we remove a package from the contents model we alter the
|
|
# model, so continuing to iterate is bad. *Furthermore* it's
|
|
# likely that the removal has affected an already iterated item
|
|
# so we should start from the beginning anyway.
|
|
# Only when we've managed to iterate the entire contents model
|
|
# without removing any items do we allow the loop to exit.
|
|
if remove:
|
|
it = self.contents.get_iter_first()
|
|
else:
|
|
it = self.contents.iter_next(it)
|
|
|
|
"""
|
|
Find the name of an item in the image contents which depends on the item
|
|
name.
|
|
Returns either an item name (str) or None
|
|
"""
|
|
def find_alt_dependency(self, name):
|
|
it = self.contents.get_iter_first()
|
|
while it:
|
|
# iterate all items in the contents model
|
|
path = self.contents.get_path(it)
|
|
deps = self.contents[path][self.COL_DEPS]
|
|
itname = self.contents[path][self.COL_NAME]
|
|
inc = self.contents[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.contents.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
|
|
dep_included = self.contents_includes_name(dep)
|
|
path = self.find_path_for_item(dep)
|
|
if not dep_included and not dep.endswith("-native") and not dep.endswith("-cross"):
|
|
if path:
|
|
self.include_item(path, name, image_contents)
|
|
else:
|
|
pass
|
|
# Set brought in by for any no longer orphan packages
|
|
elif dep_included and path:
|
|
if not self[path][self.COL_BINB]:
|
|
self[path][self.COL_BINB] = name
|
|
|
|
"""
|
|
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):
|
|
# Deselect images - slightly more complex logic so that we don't
|
|
# have to iterate all of the contents of the main model, instead
|
|
# just iterate the images model.
|
|
if self.selected_image:
|
|
iit = self.images.get_iter_first()
|
|
while iit:
|
|
pit = self.images.convert_iter_to_child_iter(iit)
|
|
self.set(pit, self.COL_INC, False)
|
|
iit = self.images.iter_next(iit)
|
|
self.selected_image = None
|
|
|
|
it = self.contents.get_iter_first()
|
|
while it:
|
|
oit = self.contents.convert_iter_to_child_iter(it)
|
|
self.set(oit,
|
|
self.COL_INC, False,
|
|
self.COL_BINB, "",
|
|
self.COL_IMG, False)
|
|
# 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 image_contents_removed(self):
|
|
it = self.get_iter_first()
|
|
while it:
|
|
sel = self.get_value(it, self.COL_INC)
|
|
img = self.get_value(it, self.COL_IMG)
|
|
if img and not sel:
|
|
return True
|
|
it = self.iter_next(it)
|
|
return False
|
|
|
|
def get_build_rep(self):
|
|
userpkgs, allpkgs = self.get_selected_packages()
|
|
# If base image contents have been removed start from an empty rootfs
|
|
if not self.selected_image or self.image_contents_removed():
|
|
image = "empty"
|
|
else:
|
|
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:
|
|
name = self.contents.get_value(it, self.COL_NAME)
|
|
itype = self.contents.get_value(it, self.COL_TYPE)
|
|
deps = self.contents.get_value(it, self.COL_DEPS)
|
|
|
|
it = self.contents.iter_next(it)
|
|
|
|
if not itype == 'package':
|
|
continue
|
|
|
|
if deps.count(pn) != 0:
|
|
revdeps.append(name)
|
|
|
|
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)
|