Handle dependencies when removing a package.
authorDanek Duvall <danek.duvall@sun.com>
Mon, 06 Aug 2007 14:53:13 -0700
changeset 67 62c897652bbc
parent 66 e307f4f837c3
child 68 b0128a34d464
Handle dependencies when removing a package.
src/client.py
src/modules/actions/depend.py
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/client/pkgplan.py
--- a/src/client.py	Fri Aug 03 11:46:58 2007 -0700
+++ b/src/client.py	Mon Aug 06 14:53:13 2007 -0700
@@ -74,7 +74,7 @@
         pkg catalog [--verbose] pkg_fmri_pattern
         pkg status [-uv] [pkg_fmri_pattern ...]
         pkg install [-nv] pkg_fmri
-        pkg uninstall [-nv] pkg_fmri
+        pkg uninstall [-nrv] pkg_fmri
         pkg freeze [--version version_spec] [--release] [--branch] pkg_fmri
         pkg unfreeze pkg_fmri
         pkg search token
@@ -205,18 +205,20 @@
         """Attempt to take package specified to DELETED state."""
 
         if len(args) > 0:
-                opts, pargs = getopt.getopt(args, "nv")
+                opts, pargs = getopt.getopt(args, "nrv")
 
-        noexecute = verbose = False
+        noexecute = recursive_removal = verbose = False
         for opt, arg in opts:
                 if opt == "-n":
                         noexecute = True
+                elif opt == "-r":
+                        recursive_removal = True
                 elif opt == "-v":
                         verbose = True
 
         image.reload_catalogs() # XXX ???
 
-        ip = imageplan.ImagePlan(image)
+        ip = imageplan.ImagePlan(image, recursive_removal)
 
         for ppat in pargs:
                 rpat = re.sub("\*", ".*", ppat)
@@ -369,7 +371,10 @@
         elif subcommand == "install":
                 install(pcfg, icfg, pargs)
         elif subcommand == "uninstall":
-                uninstall(pcfg, icfg, pargs)
+                try:
+                        uninstall(pcfg, icfg, pargs)
+                except KeyboardInterrupt:
+                        pass
         elif subcommand == "freeze":
                 freeze(pcfg, icfg, pargs)
         elif subcommand == "unfreeze":
--- a/src/modules/actions/depend.py	Fri Aug 03 11:46:58 2007 -0700
+++ b/src/modules/actions/depend.py	Mon Aug 06 14:53:13 2007 -0700
@@ -31,6 +31,7 @@
 relationship between the package containing the action and another package.
 """
 
+import urllib
 import generic
 
 class DependencyAction(generic.Action):
@@ -49,3 +50,27 @@
 
         def __init__(self, data=None, **attrs):
                 generic.Action.__init__(self, data, **attrs)
+
+        def generate_indices(self):
+                # XXX Probably need to do something for other types, too.
+                if self.attrs["type"] != "require":
+                        return {}
+
+                fmri = self.attrs["fmri"]
+
+                # XXX Ideally, we'd turn the string into a PkgFmri, and separate
+                # the stem from the version, or use get_dir_path, but we can't
+                # create a PkgFmri without supplying a build release and without
+                # it creating a dummy timestamp.  So we have to split it apart
+                # manually.
+                #
+                # XXX This code will need to change once we start using fmris
+                # with authorities.
+                if fmri.startswith("pkg:/"):
+                        fmri = fmri[5:]
+                # Note that this creates a directory hierarchy!
+                fmri = urllib.quote(fmri, "@").replace("@", "/")
+
+                return {
+                    "depend": fmri
+                }
--- a/src/modules/client/image.py	Fri Aug 03 11:46:58 2007 -0700
+++ b/src/modules/client/image.py	Mon Aug 06 14:53:13 2007 -0700
@@ -222,6 +222,26 @@
 
                 return False
 
+        def get_dependents(self, pfmri):
+                """Return a list of the packages directly dependent on the given FMRI."""
+
+                thedir = os.path.join(self.imgdir, "index", "depend",
+                    urllib.quote(str(pfmri.get_pkg_stem())[5:], ""))
+
+                if not os.path.isdir(thedir):
+                        return []
+
+                for v in os.listdir(thedir):
+                        f = fmri.PkgFmri(pfmri.get_pkg_stem() + "@" + v,
+                            self.attrs["Build-Release"])
+                        if pfmri.is_successor(f):
+                                dependents = [
+                                    urllib.unquote(d)
+                                    for d in os.listdir(os.path.join(thedir, v))
+                                ]
+
+                return dependents
+
         def reload_catalogs(self):
                 cdir = "%s/%s" % (self.imgdir, "catalog")
                 for cf in os.listdir(cdir):
--- a/src/modules/client/imageplan.py	Fri Aug 03 11:46:58 2007 -0700
+++ b/src/modules/client/imageplan.py	Mon Aug 06 14:53:13 2007 -0700
@@ -60,9 +60,10 @@
         "pkg delete fmri; pkg install fmri@v(n - 1)", then we'd better have a
         plan to identify when this operation is safe or unsafe."""
 
-        def __init__(self, image):
+        def __init__(self, image, recursive_removal = False):
                 self.image = image
                 self.state = UNEVALUATED
+                self.recursive_removal = recursive_removal
 
                 self.target_fmris = []
                 self.target_rem_fmris = []
@@ -91,6 +92,17 @@
                                         return False
                 return False
 
+        def is_proposed_rem_fmri(self, fmri):
+                for pf in self.target_rem_fmris:
+                        if fmri.is_same_pkg(pf):
+                                return True
+                                # XXX is this the right test?
+                                if not fmri.is_successor(pf):
+                                        return True
+                                else:
+                                        return False
+                return False
+
         def propose_fmri(self, fmri):
                 # is a version of fmri.stem in the inventory?
                 if self.image.is_installed(fmri):
@@ -209,9 +221,16 @@
         def evaluate_fmri_removal(self, pfmri):
                 assert self.image.has_manifest(pfmri)
 
-                m = self.image.get_manifest(pfmri)
+                dependents = self.image.get_dependents(pfmri)
 
-                # XXX What to do with dependencies?
+                if dependents and not self.recursive_removal:
+                        print "Cannot remove '%s' due to" % pfmri
+                        print "the following packages that directly depend on it:"
+                        for d in dependents:
+                                print " ", fmri.PkgFmri(d, "")
+                        return
+
+                m = self.image.get_manifest(pfmri)
 
                 pp = pkgplan.PkgPlan(self.image)
 
@@ -223,6 +242,18 @@
 
                 pp.evaluate()
 
+                for d in dependents:
+                        rf = fmri.PkgFmri(d, None)
+                        if self.is_proposed_rem_fmri(rf):
+                                continue
+                        if not self.image.is_installed(rf):
+                                continue
+                        self.target_rem_fmris.append(rf)
+                        self.evaluate_fmri_removal(rf)
+
+                # Post-order append will ensure topological sorting for acyclic
+                # dependency graphs.  Cycles need to be arbitrarily broken, and
+                # are done so in the loop above.
                 self.pkg_plans.append(pp)
 
         def evaluate(self):
@@ -232,7 +263,7 @@
                 for f in self.target_fmris[:]:
                         self.evaluate_fmri(f)
 
-                for f in self.target_rem_fmris:
+                for f in self.target_rem_fmris[:]:
                         self.evaluate_fmri_removal(f)
 
                 self.state = EVALUATED_OK
--- a/src/modules/client/pkgplan.py	Fri Aug 03 11:46:58 2007 -0700
+++ b/src/modules/client/pkgplan.py	Mon Aug 06 14:53:13 2007 -0700
@@ -229,5 +229,12 @@
                                 link = os.path.join(dir,
                                     self.destination_fmri.get_url_path())
 
+                                # If the value has slashes in it, link will be
+                                # that many directories further down, so we need
+                                # to add suffcient parent dirs to get back up to
+                                # the right level.
+                                extra = os.path.sep.join(("..",) * v.count("/"))
+
                                 if not os.path.lexists(link):
-                                        os.symlink(target, link)
+                                        os.symlink(
+                                            os.path.join(extra, target), link)