Hi, Suppose we want to remove package A and all of its unused dependencies. As written, the plugin will only remove the dependencies of A which would become leaves after automatically installing A. In a situation like
A <-- B --> C --> D where "X --> Y" means "Y depends on X". where B, C, D were dep-installed, we clearly want to remove B, C, and D as well. But the plugin will not do this because B won't be a leaf after removing A. I have attached sample code for an alternative approach. Instead of removing only the dependencies which would become leaves after removing A, we remove all dependencies of A whose reverse dependencies, after removing A, would be all dep-installed. This approach would deal better with dependency relationships like shown above, and it should also fix the FIXME concerning circular dependencies. Please let me know what you think. In particular, the plugin runs more slowly now because it has to examine more packages. Attached files: remove-with-leaves.py (original) remove-with-leaves-working.py (my version) remove-with-leaves.diff Regards, Casey
# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2008 Red Hat, Inc # written by Seth Vidal <skvi...@fedoraproject.org> #FIXME: # When two requirements of a pkg being removed mutually require each other # there's no way to have one know about the other and have this know to remove both # ex: foo is being removed. it requires bar. bar requires baz. baz requires bar. # nothing other than foo and baz require bar. """ This plugin allows packages to clean up dependencies they pulled in which are not in use by any other package. """ from yum.plugins import TYPE_CORE from yum.constants import * requires_api_version = '2.4' plugin_type = (TYPE_CORE,) _requires_cache = {} ignore_list = ['glibc', 'bash', 'libgcc'] exclude_bin = False remove_always = False def _requires_this_package(rpmdb, pkg): if pkg in _requires_cache: return _requires_cache[pkg] requirers = {} for prov in pkg.provides: for req_pkg in rpmdb.getRequires(prov[0], prov[1], prov[2]): if req_pkg == pkg: continue requirers[req_pkg.pkgtup] = 1 # do filelists, too :( for prov in pkg.filelist + pkg.dirlist + pkg.ghostlist: for req_pkg in rpmdb.getRequires(prov): if req_pkg == pkg: continue requirers[req_pkg.pkgtup] = 1 _requires_cache[pkg] = requirers.keys() return requirers.keys() def postresolve_hook(conduit): global exclude_bin, remove_always opts, commands = conduit.getCmdLine() if opts and hasattr(opts,'exclude_bin'): if exclude_bin or opts.exclude_bin: exclude_bin = True if (opts and opts.remove_leaves) or remove_always: # get all the items in tsInfo = conduit.getTsInfo() rpmdb = conduit.getRpmDB() oldlen = 0 while oldlen != len(tsInfo): oldlen = len(tsInfo) for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]): if conduit._base.allowedMultipleInstalls(txmbr.po): # these make everything dodgy, skip it continue for req in txmbr.po.requires: if req[0].startswith('rpmlib('): continue for pkg in rpmdb.getProvides(req[0], req[1], req[2]): if pkg.pkgtup in [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ]: continue # skip ones already marked for remove, kinda pointless if pkg.name in ignore_list: # there are some pkgs which are NEVER going to be leafremovals continue # Skip manually installed packages. if pkg.yumdb_info.get('reason') == 'user': continue non_removed_requires = [] for req_pkgtup in _requires_this_package(rpmdb,pkg): pkgtups = [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ] if req_pkgtup not in pkgtups: non_removed_requires.append(req_pkgtup) if exclude_bin: # if this pkg is a binary of some kind, skip it is_bin=False for file_name in pkg.filelist: if file_name.find('bin') != -1: is_bin = True if is_bin: continue if not non_removed_requires: if hasattr(conduit, 'registerPackageName'): conduit.registerPackageName("yum-plugin-remove-with-leaves") conduit.info(2, 'removing %s. It is not required by anything else.' % pkg) conduit._base.remove(pkg) def config_hook(conduit): global exclude_bin, remove_always exclude_bin = conduit.confBool('main', 'exclude_bin', default=False) remove_always = conduit.confBool('main', 'remove_always', default=False) parser = conduit.getOptParser() if parser: if hasattr(parser, 'plugin_option_group'): parser = parser.plugin_option_group parser.add_option("", "--leaves-exclude-bin", dest="exclude_bin", action="store_true", default=False, help="do not remove leaf packages which contain executable binaries") parser.add_option('', '--remove-leaves', dest='remove_leaves', action='store_true', default=False, help="remove dependencies no longer needed by any other packages")
# This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2008 Red Hat, Inc # written by Seth Vidal <skvi...@fedoraproject.org> #FIXME: # When two requirements of a pkg being removed mutually require each other # there's no way to have one know about the other and have this know to remove both # ex: foo is being removed. it requires bar. bar requires baz. baz requires bar. # nothing other than foo and baz require bar. """ This plugin allows packages to clean up dependencies they pulled in which are not in use by any other package. """ from yum.plugins import TYPE_CORE from yum.constants import * requires_api_version = '2.4' plugin_type = (TYPE_CORE,) _requires_cache = {} ignore_list = ['glibc', 'bash', 'libgcc'] exclude_bin = False remove_always = False def _requires_this_package(rpmdb, pkg): if pkg in _requires_cache: return _requires_cache[pkg] requirers = {} for prov in pkg.provides: for req_pkg in rpmdb.getRequires(prov[0], prov[1], prov[2]): if req_pkg == pkg: continue #requirers[req_pkg.pkgtup] = 1 requirers[req_pkg] = 1 # do filelists, too :( for prov in pkg.filelist + pkg.dirlist + pkg.ghostlist: for req_pkg in rpmdb.getRequires(prov): if req_pkg == pkg: continue #requirers[req_pkg.pkgtup] = 1 requirers[req_pkg] = 1 _requires_cache[pkg] = requirers.keys() return requirers.keys() # Checks whether any (transitive) revdeps were user-installed def has_ui_revdeps(rpmdb, pkglist, root_pkg, tspkgs): print "Examining revdeps of", root_pkg # track which pkgs we have visited already visited = {} for pkg in pkglist: visited[pkg] = False for pkg in tspkgs: visited[pkg] = True stack = [] stack.append(root_pkg) # depth-first search while stack: curpkg = stack[-1] if not visited[curpkg]: if curpkg.yumdb_info.reason != 'dep' and curpkg not in tspkgs: print root_pkg, "has revdep", curpkg, "which was user-installed" return True visited[curpkg] = True all_leaves_visited = True leaves = _requires_this_package(rpmdb, curpkg) for leaf in leaves: if not visited[leaf]: stack.append(leaf) all_leaves_visited = False break if all_leaves_visited: stack.pop() print root_pkg, "has no user-installed revdeps" return False def postresolve_hook(conduit): global exclude_bin, remove_always opts, commands = conduit.getCmdLine() if opts and hasattr(opts,'exclude_bin'): if exclude_bin or opts.exclude_bin: exclude_bin = True if (opts and opts.remove_leaves) or remove_always: # get all the items in tsInfo = conduit.getTsInfo() rpmdb = conduit.getRpmDB() pkglist = rpmdb.returnPackages() oldlen = 0 while oldlen != len(tsInfo): oldlen = len(tsInfo) txpkgs = [txmbr.po for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE])]; for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]): if conduit._base.allowedMultipleInstalls(txmbr.po): # these make everything dodgy, skip it continue for req in txmbr.po.requires: if req[0].startswith('rpmlib('): continue for pkg in rpmdb.getProvides(req[0], req[1], req[2]): if pkg.pkgtup in [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ]: continue # skip ones already marked for remove, kinda pointless if pkg.name in ignore_list: # there are some pkgs which are NEVER going to be leafremovals continue # Skip manually installed packages. if pkg.yumdb_info.get('reason') == 'user': continue #non_removed_requires = [] #for req_pkgtup in _requires_this_package(rpmdb,pkg): # pkgtups = [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ] # if req_pkgtup not in pkgtups: # non_removed_requires.append(req_pkgtup) if exclude_bin: # if this pkg is a binary of some kind, skip it is_bin=False for file_name in pkg.filelist: if file_name.find('bin') != -1: is_bin = True if is_bin: continue #if not non_removed_requires: if not has_ui_revdeps(rpmdb, pkglist, pkg, txpkgs): if hasattr(conduit, 'registerPackageName'): conduit.registerPackageName("yum-plugin-remove-with-leaves") conduit.info(2, 'removing %s. It is not required by anything else.' % pkg) conduit._base.remove(pkg) def config_hook(conduit): global exclude_bin, remove_always exclude_bin = conduit.confBool('main', 'exclude_bin', default=False) remove_always = conduit.confBool('main', 'remove_always', default=False) parser = conduit.getOptParser() if parser: if hasattr(parser, 'plugin_option_group'): parser = parser.plugin_option_group parser.add_option("", "--leaves-exclude-bin", dest="exclude_bin", action="store_true", default=False, help="do not remove leaf packages which contain executable binaries") parser.add_option('', '--remove-leaves', dest='remove_leaves', action='store_true', default=False, help="remove dependencies no longer needed by any other packages")
--- remove-with-leaves.py 2011-03-20 10:29:37.120338093 -0700 +++ remove-with-leaves-working.py 2011-03-20 15:59:36.837700528 -0700 @@ -40,7 +40,6 @@ exclude_bin = False remove_always = False - def _requires_this_package(rpmdb, pkg): if pkg in _requires_cache: return _requires_cache[pkg] @@ -50,17 +49,50 @@ for req_pkg in rpmdb.getRequires(prov[0], prov[1], prov[2]): if req_pkg == pkg: continue - requirers[req_pkg.pkgtup] = 1 + #requirers[req_pkg.pkgtup] = 1 + requirers[req_pkg] = 1 # do filelists, too :( for prov in pkg.filelist + pkg.dirlist + pkg.ghostlist: for req_pkg in rpmdb.getRequires(prov): if req_pkg == pkg: continue - requirers[req_pkg.pkgtup] = 1 + #requirers[req_pkg.pkgtup] = 1 + requirers[req_pkg] = 1 _requires_cache[pkg] = requirers.keys() return requirers.keys() +# Checks whether any (transitive) revdeps were user-installed +def has_ui_revdeps(rpmdb, pkglist, root_pkg, tspkgs): + print "Examining revdeps of", root_pkg + # track which pkgs we have visited already + visited = {} + for pkg in pkglist: + visited[pkg] = False + for pkg in tspkgs: + visited[pkg] = True + stack = [] + stack.append(root_pkg) + # depth-first search + while stack: + curpkg = stack[-1] + if not visited[curpkg]: + if curpkg.yumdb_info.reason != 'dep' and curpkg not in tspkgs: + print root_pkg, "has revdep", curpkg, "which was user-installed" + return True + visited[curpkg] = True + all_leaves_visited = True + leaves = _requires_this_package(rpmdb, curpkg) + for leaf in leaves: + if not visited[leaf]: + stack.append(leaf) + all_leaves_visited = False + break + if all_leaves_visited: + stack.pop() + print root_pkg, "has no user-installed revdeps" + return False + def postresolve_hook(conduit): global exclude_bin, remove_always @@ -73,9 +105,11 @@ # get all the items in tsInfo = conduit.getTsInfo() rpmdb = conduit.getRpmDB() + pkglist = rpmdb.returnPackages() oldlen = 0 while oldlen != len(tsInfo): oldlen = len(tsInfo) + txpkgs = [txmbr.po for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE])]; for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]): if conduit._base.allowedMultipleInstalls(txmbr.po): # these make everything dodgy, skip it @@ -93,11 +127,11 @@ if pkg.yumdb_info.get('reason') == 'user': continue - non_removed_requires = [] - for req_pkgtup in _requires_this_package(rpmdb,pkg): - pkgtups = [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ] - if req_pkgtup not in pkgtups: - non_removed_requires.append(req_pkgtup) + #non_removed_requires = [] + #for req_pkgtup in _requires_this_package(rpmdb,pkg): + # pkgtups = [ txmbr.po.pkgtup for txmbr in tsInfo.getMembersWithState(output_states=[TS_ERASE]) ] + # if req_pkgtup not in pkgtups: + # non_removed_requires.append(req_pkgtup) if exclude_bin: # if this pkg is a binary of some kind, skip it is_bin=False for file_name in pkg.filelist: @@ -105,8 +139,9 @@ is_bin = True if is_bin: continue - - if not non_removed_requires: + + #if not non_removed_requires: + if not has_ui_revdeps(rpmdb, pkglist, pkg, txpkgs): if hasattr(conduit, 'registerPackageName'): conduit.registerPackageName("yum-plugin-remove-with-leaves") conduit.info(2, 'removing %s. It is not required by anything else.' % pkg)
signature.asc
Description: OpenPGP digital signature
_______________________________________________ Yum-devel mailing list Yum-devel@lists.baseurl.org http://lists.baseurl.org/mailman/listinfo/yum-devel