Merged from trunk 11149:11157
authorFabian Groffen <grobian@gentoo.org>
Wed, 23 Jul 2008 06:52:10 +0000 (06:52 -0000)
committerFabian Groffen <grobian@gentoo.org>
Wed, 23 Jul 2008 06:52:10 +0000 (06:52 -0000)
   | 11150   | Fix broken references to the old elog_modules namespace.     |
   | zmedico |                                                              |

   | 11151   | Redirect the 'Recording in "world" favorites file...'        |
   | zmedico | message through _status_msg().                               |

   | 11152   | For --depclean and --prune, eliminate duplicate graph        |
   | zmedico | creation code by re-using the depgraph class to do it. The   |
   |         | depgraph class was used in the past for this purpose but     |
   |         | back then it didn't handle USE flags correctly for installed |
   |         | packages. Now it works fine.                                 |

   | 11153   | Fix typo.                                                    |
   | zmedico |                                                              |

   | 11154   | Fix breakage in the depclean/prune unsatisfied deps          |
   | zmedico | handling.                                                    |

   | 11155   | In dblink._preserve_libs(), strip leading $ROOT from         |
   | zmedico | contents paths so that it works when $ROOT != /.             |

   | 11156   | In dblink.findProviders(), clone the "path" variable before  |
   | zmedico | extending it since obviously it shouldn't grow each time     |
   |         | that findProviders() is called.                              |

   | 11157   | Add initial lib awareness for --depclean and --prune.        |
   | zmedico | Currently it will simply bail out if the clean list contains |
   |         | a package that will cause link breakage if removed. TODO:    |
   |         | Calculate a new cleanlist instead of bailing out, and        |
   |         | implement a package set for rebuilding consumers.            |

svn path=/main/branches/prefix/; revision=11171

pym/_emerge/__init__.py
pym/portage/__init__.py
pym/portage/dbapi/vartree.py
pym/portage/elog/mod_custom.py
pym/portage/sets/base.py

index 46760052d6b789f46189191e2f034577e227546e..932824055189814cbd7b9cf46aac942c037a3d92 100644 (file)
@@ -80,7 +80,7 @@ from portage.const import EPREFIX, BPREFIX, EPREFIX_LSTRIP
 from portage.data import secpass
 from portage.elog.messages import eerror
 from portage.util import normalize_path as normpath
-from portage.util import writemsg
+from portage.util import writemsg, writemsg_level
 from portage.sets import load_default_config, SETPREFIX
 from portage.sets.base import InternalPackageSet
 
@@ -393,7 +393,14 @@ def create_depgraph_params(myopts, myaction):
        # deep:      go into the dependencies of already merged packages
        # empty:     pretend nothing is merged
        # complete:  completely account for all known dependencies
+       # remove:    build graph for use in removing packages
        myparams = set(["recurse"])
+
+       if myaction == "remove":
+               myparams.add("remove")
+               myparams.add("complete")
+               return myparams
+
        if "--update" in myopts or \
                "--newuse" in myopts or \
                "--reinstall" in myopts or \
@@ -1006,7 +1013,7 @@ class BlockerDepPriority(DepPriority):
 BlockerDepPriority.instance = BlockerDepPriority()
 
 class UnmergeDepPriority(AbstractDepPriority):
-       __slots__ = ()
+       __slots__ = ("satisfied",)
        """
        Combination of properties           Priority  Category
 
@@ -4141,6 +4148,7 @@ class depgraph(object):
                self._circular_deps_for_display = None
                self._dep_stack = []
                self._unsatisfied_deps = []
+               self._initially_unsatisfied_deps = []
                self._ignored_deps = []
                self._required_set_names = set(["system", "world"])
                self._select_atoms = self._select_atoms_highest_available
@@ -4515,9 +4523,9 @@ class depgraph(object):
 
                deps = (
                        ("/", edepend["DEPEND"],
-                               DepPriority(buildtime=True, satisfied=bdeps_satisfied)),
-                       (myroot, edepend["RDEPEND"], DepPriority(runtime=True)),
-                       (myroot, edepend["PDEPEND"], DepPriority(runtime_post=True))
+                               self._priority(buildtime=True, satisfied=bdeps_satisfied)),
+                       (myroot, edepend["RDEPEND"], self._priority(runtime=True)),
+                       (myroot, edepend["PDEPEND"], self._priority(runtime_post=True))
                )
 
                debug = "--debug" in self.myopts
@@ -4589,6 +4597,13 @@ class depgraph(object):
                        return 0
                return 1
 
+       def _priority(self, **kwargs):
+               if "remove" in self.myparams:
+                       priority_constructor = UnmergeDepPriority
+               else:
+                       priority_constructor = DepPriority
+               return priority_constructor(**kwargs)
+
        def _dep_expand(self, root_config, atom_without_category):
                """
                @param root_config: a root config instance
@@ -5577,7 +5592,7 @@ class depgraph(object):
                                dep = self._unsatisfied_deps.pop()
                                matches = vardb.match_pkgs(dep.atom)
                                if not matches:
-                                       # Initially unsatisfied.
+                                       self._initially_unsatisfied_deps.append(dep)
                                        continue
                                # An scheduled installation broke a deep dependency.
                                # Add the installed package to the graph so that it
@@ -9688,8 +9703,8 @@ class Scheduler(PollScheduler):
                        world_set.load() # maybe it's changed on disk
                        atom = create_world_atom(pkg, args_set, root_config)
                        if atom:
-                               portage.writemsg_stdout(('>>> Recording %s in "world" ' + \
-                                       'favorites file...\n') % atom)
+                               self._status_msg(('Recording %s in "world" ' + \
+                                       'favorites file...') % atom)
                                logger.log(" === (%s of %s) Updating world file (%s)" % \
                                        (pkg_count.curval, pkg_count.maxval, pkg.cpv))
                                world_set.add(atom)
@@ -11491,11 +11506,7 @@ def action_depclean(settings, trees, ldpath_mtimes,
        allow_missing_deps = len(myfiles) > 0
 
        msg = []
-       msg.append("Depclean may break link level dependencies.  Thus, it is\n")
-       msg.append("recommended to use a tool such as " + good("`revdep-rebuild`") + " (from\n")
-       msg.append("app-portage/gentoolkit) in order to detect such breakage.\n")
-       msg.append("\n")
-       msg.append("Also study the list of packages to be cleaned for any obvious\n")
+       msg.append("Always study the list of packages to be cleaned for any obvious\n")
        msg.append("mistakes. Packages that are part of the world set will always\n")
        msg.append("be kept.  They can be manually added to this set with\n")
        msg.append(good("`emerge --noreplace <atom>`") + ".  Packages that are listed in\n")
@@ -11510,57 +11521,71 @@ def action_depclean(settings, trees, ldpath_mtimes,
        if action == "depclean" and "--quiet" not in myopts and not myfiles:
                portage.writemsg_stdout("\n")
                for x in msg:
-                       portage.writemsg_stdout(colorize("BAD", "*** WARNING ***  ") + x)
+                       portage.writemsg_stdout(colorize("WARN", " * ") + x)
 
        xterm_titles = "notitles" not in settings.features
        myroot = settings["ROOT"]
-       portdb = trees[myroot]["porttree"].dbapi
-       pkg_cache = {}
-       dep_check_trees = {}
-       dep_check_trees[myroot] = {}
-       dep_check_trees[myroot]["vartree"] = \
-               FakeVartree(trees[myroot]["root_config"], pkg_cache=pkg_cache)
-       vardb = dep_check_trees[myroot]["vartree"].dbapi
-       # Constrain dependency selection to the installed packages.
-       dep_check_trees[myroot]["porttree"] = dep_check_trees[myroot]["vartree"]
        root_config = trees[myroot]["root_config"]
-       setconfig = root_config.setconfig
-       syslist = setconfig.getSetAtoms("system")
-       worldlist = setconfig.getSetAtoms("world")
-       args_set = InternalPackageSet()
-       fakedb = portage.fakedbapi(settings=settings)
-       myvarlist = vardb.cpv_all()
-
-       if not syslist:
-               print "\n!!! You have no system list.",
-       if not worldlist:
-               print "\n!!! You have no world file.",
-       if not myvarlist:
-               print "\n!!! You have no installed package database (%s)." % portage.VDB_PATH,
-
-       if not (syslist and worldlist and myvarlist):
-               print "\n!!! Proceeding "+(syslist and myvarlist and "may" or "will")
-               print " break your installation.\n"
+       getSetAtoms = root_config.setconfig.getSetAtoms
+       vardb = trees[myroot]["vartree"].dbapi
+
+       required_set_names = ("system", "world")
+       required_sets = {}
+       set_args = []
+
+       for s in required_set_names:
+               required_sets[s] = InternalPackageSet(
+                       initial_atoms=getSetAtoms(s))
+
+       
+       # When removing packages, use a temporary version of world
+       # which excludes packages that are intended to be eligible for
+       # removal.
+       world_temp_set = required_sets["world"]
+       system_set = required_sets["system"]
+
+       if not system_set or not world_temp_set:
+
+               if not system_set:
+                       writemsg_level("!!! You have no system list.\n",
+                               level=logging.ERROR, noiselevel=-1)
+
+               if not world_temp_set:
+                       writemsg_level("!!! You have no world file.\n",
+                                       level=logging.WARNING, noiselevel=-1)
+
+               writemsg_level("!!! Proceeding is likely to " + \
+                       "break your installation.\n",
+                       level=logging.WARNING, noiselevel=-1)
                if "--pretend" not in myopts:
                        countdown(int(settings["EMERGE_WARNING_DELAY"]), ">>> Depclean")
 
        if action == "depclean":
                emergelog(xterm_titles, " >>> depclean")
+
+       import textwrap
+       args_set = InternalPackageSet()
        if myfiles:
                for x in myfiles:
                        if not is_valid_package_atom(x):
-                               portage.writemsg("!!! '%s' is not a valid package atom.\n" % x,
-                                       noiselevel=-1)
-                               portage.writemsg("!!! Please check ebuild(5) for full details.\n")
+                               writemsg_level("!!! '%s' is not a valid package atom.\n" % x,
+                                       level=logging.ERROR, noiselevel=-1)
+                               writemsg_level("!!! Please check ebuild(5) for full details.\n")
                                return
                        try:
                                atom = portage.dep_expand(x, mydb=vardb, settings=settings)
                        except ValueError, e:
-                               print "!!! The short ebuild name \"" + x + "\" is ambiguous.  Please specify"
-                               print "!!! one of the following fully-qualified ebuild names instead:\n"
+                               msg = "The short ebuild name \"" + x + \
+                                       "\" is ambiguous.  Please specify " + \
+                                       "one of the following " + \
+                                       "fully-qualified ebuild names instead:"
+                               for line in textwrap.wrap(msg, 70):
+                                       writemsg_level("!!! %s\n" % (line,),
+                                               level=logging.ERROR, noiselevel=-1)
                                for i in e[0]:
-                                       print "    " + colorize("INFORM", i)
-                               print
+                                       writemsg_level("    %s\n" % colorize("INFORM", i),
+                                               level=logging.ERROR, noiselevel=-1)
+                               writemsg_level("\n", level=logging.ERROR, noiselevel=-1)
                                return
                        args_set.add(atom)
                matched_packages = False
@@ -11569,149 +11594,114 @@ def action_depclean(settings, trees, ldpath_mtimes,
                                matched_packages = True
                                break
                if not matched_packages:
-                       portage.writemsg_stdout(
-                               ">>> No packages selected for removal by %s\n" % action)
+                       writemsg_level(">>> No packages selected for removal by %s\n" % \
+                               action)
                        return
 
-       if "--quiet" not in myopts:
-               print "\nCalculating dependencies  ",
+       writemsg_level("\nCalculating dependencies  ")
+       resolver_params = create_depgraph_params(myopts, "remove")
+       resolver = depgraph(settings, trees, myopts, resolver_params, spinner)
+       vardb = resolver.trees[myroot]["vartree"].dbapi
 
-       runtime = UnmergeDepPriority(runtime=True)
-       runtime_post = UnmergeDepPriority(runtime_post=True)
-       buildtime = UnmergeDepPriority(buildtime=True)
+       if action == "depclean":
 
-       priority_map = {
-               "RDEPEND": runtime,
-               "PDEPEND": runtime_post,
-               "DEPEND": buildtime,
-       }
+               if args_set:
+                       # Pull in everything that's installed but not matched
+                       # by an argument atom since we don't want to clean any
+                       # package if something depends on it.
+
+                       world_temp_set.clear()
+                       for pkg in vardb:
+                               spinner.update()
+
+                               try:
+                                       if args_set.findAtomForPackage(pkg) is None:
+                                               world_temp_set.add("=" + pkg.cpv)
+                                               continue
+                               except portage.exception.InvalidDependString, e:
+                                       show_invalid_depstring_notice(pkg,
+                                               pkg.metadata["PROVIDE"], str(e))
+                                       del e
+                                       world_temp_set.add("=" + pkg.cpv)
+                                       continue
 
-       remaining_atoms = []
-       if action == "depclean":
-               for atom in syslist:
-                       if vardb.match(atom):
-                               remaining_atoms.append((atom, 'system', runtime))
-               if myfiles:
-                       # Pull in everything that's installed since we don't want
-                       # to clean any package if something depends on it.
-                       remaining_atoms.extend(
-                               ("="+cpv, 'world', runtime) for cpv in vardb.cpv_all())
-               else:
-                       for atom in worldlist:
-                               if vardb.match(atom):
-                                       remaining_atoms.append((atom, 'world', runtime))
        elif action == "prune":
-               for atom in syslist:
-                       if vardb.match(atom):
-                               remaining_atoms.append((atom, 'system', runtime))
-               # Pull in everything that's installed since we don't want to prune a
-               # package if something depends on it.
-               remaining_atoms.extend(
-                       (atom, 'world', runtime) for atom in vardb.cp_all())
-               if not myfiles:
+
+               # Pull in everything that's installed since we don't
+               # to prune a package if something depends on it.
+               world_temp_set.clear()
+               world_temp_set.update(vardb.cp_all())
+
+               if not args_set:
+
                        # Try to prune everything that's slotted.
                        for cp in vardb.cp_all():
                                if len(vardb.cp_list(cp)) > 1:
                                        args_set.add(cp)
 
-       unresolveable = {}
-       aux_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
-       metadata_keys = depgraph._mydbapi_keys
-       graph = digraph()
-       with_bdeps = myopts.get("--with-bdeps", "y") == "y"
-
-       while remaining_atoms:
-               atom, parent, priority = remaining_atoms.pop()
-               pkgs = vardb.match(atom)
-               if not pkgs:
-                       if priority > UnmergeDepPriority.SOFT:
-                               unresolveable.setdefault(atom, []).append(parent)
-                       continue
-               if action == "depclean" and parent == "world" and myfiles:
-                       # Filter out packages given as arguments since the user wants
-                       # to remove those.
-                       filtered_pkgs = []
-                       for pkg in pkgs:
-                               arg_atom = None
-                               try:
-                                       arg_atom = args_set.findAtomForPackage(
-                                               pkg_cache[("installed", myroot, pkg, "nomerge")])
-                               except portage.exception.InvalidDependString, e:
-                                       file_path = os.path.join(
-                                               myroot, portage.VDB_PATH, pkg, "PROVIDE")
-                                       portage.writemsg("\n\nInvalid PROVIDE: %s\n" % str(e),
-                                               noiselevel=-1)
-                                       portage.writemsg("See '%s'\n" % file_path,
-                                               noiselevel=-1)
-                                       del e
-                               if not arg_atom:
-                                       filtered_pkgs.append(pkg)
-                       pkgs = filtered_pkgs
-               if len(pkgs) > 1:
-                       # For consistency with the update algorithm, keep the highest
-                       # visible version and prune any versions that are old or masked.
-                       for cpv in reversed(pkgs):
-                               if visible(settings,
-                                       pkg_cache[("installed", myroot, cpv, "nomerge")]):
-                                       pkgs = [cpv]
-                                       break
-                       if len(pkgs) > 1:
-                               # They're all masked, so just keep the highest version.
-                               pkgs = [pkgs[-1]]
-               for pkg in pkgs:
-                       graph.add(pkg, parent, priority=priority)
-                       if fakedb.cpv_exists(pkg):
-                               continue
+               # Remove atoms from world that match installed packages
+               # that are also matched by argument atoms, but do not remove
+               # them if they match the highest installed version.
+               for pkg in vardb:
                        spinner.update()
-                       fakedb.cpv_inject(pkg)
-                       myaux = izip(aux_keys, vardb.aux_get(pkg, aux_keys))
-                       mydeps = []
-
-                       usedef = vardb.aux_get(pkg, ["USE"])[0].split()
-                       for dep_type, depstr in myaux:
+                       pkgs_for_cp = vardb.match_pkgs(pkg.cp)
+                       if not pkgs_for_cp or pkg not in pkgs_for_cp:
+                               raise AssertionError("package expected in matches: " + \
+                                       "cp = %s, cpv = %s matches = %s" % \
+                                       (pkg.cp, pkg.cpv, [str(x) for x in pkgs_for_cp]))
+
+                       highest_version = pkgs_for_cp[-1]
+                       if pkg == highest_version:
+                               # pkg is the highest version
+                               world_temp_set.add("=" + pkg.cpv)
+                               continue
 
-                               if not depstr:
-                                       continue
+                       if len(pkgs_for_cp) <= 1:
+                               raise AssertionError("more packages expected: " + \
+                                       "cp = %s, cpv = %s matches = %s" % \
+                                       (pkg.cp, pkg.cpv, [str(x) for x in pkgs_for_cp]))
 
-                               if not with_bdeps and dep_type == "DEPEND":
+                       try:
+                               if args_set.findAtomForPackage(pkg) is None:
+                                       world_temp_set.add("=" + pkg.cpv)
                                        continue
+                       except portage.exception.InvalidDependString, e:
+                               show_invalid_depstring_notice(pkg,
+                                       pkg.metadata["PROVIDE"], str(e))
+                               del e
+                               world_temp_set.add("=" + pkg.cpv)
+                               continue
 
-                               priority = priority_map[dep_type]
-                               if "--debug" in myopts:
-                                       print
-                                       print "Parent:   ", pkg
-                                       print "Depstring:", depstr
-                                       print "Priority:", priority
+       set_args = {}
+       for s, package_set in required_sets.iteritems():
+               set_atom = SETPREFIX + s
+               set_arg = SetArg(arg=set_atom, set=package_set,
+                       root_config=resolver.roots[myroot])
+               set_args[s] = set_arg
+               for atom in set_arg.set:
+                       resolver._dep_stack.append(
+                               Dependency(atom=atom, root=myroot, parent=set_arg))
+                       resolver.digraph.add(set_arg, None)
 
-                               try:
-                                       portage.dep._dep_check_strict = False
-                                       success, atoms = portage.dep_check(depstr, None, settings,
-                                               myuse=usedef, trees=dep_check_trees, myroot=myroot)
-                               finally:
-                                       portage.dep._dep_check_strict = True
-                               if not success:
-                                       show_invalid_depstring_notice(
-                                               ("installed", myroot, pkg, "nomerge"),
-                                               depstr, atoms)
-                                       return
+       success = resolver._complete_graph()
+       writemsg_level("\b\b... done!\n")
 
-                               if "--debug" in myopts:
-                                       print "Candidates:", atoms
+       resolver.display_problems()
 
-                               for atom in atoms:
-                                       if atom.startswith("!"):
-                                               continue
-                                       remaining_atoms.append((atom, pkg, priority))
+       if not success:
+               return 1
 
-       if "--quiet" not in myopts:
-               print "\b\b... done!\n"
+       unresolveable = set()
+       for dep in resolver._initially_unsatisfied_deps:
+               if isinstance(dep.parent, Package):
+                       unresolveable.add((dep.atom, dep.parent.cpv))
 
        if unresolveable and not allow_missing_deps:
                print "Dependencies could not be completely resolved due to"
                print "the following required packages not being installed:"
                print
-               for atom in unresolveable:
-                       print atom, "required by", " ".join(unresolveable[atom])
+               for atom, parent in unresolveable:
+                       print atom, "required by", str(parent)
        if unresolveable and not allow_missing_deps:
                print
                print "Have you forgotten to run " + good("`emerge --update --newuse --deep world`") + " prior to"
@@ -11725,6 +11715,12 @@ def action_depclean(settings, trees, ldpath_mtimes,
                                good("--nodeps")
                return
 
+       graph = resolver.digraph.copy()
+       required_pkgs_total = 0
+       for node in graph:
+               if isinstance(node, Package):
+                       required_pkgs_total += 1
+
        def show_parents(child_node):
                parent_nodes = graph.parent_nodes(child_node)
                if not parent_nodes:
@@ -11732,44 +11728,45 @@ def action_depclean(settings, trees, ldpath_mtimes,
                        # real parent since all installed packages are pulled in.  In that
                        # case there's nothing to show here.
                        return
-               parent_nodes.sort()
+               parent_strs = []
+               for node in parent_nodes:
+                       parent_strs.append(str(getattr(node, "cpv", node)))
+               parent_strs.sort()
                msg = []
-               msg.append("  %s pulled in by:\n" % str(child_node))
-               for parent_node in parent_nodes:
-                       msg.append("    %s\n" % str(parent_node))
+               msg.append("  %s pulled in by:\n" % (child_node.cpv,))
+               for parent_str in parent_strs:
+                       msg.append("    %s\n" % (parent_str,))
                msg.append("\n")
                portage.writemsg_stdout("".join(msg), noiselevel=-1)
 
        cleanlist = []
        if action == "depclean":
-               if myfiles:
-                       for pkg in vardb.cpv_all():
+               if args_set:
+                       for pkg in vardb:
                                arg_atom = None
                                try:
-                                       arg_atom = args_set.findAtomForPackage(
-                                               pkg_cache[("installed", myroot, pkg, "nomerge")])
+                                       arg_atom = args_set.findAtomForPackage(pkg)
                                except portage.exception.InvalidDependString:
                                        # this error has already been displayed by now
                                        continue
                                if arg_atom:
-                                       if not fakedb.cpv_exists(pkg):
+                                       if pkg not in graph:
                                                cleanlist.append(pkg)
                                        elif "--verbose" in myopts:
                                                show_parents(pkg)
                else:
-                       for pkg in vardb.cpv_all():
-                               if not fakedb.cpv_exists(pkg):
+                       for pkg in vardb:
+                               if pkg not in graph:
                                        cleanlist.append(pkg)
                                elif "--verbose" in myopts:
                                        show_parents(pkg)
        elif action == "prune":
                # Prune really uses all installed instead of world.  It's not a real
                # reverse dependency so don't display it as such.
-               if graph.contains("world"):
-                       graph.remove("world")
+               graph.remove(set_args["world"])
                for atom in args_set:
-                       for pkg in vardb.match(atom):
-                               if not fakedb.cpv_exists(pkg):
+                       for pkg in vardb.match_pkgs(atom):
+                               if pkg not in graph:
                                        cleanlist.append(pkg)
                                elif "--verbose" in myopts:
                                        show_parents(pkg)
@@ -11787,6 +11784,158 @@ def action_depclean(settings, trees, ldpath_mtimes,
                                        good("--nodeps"))
 
        if len(cleanlist):
+               clean_set = set(cleanlist)
+
+               # Check if any of these package are the sole providers of libraries
+               # with consumers that have not been selected for removal. If so, these
+               # packages and any dependencies need to be added to the graph.
+               real_vardb = trees[myroot]["vartree"].dbapi
+               linkmap = real_vardb.linkmap
+               liblist = linkmap.listLibraryObjects()
+               consumer_cache = {}
+               provider_cache = {}
+               soname_cache = {}
+               consumer_map = {}
+
+               writemsg_level(">>> Checking for lib consumers...\n")
+
+               for pkg in cleanlist:
+                       pkg_dblink = real_vardb._dblink(pkg.cpv)
+                       provided_libs = set()
+
+                       for lib in liblist:
+                               if pkg_dblink.isowner(lib, myroot):
+                                       provided_libs.add(lib)
+
+                       if not provided_libs:
+                               continue
+
+                       consumers = {}
+                       for lib in provided_libs:
+                               lib_consumers = consumer_cache.get(lib)
+                               if lib_consumers is None:
+                                       lib_consumers = linkmap.findConsumers(lib)
+                                       consumer_cache[lib] = lib_consumers
+                               if lib_consumers:
+                                       consumers[lib] = lib_consumers
+
+                       if not consumers:
+                               continue
+
+                       for lib, lib_consumers in consumers.items():
+                               for consumer_file in list(lib_consumers):
+                                       if pkg_dblink.isowner(consumer_file, myroot):
+                                               lib_consumers.remove(consumer_file)
+                               if not lib_consumers:
+                                       del consumers[lib]
+
+                       if not consumers:
+                               continue
+
+                       for lib, lib_consumers in consumers.iteritems():
+
+                               soname = soname_cache.get(lib)
+                               if soname is None:
+                                       soname = linkmap.getSoname(lib)
+                                       soname_cache[lib] = soname
+
+                               consumer_providers = []
+                               for lib_consumer in lib_consumers:
+                                       providers = provider_cache.get(lib)
+                                       if providers is None:
+                                               providers = linkmap.findProviders(lib_consumer)
+                                               provider_cache[lib_consumer] = providers
+                                       if soname not in providers:
+                                               # Why does this happen?
+                                               continue
+                                       consumer_providers.append(
+                                               (lib_consumer, providers[soname]))
+
+                               consumers[lib] = consumer_providers
+
+                       consumer_map[pkg] = consumers
+
+               if consumer_map:
+
+                       search_files = set()
+                       for consumers in consumer_map.itervalues():
+                               for lib, consumer_providers in consumers.iteritems():
+                                       for lib_consumer, providers in consumer_providers:
+                                               search_files.add(lib_consumer)
+                                               search_files.update(providers)
+
+                       writemsg_level(">>> Assigning files to packages...\n")
+                       file_owners = real_vardb._owners.getFileOwnerMap(search_files)
+
+                       for pkg, consumers in consumer_map.items():
+                               for lib, consumer_providers in consumers.items():
+                                       lib_consumers = set()
+
+                                       for lib_consumer, providers in consumer_providers:
+                                               owner_set = file_owners.get(lib_consumer)
+                                               provider_dblinks = set()
+                                               provider_pkgs = set()
+
+                                               if len(providers) > 1:
+                                                       for provider in providers:
+                                                               provider_set = file_owners.get(provider)
+                                                               if provider_set is not None:
+                                                                       provider_dblinks.update(provider_set)
+
+                                               if len(provider_dblinks) > 1:
+                                                       for provider_dblink in provider_dblinks:
+                                                               pkg_key = ("installed", myroot,
+                                                                       provider_dblink.mycpv, "nomerge")
+                                                               if pkg_key not in clean_set:
+                                                                       provider_pkgs.add(vardb.get(pkg_key))
+
+                                               if provider_pkgs:
+                                                       continue
+
+                                               if owner_set is not None:
+                                                       lib_consumers.update(owner_set)
+
+                                       for consumer_dblink in list(lib_consumers):
+                                               if ("installed", myroot, consumer_dblink.mycpv,
+                                                       "nomerge") in clean_set:
+                                                       lib_consumers.remove(consumer_dblink)
+                                                       continue
+
+                                       if lib_consumers:
+                                               consumers[lib] = lib_consumers
+                                       else:
+                                               del consumers[lib]
+                               if not consumers:
+                                       del consumer_map[pkg]
+
+               if consumer_map:
+                       # TODO: Implement a package set for rebuilding consumer packages.
+
+                       msg = "In order to avoid breakage of link level " + \
+                               "dependencies, one or more packages will not be removed. " + \
+                               "This can be solved by rebuilding " + \
+                               "the packages that pulled them in."
+
+                       prefix = bad(" * ")
+                       from textwrap import wrap
+                       writemsg_level("".join(prefix + "%s\n" % line for \
+                               line in wrap(msg, 70)), level=logging.WARNING, noiselevel=-1)
+
+                       msg = []
+                       for pkg, consumers in consumer_map.iteritems():
+                               unique_consumers = set(chain(*consumers.values()))
+                               unique_consumers = sorted(consumer.mycpv \
+                                       for consumer in unique_consumers)
+                               msg.append("")
+                               msg.append("  %s pulled in by:" % (pkg.cpv,))
+                               for consumer in unique_consumers:
+                                       msg.append("    %s" % (consumer,))
+                       msg.append("")
+                       writemsg_level("".join(prefix + "%s\n" % line for line in msg),
+                               level=logging.WARNING, noiselevel=-1)
+                       # TODO: Add packages + deps to graph, and calculate new clean list.
+                       return 1
+
                # Use a topological sort to create an unmerge order such that
                # each package is unmerged before it's dependencies. This is
                # necessary to avoid breaking things that may need to run
@@ -11795,20 +11944,31 @@ def action_depclean(settings, trees, ldpath_mtimes,
                # Create a new graph to account for dependencies between the
                # packages being unmerged.
                graph = digraph()
-               clean_set = set(cleanlist)
                del cleanlist[:]
+
+               dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
+               runtime = UnmergeDepPriority(runtime=True)
+               runtime_post = UnmergeDepPriority(runtime_post=True)
+               buildtime = UnmergeDepPriority(buildtime=True)
+               priority_map = {
+                       "RDEPEND": runtime,
+                       "PDEPEND": runtime_post,
+                       "DEPEND": buildtime,
+               }
+
                for node in clean_set:
                        graph.add(node, None)
-                       myaux = izip(aux_keys, vardb.aux_get(node, aux_keys))
                        mydeps = []
-                       usedef = vardb.aux_get(node, ["USE"])[0].split()
-                       for dep_type, depstr in myaux:
+                       node_use = node.metadata["USE"].split()
+                       for dep_type in dep_keys:
+                               depstr = node.metadata[dep_type]
                                if not depstr:
                                        continue
                                try:
                                        portage.dep._dep_check_strict = False
                                        success, atoms = portage.dep_check(depstr, None, settings,
-                                               myuse=usedef, trees=dep_check_trees, myroot=myroot)
+                                               myuse=node_use, trees=resolver._graph_trees,
+                                               myroot=myroot)
                                finally:
                                        portage.dep._dep_check_strict = True
                                if not success:
@@ -11816,24 +11976,24 @@ def action_depclean(settings, trees, ldpath_mtimes,
                                                ("installed", myroot, node, "nomerge"),
                                                depstr, atoms)
                                        return
-
+ 
                                priority = priority_map[dep_type]
                                for atom in atoms:
                                        if atom.startswith("!"):
                                                continue
-                                       matches = vardb.match(atom)
+                                       matches = vardb.match_pkgs(atom)
                                        if not matches:
                                                continue
-                                       for cpv in matches:
-                                               if cpv in clean_set:
-                                                       graph.add(cpv, node, priority=priority)
+                                       for child_node in matches:
+                                               if child_node in clean_set:
+                                                       graph.add(child_node, node, priority=priority)
 
                ordered = True
                if len(graph.order) == len(graph.root_nodes()):
                        # If there are no dependencies between packages
                        # let unmerge() group them by cat/pn.
                        ordered = False
-                       cleanlist = graph.all_nodes()
+                       cleanlist = [pkg.cpv for pkg in graph.order]
                else:
                        # Order nodes from lowest to highest overall reference count for
                        # optimal root node selection.
@@ -11861,7 +12021,7 @@ def action_depclean(settings, trees, ldpath_mtimes,
                                        del nodes[1:]
                                for node in nodes:
                                        graph.remove(node)
-                                       cleanlist.append(node)
+                                       cleanlist.append(node.cpv)
 
                unmerge(root_config, myopts, "unmerge", cleanlist,
                        ldpath_mtimes, ordered=ordered)
@@ -11872,11 +12032,12 @@ def action_depclean(settings, trees, ldpath_mtimes,
        if not cleanlist and "--quiet" in myopts:
                return
 
-       print "Packages installed:   "+str(len(myvarlist))
-       print "Packages in world:    "+str(len(worldlist))
-       print "Packages in system:   "+str(len(syslist))
-       print "Unique package names: "+str(len(myvarlist))
-       print "Required packages:    "+str(len(fakedb.cpv_all()))
+       print "Packages installed:   "+str(len(vardb.cpv_all()))
+       print "Packages in world:    " + \
+               str(len(root_config.sets["world"].getAtoms()))
+       print "Packages in system:   " + \
+               str(len(root_config.sets["system"].getAtoms()))
+       print "Required packages:    "+str(required_pkgs_total)
        if "--pretend" in myopts:
                print "Number to remove:     "+str(len(cleanlist))
        else:
index df0a2099b0068d0b7e5d5536e0dd057add492c67..b40041ffa621ff24611ac3073dd523a66d58c266 100644 (file)
@@ -434,6 +434,9 @@ class digraph(object):
                del self.nodes[child][1][parent]
                del self.nodes[parent][0][child]
 
+       def __iter__(self):
+               return iter(self.order)
+
        def contains(self, node):
                """Checks if the digraph contains mynode"""
                return node in self.nodes
index 4a7fc7ad3c9b420e4a201a3408c1b7e96c5a0531..bfd972684c85771854cbf3ad89adb826a415a555 100644 (file)
@@ -205,7 +205,17 @@ class LinkageMap(object):
                        for arch in self._libs[soname]:
                                rValue.extend(self._libs[soname][arch]["providers"])
                return rValue
-       
+
+       def getSoname(self, obj):
+               if not self._libs:
+                       self.rebuild()
+               if obj not in self._obj_properties:
+                       obj = realpath(obj)
+                       if obj not in self._obj_properties:
+                               raise KeyError("%s not in object list" % obj)
+               arch, needed, path, soname = self._obj_properties[obj]
+               return soname
+
        def findProviders(self, obj):
                if not self._libs:
                        self.rebuild()
@@ -224,6 +234,7 @@ class LinkageMap(object):
                        if obj not in self._obj_properties:
                                raise KeyError("%s not in object list" % obj)
                arch, needed, path, soname = self._obj_properties[obj]
+               path = path[:]
                path.extend(self._defpath)
                path = set(realpath(x) for x in path)
                for x in needed:
@@ -1192,6 +1203,18 @@ class vardbapi(dbapi):
                                owned_files.add(f)
                        return owners
 
+               def getFileOwnerMap(self, path_iter):
+                       owners = self.get_owners(path_iter)
+                       file_owners = {}
+                       for pkg_dblink, files in owners.iteritems():
+                               for f in files:
+                                       owner_set = file_owners.get(f)
+                                       if owner_set is None:
+                                               owner_set = set()
+                                               file_owners[f] = owner_set
+                                       owner_set.add(pkg_dblink)
+                       return file_owners
+
                def iter_owners(self, path_iter):
                        """
                        Iterate over tuples of (dblink, path). In order to avoid
@@ -2250,8 +2273,10 @@ class dblink(object):
                liblist = linkmap.listLibraryObjects()
 
                # get list of libraries from old package instance
-               old_contents = self._installed_instance.getcontents().keys()
-               old_libs = set(old_contents).intersection(liblist)
+               root_len = len(self.myroot) - 1
+               old_contents = set(p[root_len:] \
+                       for p in self._installed_instance.getcontents())
+               old_libs = old_contents.intersection(liblist)
 
                # get list of libraries from new package instance
                mylibs = set([os.path.join(os.sep, x) for x in mycontents]).intersection(liblist)
index 09d981a0c05d85fa9946bd9d2047a8d1d9b9f2b3..0bcd42a95cbe14769b2bd8e45b39cf1808f14055 100644 (file)
@@ -3,10 +3,10 @@
 # Distributed under the terms of the GNU General Public License v2
 # $Id$
 
-import portage.elog_modules.mod_save, portage.process, portage.exception
+import portage.elog.mod_save, portage.process, portage.exception
 
 def process(mysettings, key, logentries, fulltext):
-       elogfilename = portage.elog_modules.mod_save.process(mysettings, key, logentries, fulltext)
+       elogfilename = portage.elog.mod_save.process(mysettings, key, logentries, fulltext)
        
        if not mysettings.get("PORTAGE_ELOG_COMMAND"):
                raise portage.exception.MissingParameter("!!! Custom logging requested but PORTAGE_ELOG_COMMAND is not defined")
index 717b163222345c91f82953fc6d60d3d1efe9c2d0..4de3e847da0e9eddbfc8bcf0d29da1f2f1b894a2 100644 (file)
@@ -37,6 +37,10 @@ class PackageSet(object):
                for x in self._nonatoms:
                        yield x
 
+       def __nonzero__(self):
+               self._load()
+               return bool(self._atoms or self._nonatoms)
+
        def supportsOperation(self, op):
                if not op in OPERATIONS:
                        raise ValueError(op)