[patch] auto dependency removal

Michael Vogt mvo at ubuntu.com
Wed Jun 28 13:54:03 PDT 2006


Hi,

attached is a patch that adds basic support for automatic removal of
unused dependencies. The patch is based on code from Gustavo (thanks!)
and uses the techniques pioneered by Daniel Burrows in aptitude, all
the bugs are added by me.

I would appreciate feedback on it! 

To use it just install/remove stuff as usual. You can see the packages
that smart auto-installed (to satisfy dependencies) with: 
$ smart flag --show auto

To get suggestions about packages to remove type:
$ smart remove --auto

But make sure to double (or triple) check those suggestions for
now. The code is very young. The no-longer-needed packages are
calculated with a simple mark-and-sweep algorithm that takes all the
manually installed (ie. !auto) packages as the root of the set and
follows the dependencies to mark packages. 


Cheers,
 Michael
-- 
Linux is not The Answer. Yes is the answer. Linux is The Question. - Neo
-------------- next part --------------
Index: smart/commands/remove.py
===================================================================
--- smart/commands/remove.py	(revision 734)
+++ smart/commands/remove.py	(working copy)
@@ -56,6 +56,9 @@
                              "when possible"))
     parser.add_option("-y", "--yes", action="store_true",
                       help=_("do not ask for confirmation"))
+    parser.add_option("--auto", action="store_true",
+                      help=_("use the auto-install information to "
+                             "remove stuff."))
     opts, args = parser.parse_args(argv)
     opts.args = args
     return opts
@@ -69,6 +72,12 @@
     cache = ctrl.getCache()
     trans = Transaction(cache, PolicyRemove)
     policy = trans.getPolicy()
+
+    if opts.auto:
+        rmcs = ctrl.markAndSweep()
+        confirm = not opts.yes
+        ctrl.commitChangeSet(rmcs, confirm=confirm)
+    
     for arg in opts.args:
 
         ratio, results, suggestions = ctrl.search(arg)
Index: smart/control.py
===================================================================
--- smart/control.py	(revision 734)
+++ smart/control.py	(working copy)
@@ -396,6 +396,39 @@
         self._pathlocks.lock(channelsdir)
         return result
 
+    def markAndSweep(self, changeset=None):
+        if changeset==None:
+            changeset={}
+        # Mark ...
+        all = self._cache.getPackages()
+        auto = pkgconf.filterByFlag("auto", all)
+        marked = {}
+        for pkg in all:
+            if (pkg.installed and pkg not in auto and
+                changeset.get(pkg) != REMOVE):
+                marked[pkg] = True
+        queue = marked.keys()
+        while queue:
+            pkg = queue.pop(0)
+            for req in pkg.requires:
+                for prv in req.providedby:
+                    for prvpkg in prv.packages:
+                        if (prvpkg.installed and
+                            prvpkg not in marked and
+                            prvpkg not in changeset):
+                            marked[prvpkg] = True
+                            queue.append(prvpkg)
+        # see what is not ma
+        suggestions = ChangeSet(self._cache)
+        for pkg in all:
+            if pkg.installed and pkg not in marked:
+                suggestions[pkg] = REMOVE
+        #if suggestions:
+        #    accepted = iface.suggestChanges(suggestions)
+        #    if accepted:
+        #        changeset.update(accepted)
+        return suggestions
+
     def dumpTransactionURLs(self, trans, output=None):
         changeset = trans.getChangeSet()
         self.dumpURLs([x for x in changeset if changeset[x] is INSTALL])
@@ -514,7 +547,9 @@
                     for pkg in pmpkgs[pmclass]:
                         if pkg in cs:
                             pmcs[pkg] = cs[pkg]
+                            pmcs.setRequested(pkg, cs.getRequested(pkg))
                     if sysconf.get("commit", True):
+                        pmcs.markPackagesAutoInstalled()
                         pmclass().commit(pmcs, pkgpaths)
 
                 if sysconf.get("remove-packages", True):
Index: smart/transaction.py
===================================================================
--- smart/transaction.py	(revision 734)
+++ smart/transaction.py	(working copy)
@@ -25,16 +25,31 @@
 
 class ChangeSet(dict):
 
-    def __init__(self, cache, state=None):
+    def __init__(self, cache, state=None, requested=None):
         self._cache = cache
+        self._requested = {}
         if state:
             self.update(state)
+        if requested:
+            self._requested.update(requested)
 
+    def clear(self):
+        dict.clear(self)
+        self._requested.clear()
+
+    def update(self, other):
+        dict.update(self, other)
+        if type(other) is ChangeSet:
+            self._requested.update(other._requested)
+
+    def copy(self):
+        return ChangeSet(self._cache, self, self._requested)
+
     def getCache(self):
         return self._cache
 
     def getState(self):
-        return self.copy()
+        return (self.copy(), self._requested.copy())
 
     def setState(self, state):
         if state is not self:
@@ -44,19 +59,44 @@
     def getPersistentState(self):
         state = {}
         for pkg in self:
-            state[(pkg.__class__, pkg.name, pkg.version)] = self[pkg]
+            req = pkg in self._requested
+            state[(pkg.__class__, pkg.name, pkg.version)] = self[pkg], req
         return state
 
     def setPersistentState(self, state):
         self.clear()
         for pkg in self._cache.getPackages():
-            op = state.get((pkg.__class__, pkg.name, pkg.version))
-            if op is not None:
+            tup = state.get((pkg.__class__, pkg.name, pkg.version))
+            if tup is not None:
+                op, req = tup
                 self[pkg] = op
+                if req:
+                    self._requested[pkg] = True
 
-    def copy(self):
-        return ChangeSet(self._cache, self)
+    def markPackagesAutoInstalled(self):
+        """ mark all packages that have been auto-installed permanently
+            and reset the flag if the package was removed again
+        """
+        for pkg in self:
+            if (self[pkg] is INSTALL and
+                not self.getRequested(pkg)):
+                pkgconf.setFlag("auto",
+                                pkg.name, "=", pkg.version)
+            elif self[pkg] is REMOVE and pkgconf.testFlag("auto", pkg):
+                pkgconf.clearFlag("auto")
+            # FIXME: we probably want to update the version of a
+            #        autoinstalled package on UPGRADE
 
+    def getRequested(self, pkg):
+        return pkg in self._requested
+
+    def setRequested(self, pkg, flag):
+        assert pkg in self
+        if flag:
+            self._requested[pkg] = True
+        elif pkg in self._requested:
+            del self._requested[pkg]
+
     def set(self, pkg, op, force=False):
         if self.get(pkg) is op:
             return
@@ -66,12 +106,16 @@
             else:
                 if pkg in self:
                     del self[pkg]
+                    if pkg in self._requested:
+                        del self._requested[pkg]
         else:
             if force or pkg.installed:
                 self[pkg] = REMOVE
             else:
                 if pkg in self:
                     del self[pkg]
+                    if pkg in self._requested:
+                        del self._requested[pkg]
 
     def installed(self, pkg):
         op = self.get(pkg)
@@ -83,6 +127,8 @@
             sop = self[pkg]
             if sop is not other.get(pkg):
                 diff[pkg] = sop
+                if pkg in self._requested:
+                    diff._requested[pkg] = True
         return diff
 
     def intersect(self, other):
@@ -91,6 +137,8 @@
             sop = self[pkg]
             if sop is other.get(pkg):
                 isct[pkg] = sop
+                if pkg in self._requested:
+                    isct._requested[pkg] = True
         return isct
 
     def __str__(self):
@@ -1105,6 +1153,8 @@
                         op = REMOVE
                 if op is INSTALL or op is REINSTALL:
                     self._install(pkg, changeset, locked, pending)
+                    if pkg in changeset:
+                        changeset.setRequested(pkg, True)
                 elif op is REMOVE:
                     self._remove(pkg, changeset, locked, pending)
                 elif op is UPGRADE:


More information about the Smart mailing list