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)

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
Yum-devel mailing list
Yum-devel@lists.baseurl.org
http://lists.baseurl.org/mailman/listinfo/yum-devel

Reply via email to