Handle dependencies when removing a package.
--- 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)