def testCpvSortKey(self):

		tests = [ (("a/b-2_alpha", "a", "b", "a/b-2", "a/a-1", "a/b-1"),
			( "a", "a/a-1", "a/b-1", "a/b-2_alpha", "a/b-2", "b")),
		]

		for test in tests:
			self.assertEqual( tuple(sorted(test[0], key=cpv_sort_key())), test[1] )
示例#2
0
	def testCpvSortKey(self):

		tests = [
			(("a/b-2_alpha", "a", "b", "a/b-2", "a/a-1", "a/b-1"),
			 ("a", "a/a-1", "a/b-1", "a/b-2_alpha", "a/b-2", "b")),
		]

		for test in tests:
			self.assertEqual(tuple(sorted(test[0], key=cpv_sort_key())), test[1])
示例#3
0
def _unmerge_display(root_config, myopts, unmerge_action,
	unmerge_files, clean_delay=1, ordered=0,
	writemsg_level=portage.util.writemsg_level):
	"""
	Returns a tuple of (returncode, pkgmap) where returncode is
	os.EX_OK if no errors occur, and 1 otherwise.
	"""

	quiet = "--quiet" in myopts
	settings = root_config.settings
	sets = root_config.sets
	vartree = root_config.trees["vartree"]
	candidate_catpkgs=[]
	global_unmerge=0
	out = portage.output.EOutput()
	pkg_cache = {}
	db_keys = list(vartree.dbapi._aux_cache_keys)

	def _pkg(cpv):
		pkg = pkg_cache.get(cpv)
		if pkg is None:
			pkg = Package(built=True, cpv=cpv, installed=True,
				metadata=zip(db_keys, vartree.dbapi.aux_get(cpv, db_keys)),
				operation="uninstall", root_config=root_config,
				type_name="installed")
			pkg_cache[cpv] = pkg
		return pkg

	vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
	try:
		# At least the parent needs to exist for the lock file.
		portage.util.ensure_dirs(vdb_path)
	except portage.exception.PortageException:
		pass
	vdb_lock = None
	try:
		if os.access(vdb_path, os.W_OK):
			vartree.dbapi.lock()
			vdb_lock = True

		realsyslist = []
		sys_virt_map = {}
		for x in sets["system"].getAtoms():
			for atom in expand_new_virt(vartree.dbapi, x):
				if not atom.blocker:
					realsyslist.append(atom)
					if atom.cp != x.cp:
						sys_virt_map[atom.cp] = x.cp

		syslist = []
		for x in realsyslist:
			mycp = x.cp
			# Since Gentoo stopped using old-style virtuals in
			# 2011, typically it's possible to avoid getvirtuals()
			# calls entirely. It will not be triggered here by
			# new-style virtuals since those are expanded to
			# non-virtual atoms above by expand_new_virt().
			if mycp.startswith("virtual/") and \
				mycp in settings.getvirtuals():
				providers = []
				for provider in settings.getvirtuals()[mycp]:
					if vartree.dbapi.match(provider):
						providers.append(provider)
				if len(providers) == 1:
					syslist.extend(providers)
			else:
				syslist.append(mycp)
		syslist = frozenset(syslist)
	
		if not unmerge_files:
			if unmerge_action == "unmerge":
				print()
				print(bold("emerge unmerge") + " can only be used with specific package names")
				print()
				return 1, {}
			else:
				global_unmerge = 1
	
		localtree = vartree
		# process all arguments and add all
		# valid db entries to candidate_catpkgs
		if global_unmerge:
			if not unmerge_files:
				candidate_catpkgs.extend(vartree.dbapi.cp_all())
		else:
			#we've got command-line arguments
			if not unmerge_files:
				print("\nNo packages to unmerge have been provided.\n")
				return 1, {}
			for x in unmerge_files:
				arg_parts = x.split('/')
				if x[0] not in [".","/"] and \
					arg_parts[-1][-7:] != ".ebuild":
					#possible cat/pkg or dep; treat as such
					candidate_catpkgs.append(x)
				elif unmerge_action in ["prune","clean"]:
					print("\n!!! Prune and clean do not accept individual" + \
						" ebuilds as arguments;\n    skipping.\n")
					continue
				else:
					# it appears that the user is specifying an installed
					# ebuild and we're in "unmerge" mode, so it's ok.
					if not os.path.exists(x):
						print("\n!!! The path '"+x+"' doesn't exist.\n")
						return 1, {}
	
					absx   = os.path.abspath(x)
					sp_absx = absx.split("/")
					if sp_absx[-1][-7:] == ".ebuild":
						del sp_absx[-1]
						absx = "/".join(sp_absx)
	
					sp_absx_len = len(sp_absx)
	
					vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
	
					sp_vdb     = vdb_path.split("/")
					sp_vdb_len = len(sp_vdb)
	
					if not os.path.exists(absx+"/CONTENTS"):
						print("!!! Not a valid db dir: "+str(absx))
						return 1, {}
	
					if sp_absx_len <= sp_vdb_len:
						# The Path is shorter... so it can't be inside the vdb.
						print(sp_absx)
						print(absx)
						print("\n!!!",x,"cannot be inside "+ \
							vdb_path+"; aborting.\n")
						return 1, {}
	
					for idx in range(0,sp_vdb_len):
						if idx >= sp_absx_len or sp_vdb[idx] != sp_absx[idx]:
							print(sp_absx)
							print(absx)
							print("\n!!!", x, "is not inside "+\
								vdb_path+"; aborting.\n")
							return 1, {}
	
					print("="+"/".join(sp_absx[sp_vdb_len:]))
					candidate_catpkgs.append(
						"="+"/".join(sp_absx[sp_vdb_len:]))
	
		newline=""
		if (not "--quiet" in myopts):
			newline="\n"
		if settings["ROOT"] != "/":
			writemsg_level(darkgreen(newline+ \
				">>> Using system located in ROOT tree %s\n" % \
				settings["ROOT"]))

		if (("--pretend" in myopts) or ("--ask" in myopts)) and \
			not ("--quiet" in myopts):
			writemsg_level(darkgreen(newline+\
				">>> These are the packages that would be unmerged:\n"))

		# Preservation of order is required for --depclean and --prune so
		# that dependencies are respected. Use all_selected to eliminate
		# duplicate packages since the same package may be selected by
		# multiple atoms.
		pkgmap = []
		all_selected = set()
		for x in candidate_catpkgs:
			# cycle through all our candidate deps and determine
			# what will and will not get unmerged
			try:
				mymatch = vartree.dbapi.match(x)
			except portage.exception.AmbiguousPackageName as errpkgs:
				print("\n\n!!! The short ebuild name \"" + \
					x + "\" is ambiguous.  Please specify")
				print("!!! one of the following fully-qualified " + \
					"ebuild names instead:\n")
				for i in errpkgs[0]:
					print("    " + green(i))
				print()
				sys.exit(1)
	
			if not mymatch and x[0] not in "<>=~":
				mymatch = localtree.dep_match(x)
			if not mymatch:
				portage.writemsg("\n--- Couldn't find '%s' to %s.\n" % \
					(x.replace("null/", ""), unmerge_action), noiselevel=-1)
				continue

			pkgmap.append(
				{"protected": set(), "selected": set(), "omitted": set()})
			mykey = len(pkgmap) - 1
			if unmerge_action=="unmerge":
					for y in mymatch:
						if y not in all_selected:
							pkgmap[mykey]["selected"].add(y)
							all_selected.add(y)
			elif unmerge_action == "prune":
				if len(mymatch) == 1:
					continue
				best_version = mymatch[0]
				best_slot = vartree.getslot(best_version)
				best_counter = vartree.dbapi.cpv_counter(best_version)
				for mypkg in mymatch[1:]:
					myslot = vartree.getslot(mypkg)
					mycounter = vartree.dbapi.cpv_counter(mypkg)
					if (myslot == best_slot and mycounter > best_counter) or \
						mypkg == portage.best([mypkg, best_version]):
						if myslot == best_slot:
							if mycounter < best_counter:
								# On slot collision, keep the one with the
								# highest counter since it is the most
								# recently installed.
								continue
						best_version = mypkg
						best_slot = myslot
						best_counter = mycounter
				pkgmap[mykey]["protected"].add(best_version)
				pkgmap[mykey]["selected"].update(mypkg for mypkg in mymatch \
					if mypkg != best_version and mypkg not in all_selected)
				all_selected.update(pkgmap[mykey]["selected"])
			else:
				# unmerge_action == "clean"
				slotmap={}
				for mypkg in mymatch:
					if unmerge_action == "clean":
						myslot = localtree.getslot(mypkg)
					else:
						# since we're pruning, we don't care about slots
						# and put all the pkgs in together
						myslot = 0
					if myslot not in slotmap:
						slotmap[myslot] = {}
					slotmap[myslot][localtree.dbapi.cpv_counter(mypkg)] = mypkg

				for mypkg in vartree.dbapi.cp_list(
					portage.cpv_getkey(mymatch[0])):
					myslot = vartree.getslot(mypkg)
					if myslot not in slotmap:
						slotmap[myslot] = {}
					slotmap[myslot][vartree.dbapi.cpv_counter(mypkg)] = mypkg

				for myslot in slotmap:
					counterkeys = list(slotmap[myslot])
					if not counterkeys:
						continue
					counterkeys.sort()
					pkgmap[mykey]["protected"].add(
						slotmap[myslot][counterkeys[-1]])
					del counterkeys[-1]

					for counter in counterkeys[:]:
						mypkg = slotmap[myslot][counter]
						if mypkg not in mymatch:
							counterkeys.remove(counter)
							pkgmap[mykey]["protected"].add(
								slotmap[myslot][counter])

					#be pretty and get them in order of merge:
					for ckey in counterkeys:
						mypkg = slotmap[myslot][ckey]
						if mypkg not in all_selected:
							pkgmap[mykey]["selected"].add(mypkg)
							all_selected.add(mypkg)
					# ok, now the last-merged package
					# is protected, and the rest are selected
		numselected = len(all_selected)
		if global_unmerge and not numselected:
			portage.writemsg_stdout("\n>>> No outdated packages were found on your system.\n")
			return 1, {}
	
		if not numselected:
			portage.writemsg_stdout(
				"\n>>> No packages selected for removal by " + \
				unmerge_action + "\n")
			return 1, {}
	finally:
		if vdb_lock:
			vartree.dbapi.flush_cache()
			vartree.dbapi.unlock()

	# generate a list of package sets that are directly or indirectly listed in "selected",
	# as there is no persistent list of "installed" sets
	installed_sets = ["selected"]
	stop = False
	pos = 0
	while not stop:
		stop = True
		pos = len(installed_sets)
		for s in installed_sets[pos - 1:]:
			if s not in sets:
				continue
			candidates = [x[len(SETPREFIX):] for x in sets[s].getNonAtoms() if x.startswith(SETPREFIX)]
			if candidates:
				stop = False
				installed_sets += candidates
	installed_sets = [x for x in installed_sets if x not in root_config.setconfig.active]
	del stop, pos

	# we don't want to unmerge packages that are still listed in user-editable package sets
	# listed in "world" as they would be remerged on the next update of "world" or the 
	# relevant package sets.
	unknown_sets = set()
	for cp in range(len(pkgmap)):
		for cpv in pkgmap[cp]["selected"].copy():
			try:
				pkg = _pkg(cpv)
			except KeyError:
				# It could have been uninstalled
				# by a concurrent process.
				continue

			if unmerge_action != "clean" and root_config.root == "/":
				skip_pkg = False
				if portage.match_from_list(portage.const.PORTAGE_PACKAGE_ATOM, [pkg]):
					msg = ("Not unmerging package %s since there is no valid reason "
						"for Portage to unmerge itself.") % (pkg.cpv,)
					skip_pkg = True
				elif vartree.dbapi._dblink(cpv).isowner(portage._python_interpreter):
					msg = ("Not unmerging package %s since there is no valid reason "
						"for Portage to unmerge currently used Python interpreter.") % (pkg.cpv,)
					skip_pkg = True
				if skip_pkg:
					for line in textwrap.wrap(msg, 75):
						out.eerror(line)
					# adjust pkgmap so the display output is correct
					pkgmap[cp]["selected"].remove(cpv)
					all_selected.remove(cpv)
					pkgmap[cp]["protected"].add(cpv)
					continue

			parents = []
			for s in installed_sets:
				# skip sets that the user requested to unmerge, and skip world 
				# user-selected set, since the package will be removed from
				# that set later on.
				if s in root_config.setconfig.active or s == "selected":
					continue

				if s not in sets:
					if s in unknown_sets:
						continue
					unknown_sets.add(s)
					out = portage.output.EOutput()
					out.eerror(("Unknown set '@%s' in %s%s") % \
						(s, root_config.settings['EROOT'], portage.const.WORLD_SETS_FILE))
					continue

				# only check instances of EditablePackageSet as other classes are generally used for
				# special purposes and can be ignored here (and are usually generated dynamically, so the
				# user can't do much about them anyway)
				if isinstance(sets[s], EditablePackageSet):

					# This is derived from a snippet of code in the
					# depgraph._iter_atoms_for_pkg() method.
					for atom in sets[s].iterAtomsForPackage(pkg):
						inst_matches = vartree.dbapi.match(atom)
						inst_matches.reverse() # descending order
						higher_slot = None
						for inst_cpv in inst_matches:
							try:
								inst_pkg = _pkg(inst_cpv)
							except KeyError:
								# It could have been uninstalled
								# by a concurrent process.
								continue

							if inst_pkg.cp != atom.cp:
								continue
							if pkg >= inst_pkg:
								# This is descending order, and we're not
								# interested in any versions <= pkg given.
								break
							if pkg.slot_atom != inst_pkg.slot_atom:
								higher_slot = inst_pkg
								break
						if higher_slot is None:
							parents.append(s)
							break
			if parents:
				print(colorize("WARN", "Package %s is going to be unmerged," % cpv))
				print(colorize("WARN", "but still listed in the following package sets:"))
				print("    %s\n" % ", ".join(parents))

	del installed_sets

	numselected = len(all_selected)
	if not numselected:
		writemsg_level(
			"\n>>> No packages selected for removal by " + \
			unmerge_action + "\n")
		return 1, {}

	# Unmerge order only matters in some cases
	if not ordered:
		unordered = {}
		for d in pkgmap:
			selected = d["selected"]
			if not selected:
				continue
			cp = portage.cpv_getkey(next(iter(selected)))
			cp_dict = unordered.get(cp)
			if cp_dict is None:
				cp_dict = {}
				unordered[cp] = cp_dict
				for k in d:
					cp_dict[k] = set()
			for k, v in d.items():
				cp_dict[k].update(v)
		pkgmap = [unordered[cp] for cp in sorted(unordered)]

	for x in range(len(pkgmap)):
		selected = pkgmap[x]["selected"]
		if not selected:
			continue
		for mytype, mylist in pkgmap[x].items():
			if mytype == "selected":
				continue
			mylist.difference_update(all_selected)
		cp = portage.cpv_getkey(next(iter(selected)))
		for y in localtree.dep_match(cp):
			if y not in pkgmap[x]["omitted"] and \
				y not in pkgmap[x]["selected"] and \
				y not in pkgmap[x]["protected"] and \
				y not in all_selected:
				pkgmap[x]["omitted"].add(y)
		if global_unmerge and not pkgmap[x]["selected"]:
			#avoid cluttering the preview printout with stuff that isn't getting unmerged
			continue
		if not (pkgmap[x]["protected"] or pkgmap[x]["omitted"]) and cp in syslist:
			virt_cp = sys_virt_map.get(cp)
			if virt_cp is None:
				cp_info = "'%s'" % (cp,)
			else:
				cp_info = "'%s' (%s)" % (cp, virt_cp)
			writemsg_level(colorize("BAD","\n\n!!! " + \
				"%s is part of your system profile.\n" % (cp_info,)),
				level=logging.WARNING, noiselevel=-1)
			writemsg_level(colorize("WARN","!!! Unmerging it may " + \
				"be damaging to your system.\n\n"),
				level=logging.WARNING, noiselevel=-1)
		if not quiet:
			writemsg_level("\n %s\n" % (bold(cp),), noiselevel=-1)
		else:
			writemsg_level(bold(cp) + ": ", noiselevel=-1)
		for mytype in ["selected","protected","omitted"]:
			if not quiet:
				writemsg_level((mytype + ": ").rjust(14), noiselevel=-1)
			if pkgmap[x][mytype]:
				sorted_pkgs = []
				for mypkg in pkgmap[x][mytype]:
					try:
						sorted_pkgs.append(mypkg.cpv)
					except AttributeError:
						sorted_pkgs.append(_pkg_str(mypkg))
				sorted_pkgs.sort(key=cpv_sort_key())
				for mypkg in sorted_pkgs:
					if mytype == "selected":
						writemsg_level(
							colorize("UNMERGE_WARN", mypkg.version + " "),
							noiselevel=-1)
					else:
						writemsg_level(
							colorize("GOOD", mypkg.version + " "),
							noiselevel=-1)
			else:
				writemsg_level("none ", noiselevel=-1)
			if not quiet:
				writemsg_level("\n", noiselevel=-1)
		if quiet:
			writemsg_level("\n", noiselevel=-1)

	writemsg_level("\nAll selected packages: %s\n" % " ".join(all_selected), noiselevel=-1)

	writemsg_level("\n>>> " + colorize("UNMERGE_WARN", "'Selected'") + \
		" packages are slated for removal.\n")
	writemsg_level(">>> " + colorize("GOOD", "'Protected'") + \
			" and " + colorize("GOOD", "'omitted'") + \
			" packages will not be removed.\n\n")

	return os.EX_OK, pkgmap
示例#4
0
def _unmerge_display(root_config,
                     myopts,
                     unmerge_action,
                     unmerge_files,
                     clean_delay=1,
                     ordered=0,
                     writemsg_level=portage.util.writemsg_level):
    """
	Returns a tuple of (returncode, pkgmap) where returncode is
	os.EX_OK if no errors occur, and 1 otherwise.
	"""

    quiet = "--quiet" in myopts
    settings = root_config.settings
    sets = root_config.sets
    vartree = root_config.trees["vartree"]
    candidate_catpkgs = []
    global_unmerge = 0
    out = portage.output.EOutput()
    pkg_cache = {}
    db_keys = list(vartree.dbapi._aux_cache_keys)

    def _pkg(cpv):
        pkg = pkg_cache.get(cpv)
        if pkg is None:
            pkg = Package(built=True,
                          cpv=cpv,
                          installed=True,
                          metadata=zip(db_keys,
                                       vartree.dbapi.aux_get(cpv, db_keys)),
                          operation="uninstall",
                          root_config=root_config,
                          type_name="installed")
            pkg_cache[cpv] = pkg
        return pkg

    vdb_path = os.path.join(settings["EROOT"], portage.VDB_PATH)
    try:
        # At least the parent needs to exist for the lock file.
        portage.util.ensure_dirs(vdb_path)
    except portage.exception.PortageException:
        pass
    vdb_lock = None
    try:
        if os.access(vdb_path, os.W_OK):
            vartree.dbapi.lock()
            vdb_lock = True

        realsyslist = []
        sys_virt_map = {}
        for x in sets["system"].getAtoms():
            for atom in expand_new_virt(vartree.dbapi, x):
                if not atom.blocker:
                    realsyslist.append(atom)
                    if atom.cp != x.cp:
                        sys_virt_map[atom.cp] = x.cp

        syslist = []
        for x in realsyslist:
            mycp = x.cp
            # Since Gentoo stopped using old-style virtuals in
            # 2011, typically it's possible to avoid getvirtuals()
            # calls entirely. It will not be triggered here by
            # new-style virtuals since those are expanded to
            # non-virtual atoms above by expand_new_virt().
            if mycp.startswith("virtual/") and \
             mycp in settings.getvirtuals():
                providers = []
                for provider in settings.getvirtuals()[mycp]:
                    if vartree.dbapi.match(provider):
                        providers.append(provider)
                if len(providers) == 1:
                    syslist.extend(providers)
            else:
                syslist.append(mycp)
        syslist = frozenset(syslist)

        if not unmerge_files:
            if unmerge_action in ["rage-clean", "unmerge"]:
                print()
                print(
                    bold("emerge %s" % unmerge_action) +
                    " can only be used with specific package names")
                print()
                return 1, {}

            global_unmerge = 1

        # process all arguments and add all
        # valid db entries to candidate_catpkgs
        if global_unmerge:
            if not unmerge_files:
                candidate_catpkgs.extend(vartree.dbapi.cp_all())
        else:
            #we've got command-line arguments
            if not unmerge_files:
                print("\nNo packages to %s have been provided.\n" %
                      unmerge_action)
                return 1, {}
            for x in unmerge_files:
                arg_parts = x.split('/')
                if x[0] not in [".","/"] and \
                 arg_parts[-1][-7:] != ".ebuild":
                    #possible cat/pkg or dep; treat as such
                    candidate_catpkgs.append(x)
                elif unmerge_action in ["prune", "clean"]:
                    print("\n!!! Prune and clean do not accept individual" + \
                     " ebuilds as arguments;\n    skipping.\n")
                    continue
                else:
                    # it appears that the user is specifying an installed
                    # ebuild and we're in "unmerge" mode, so it's ok.
                    if not os.path.exists(x):
                        print("\n!!! The path '" + x + "' doesn't exist.\n")
                        return 1, {}

                    absx = os.path.abspath(x)
                    sp_absx = absx.split("/")
                    if sp_absx[-1][-7:] == ".ebuild":
                        del sp_absx[-1]
                        absx = "/".join(sp_absx)

                    sp_absx_len = len(sp_absx)

                    vdb_path = os.path.join(settings["EROOT"],
                                            portage.VDB_PATH)

                    sp_vdb = vdb_path.split("/")
                    sp_vdb_len = len(sp_vdb)

                    if not os.path.exists(absx + "/CONTENTS"):
                        print("!!! Not a valid db dir: " + str(absx))
                        return 1, {}

                    if sp_absx_len <= sp_vdb_len:
                        # The Path is shorter... so it can't be inside the vdb.
                        print(sp_absx)
                        print(absx)
                        print("\n!!!",x,"cannot be inside "+ \
                         vdb_path+"; aborting.\n")
                        return 1, {}

                    for idx in range(0, sp_vdb_len):
                        if idx >= sp_absx_len or sp_vdb[idx] != sp_absx[idx]:
                            print(sp_absx)
                            print(absx)
                            print("\n!!!", x, "is not inside "+\
                             vdb_path+"; aborting.\n")
                            return 1, {}

                    print("=" + "/".join(sp_absx[sp_vdb_len:]))
                    candidate_catpkgs.append("=" +
                                             "/".join(sp_absx[sp_vdb_len:]))

        newline = ""
        if not quiet:
            newline = "\n"
        if settings["ROOT"] != "/":
            writemsg_level(darkgreen(newline+ \
             ">>> Using system located in ROOT tree %s\n" % \
             settings["ROOT"]))

        if ("--pretend" in myopts or "--ask" in myopts) and not quiet:
            writemsg_level(darkgreen(newline+\
             ">>> These are the packages that would be unmerged:\n"))

        # Preservation of order is required for --depclean and --prune so
        # that dependencies are respected. Use all_selected to eliminate
        # duplicate packages since the same package may be selected by
        # multiple atoms.
        pkgmap = []
        all_selected = set()
        for x in candidate_catpkgs:
            # cycle through all our candidate deps and determine
            # what will and will not get unmerged
            try:
                mymatch = vartree.dbapi.match(x)
            except portage.exception.AmbiguousPackageName as errpkgs:
                print("\n\n!!! The short ebuild name \"" + \
                 x + "\" is ambiguous.  Please specify")
                print("!!! one of the following fully-qualified " + \
                 "ebuild names instead:\n")
                for i in errpkgs[0]:
                    print("    " + green(i))
                print()
                sys.exit(1)

            if not mymatch and x[0] not in "<>=~":
                mymatch = vartree.dep_match(x)
            if not mymatch:
                portage.writemsg("\n--- Couldn't find '%s' to %s.\n" % \
                 (x.replace("null/", ""), unmerge_action), noiselevel=-1)
                continue

            pkgmap.append({
                "protected": set(),
                "selected": set(),
                "omitted": set()
            })
            mykey = len(pkgmap) - 1
            if unmerge_action in ["rage-clean", "unmerge"]:
                for y in mymatch:
                    if y not in all_selected:
                        pkgmap[mykey]["selected"].add(y)
                        all_selected.add(y)
            elif unmerge_action == "prune":
                if len(mymatch) == 1:
                    continue
                best_version = mymatch[0]
                best_slot = vartree.getslot(best_version)
                best_counter = vartree.dbapi.cpv_counter(best_version)
                for mypkg in mymatch[1:]:
                    myslot = vartree.getslot(mypkg)
                    mycounter = vartree.dbapi.cpv_counter(mypkg)
                    if (myslot == best_slot and mycounter > best_counter) or \
                     mypkg == portage.best([mypkg, best_version]):
                        if myslot == best_slot:
                            if mycounter < best_counter:
                                # On slot collision, keep the one with the
                                # highest counter since it is the most
                                # recently installed.
                                continue
                        best_version = mypkg
                        best_slot = myslot
                        best_counter = mycounter
                pkgmap[mykey]["protected"].add(best_version)
                pkgmap[mykey]["selected"].update(mypkg for mypkg in mymatch \
                 if mypkg != best_version and mypkg not in all_selected)
                all_selected.update(pkgmap[mykey]["selected"])
            else:
                # unmerge_action == "clean"
                slotmap = {}
                for mypkg in mymatch:
                    if unmerge_action == "clean":
                        myslot = vartree.getslot(mypkg)
                    else:
                        # since we're pruning, we don't care about slots
                        # and put all the pkgs in together
                        myslot = 0
                    if myslot not in slotmap:
                        slotmap[myslot] = {}
                    slotmap[myslot][vartree.dbapi.cpv_counter(mypkg)] = mypkg

                for mypkg in vartree.dbapi.cp_list(
                        portage.cpv_getkey(mymatch[0])):
                    myslot = vartree.getslot(mypkg)
                    if myslot not in slotmap:
                        slotmap[myslot] = {}
                    slotmap[myslot][vartree.dbapi.cpv_counter(mypkg)] = mypkg

                for myslot in slotmap:
                    counterkeys = list(slotmap[myslot])
                    if not counterkeys:
                        continue
                    counterkeys.sort()
                    pkgmap[mykey]["protected"].add(
                        slotmap[myslot][counterkeys[-1]])
                    del counterkeys[-1]

                    for counter in counterkeys[:]:
                        mypkg = slotmap[myslot][counter]
                        if mypkg not in mymatch:
                            counterkeys.remove(counter)
                            pkgmap[mykey]["protected"].add(
                                slotmap[myslot][counter])

                    #be pretty and get them in order of merge:
                    for ckey in counterkeys:
                        mypkg = slotmap[myslot][ckey]
                        if mypkg not in all_selected:
                            pkgmap[mykey]["selected"].add(mypkg)
                            all_selected.add(mypkg)
                    # ok, now the last-merged package
                    # is protected, and the rest are selected
        numselected = len(all_selected)
        if global_unmerge and not numselected:
            portage.writemsg_stdout(
                "\n>>> No outdated packages were found on your system.\n")
            return 1, {}

        if not numselected:
            portage.writemsg_stdout(
             "\n>>> No packages selected for removal by " + \
             unmerge_action + "\n")
            return 1, {}
    finally:
        if vdb_lock:
            vartree.dbapi.flush_cache()
            vartree.dbapi.unlock()

    # generate a list of package sets that are directly or indirectly listed in "selected",
    # as there is no persistent list of "installed" sets
    installed_sets = ["selected"]
    stop = False
    pos = 0
    while not stop:
        stop = True
        pos = len(installed_sets)
        for s in installed_sets[pos - 1:]:
            if s not in sets:
                continue
            candidates = [
                x[len(SETPREFIX):] for x in sets[s].getNonAtoms()
                if x.startswith(SETPREFIX)
            ]
            if candidates:
                stop = False
                installed_sets += candidates
    installed_sets = [
        x for x in installed_sets if x not in root_config.setconfig.active
    ]
    del stop, pos

    # we don't want to unmerge packages that are still listed in user-editable package sets
    # listed in "world" as they would be remerged on the next update of "world" or the
    # relevant package sets.
    unknown_sets = set()
    for cp in range(len(pkgmap)):
        for cpv in pkgmap[cp]["selected"].copy():
            try:
                pkg = _pkg(cpv)
            except KeyError:
                # It could have been uninstalled
                # by a concurrent process.
                continue

            if unmerge_action != "clean" and root_config.root == "/":
                skip_pkg = False
                if portage.match_from_list(portage.const.PORTAGE_PACKAGE_ATOM,
                                           [pkg]):
                    msg = ("Not unmerging package %s "
                           "since there is no valid reason for Portage to "
                           "%s itself.") % (pkg.cpv, unmerge_action)
                    skip_pkg = True
                elif vartree.dbapi._dblink(cpv).isowner(
                        portage._python_interpreter):
                    msg = ("Not unmerging package %s since there is no valid "
                           "reason for Portage to %s currently used Python "
                           "interpreter.") % (pkg.cpv, unmerge_action)
                    skip_pkg = True
                if skip_pkg:
                    for line in textwrap.wrap(msg, 75):
                        out.eerror(line)
                    # adjust pkgmap so the display output is correct
                    pkgmap[cp]["selected"].remove(cpv)
                    all_selected.remove(cpv)
                    pkgmap[cp]["protected"].add(cpv)
                    continue

            parents = []
            for s in installed_sets:
                # skip sets that the user requested to unmerge, and skip world
                # user-selected set, since the package will be removed from
                # that set later on.
                if s in root_config.setconfig.active or s == "selected":
                    continue

                if s not in sets:
                    if s in unknown_sets:
                        continue
                    unknown_sets.add(s)
                    out = portage.output.EOutput()
                    out.eerror(("Unknown set '@%s' in %s%s") % \
                     (s, root_config.settings['EROOT'], portage.const.WORLD_SETS_FILE))
                    continue

                # only check instances of EditablePackageSet as other classes are generally used for
                # special purposes and can be ignored here (and are usually generated dynamically, so the
                # user can't do much about them anyway)
                if isinstance(sets[s], EditablePackageSet):

                    # This is derived from a snippet of code in the
                    # depgraph._iter_atoms_for_pkg() method.
                    for atom in sets[s].iterAtomsForPackage(pkg):
                        inst_matches = vartree.dbapi.match(atom)
                        inst_matches.reverse()  # descending order
                        higher_slot = None
                        for inst_cpv in inst_matches:
                            try:
                                inst_pkg = _pkg(inst_cpv)
                            except KeyError:
                                # It could have been uninstalled
                                # by a concurrent process.
                                continue

                            if inst_pkg.cp != atom.cp:
                                continue
                            if pkg >= inst_pkg:
                                # This is descending order, and we're not
                                # interested in any versions <= pkg given.
                                break
                            if pkg.slot_atom != inst_pkg.slot_atom:
                                higher_slot = inst_pkg
                                break
                        if higher_slot is None:
                            parents.append(s)
                            break
            if parents:
                print(
                    colorize("WARN",
                             "Package %s is going to be unmerged," % cpv))
                print(
                    colorize(
                        "WARN",
                        "but still listed in the following package sets:"))
                print("    %s\n" % ", ".join(parents))

    del installed_sets

    numselected = len(all_selected)
    if not numselected:
        writemsg_level(
         "\n>>> No packages selected for removal by " + \
         unmerge_action + "\n")
        return 1, {}

    # Unmerge order only matters in some cases
    if not ordered:
        unordered = {}
        for d in pkgmap:
            selected = d["selected"]
            if not selected:
                continue
            cp = portage.cpv_getkey(next(iter(selected)))
            cp_dict = unordered.get(cp)
            if cp_dict is None:
                cp_dict = {}
                unordered[cp] = cp_dict
                for k in d:
                    cp_dict[k] = set()
            for k, v in d.items():
                cp_dict[k].update(v)
        pkgmap = [unordered[cp] for cp in sorted(unordered)]

    # Sort each set of selected packages
    if ordered:
        for pkg in pkgmap:
            pkg["selected"] = sorted(pkg["selected"], key=cpv_sort_key())

    for x in range(len(pkgmap)):
        selected = pkgmap[x]["selected"]
        if not selected:
            continue
        for mytype, mylist in pkgmap[x].items():
            if mytype == "selected":
                continue
            mylist.difference_update(all_selected)
        cp = portage.cpv_getkey(next(iter(selected)))
        for y in vartree.dep_match(cp):
            if y not in pkgmap[x]["omitted"] and \
             y not in pkgmap[x]["selected"] and \
             y not in pkgmap[x]["protected"] and \
             y not in all_selected:
                pkgmap[x]["omitted"].add(y)
        if global_unmerge and not pkgmap[x]["selected"]:
            #avoid cluttering the preview printout with stuff that isn't getting unmerged
            continue
        if not (pkgmap[x]["protected"]
                or pkgmap[x]["omitted"]) and cp in syslist:
            virt_cp = sys_virt_map.get(cp)
            if virt_cp is None:
                cp_info = "'%s'" % (cp, )
            else:
                cp_info = "'%s' (%s)" % (cp, virt_cp)
            writemsg_level(colorize("BAD","\n\n!!! " + \
             "%s is part of your system profile.\n" % (cp_info,)),
             level=logging.WARNING, noiselevel=-1)
            writemsg_level(colorize("WARN","!!! Unmerging it may " + \
             "be damaging to your system.\n\n"),
             level=logging.WARNING, noiselevel=-1)
        if not quiet:
            writemsg_level("\n %s\n" % (bold(cp), ), noiselevel=-1)
        else:
            writemsg_level(bold(cp) + ": ", noiselevel=-1)
        for mytype in ["selected", "protected", "omitted"]:
            if not quiet:
                writemsg_level((mytype + ": ").rjust(14), noiselevel=-1)
            if pkgmap[x][mytype]:
                sorted_pkgs = []
                for mypkg in pkgmap[x][mytype]:
                    try:
                        sorted_pkgs.append(mypkg.cpv)
                    except AttributeError:
                        sorted_pkgs.append(_pkg_str(mypkg))
                sorted_pkgs.sort(key=cpv_sort_key())
                for mypkg in sorted_pkgs:
                    if mytype == "selected":
                        writemsg_level(colorize("UNMERGE_WARN",
                                                mypkg.version + " "),
                                       noiselevel=-1)
                    else:
                        writemsg_level(colorize("GOOD", mypkg.version + " "),
                                       noiselevel=-1)
            else:
                writemsg_level("none ", noiselevel=-1)
            if not quiet:
                writemsg_level("\n", noiselevel=-1)
        if quiet:
            writemsg_level("\n", noiselevel=-1)

    writemsg_level("\nAll selected packages: %s\n" %
                   " ".join('=%s' % x for x in all_selected),
                   noiselevel=-1)

    writemsg_level("\n>>> " + colorize("UNMERGE_WARN", "'Selected'") + \
     " packages are slated for removal.\n")
    writemsg_level(">>> " + colorize("GOOD", "'Protected'") + \
      " and " + colorize("GOOD", "'omitted'") + \
      " packages will not be removed.\n\n")

    return os.EX_OK, pkgmap