From: Fabian Groffen Date: Sat, 23 Feb 2008 23:37:49 +0000 (-0000) Subject: Merged from trunk 9335:9351 X-Git-Url: http://git.tremily.us/gitweb.cgi?a=commitdiff_plain;h=cf5031e82364f979b746160039dfd67156a1e534;p=portage.git Merged from trunk 9335:9351 | 9336 | Fix package selection logic in order to prevent downgrade in | | zmedico | cases when selective is True and the currently installed | | | version does not have a matching ebuild in the tree. For | | | example, this type of situation is likely to occur if the | | | user somehow obtains a binary package that has a newer | | | version than any of the ebuilds in the portage tree. If | | | package maintainers want to encourage a downgrade in a case | | | like this then they should use package.mask so that the user | | | receives a warning about the installed package being masked. | | 9337 | Fix --newuse reinstall logic broken by the previous commit. | | zmedico | | | 9338 | Update the DepPriority documentation so that the table shows | | zmedico | all the currently supported priorities and categories. | | 9339 | Bug #201045 - Use a topological sort to create an unmerge | | zmedico | order such that each package is unmerged before it's | | | dependencies. This is necessary to avoid breaking things | | | that may need to run during pkg_prerm or pkg_postrm phases. | | 9340 | Make depclean code filter out blocker atoms where | | zmedico | appropriate. | | 9341 | Fix depclean breakage so that it doesn't clean some packages | | zmedico | that shouldn't be cleaned. | | 9342 | Add missing % x (bug #208946, comment #2). | | zmedico | | | 9343 | Fix reversed order of parent/child parameders to | | zmedico | digraph.add(). | | 9344 | Remove IUSE.invalid from qawarnings since there's no reason | | zmedico | for it not to be fatal. Thanks to Betelgeuse. | | 9345 | In the action_depclean() topological sort, only add packages | | zmedico | to the graph if they are being removed. | | 9346 | Bug #201045 - Make unmerge() preserve the unmerge order of | | zmedico | packages given to it by the user or depclean. | | 9347 | Make sure all packages to be pruned or depcleaned are added | | zmedico | to the graph, even when they don't have any dependencies. | | 9348 | Now that unmerge() preserves the order that it's given, if | | zmedico | two different slots of the same package are being unmerged | | | then they are not necessarily unmerged consecutively. To | | | make the display less confusing, never display packages as | | | "protected" or "omitted" when they actually belong to the | | | overall set of "selected" packages to be unmerged. | | 9349 | Fix unmerge() breakage from due to pkgmap being changed from | | zmedico | a dict to a list. | | 9350 | Fix the --depclean/--prune code to use visible() for | | zmedico | visibility checks instead of the old portdbapi hack. | | 9351 | Make LibraryConsumerSet.mapPathsToAtoms() produce slot atoms | | zmedico | so that atoms aren't restricted to the currently installed | | | version, making upgrades possible. | svn path=/main/branches/prefix/; revision=9374 --- diff --git a/bin/repoman b/bin/repoman index ba8f27b31..ae1dc01d3 100755 --- a/bin/repoman +++ b/bin/repoman @@ -320,7 +320,6 @@ qawarnings=[ "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev", "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev", "DESCRIPTION.toolong", -"IUSE.invalid", "KEYWORDS.stupid", "KEYWORDS.missing", "RESTRICT.invalid", diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index f6c57ec2f..f2015dfbf 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -840,7 +840,38 @@ def filter_iuse_defaults(iuse): else: yield flag -class DepPriority(object): +class AbstractDepPriority(object): + __slots__ = ("__weakref__", "buildtime", "runtime", "runtime_post") + def __init__(self, **kwargs): + for myattr in chain(self.__slots__, AbstractDepPriority.__slots__): + if myattr == "__weakref__": + continue + myvalue = kwargs.get(myattr, False) + setattr(self, myattr, myvalue) + + def __lt__(self, other): + return self.__int__() < other + + def __le__(self, other): + return self.__int__() <= other + + def __eq__(self, other): + return self.__int__() == other + + def __ne__(self, other): + return self.__int__() != other + + def __gt__(self, other): + return self.__int__() > other + + def __ge__(self, other): + return self.__int__() >= other + + def copy(self): + import copy + return copy.copy(self) + +class DepPriority(AbstractDepPriority): """ This class generates an integer priority level based of various attributes of the dependency relationship. Attributes can be assigned @@ -851,13 +882,16 @@ class DepPriority(object): "buildtime", "runtime", and "system". Various combinations of attributes lead to the following priority levels: - Combination of properties Priority level + Combination of properties Priority Category - not satisfied and buildtime 0 - not satisfied and runtime -1 - satisfied and buildtime -2 - satisfied and runtime -3 - (none of the above) -4 + not satisfied and buildtime 0 HARD + not satisfied and runtime -1 MEDIUM + not satisfied and runtime_post -2 MEDIUM_SOFT + satisfied and buildtime and rebuild -3 SOFT + satisfied and buildtime -4 SOFT + satisfied and runtime -5 SOFT + satisfied and runtime_post -6 SOFT + (none of the above) -6 SOFT Several integer constants are defined for categorization of priority levels: @@ -867,17 +901,12 @@ class DepPriority(object): SOFT The upper boundary for soft dependencies. MIN The lower boundary for soft dependencies. """ - __slots__ = ("__weakref__", "satisfied", "buildtime", "runtime", "runtime_post", "rebuild") + __slots__ = ("satisfied", "rebuild") MEDIUM = -1 MEDIUM_SOFT = -2 SOFT = -3 MIN = -6 - def __init__(self, **kwargs): - for myattr in self.__slots__: - if myattr == "__weakref__": - continue - myvalue = kwargs.get(myattr, False) - setattr(self, myattr, myvalue) + def __int__(self): if not self.satisfied: if self.buildtime: @@ -895,21 +924,7 @@ class DepPriority(object): if self.runtime_post: return -6 return -6 - def __lt__(self, other): - return self.__int__() < other - def __le__(self, other): - return self.__int__() <= other - def __eq__(self, other): - return self.__int__() == other - def __ne__(self, other): - return self.__int__() != other - def __gt__(self, other): - return self.__int__() > other - def __ge__(self, other): - return self.__int__() >= other - def copy(self): - import copy - return copy.copy(self) + def __str__(self): myvalue = self.__int__() if myvalue > self.MEDIUM: @@ -920,6 +935,35 @@ class DepPriority(object): return "medium-soft" return "soft" +class UnmergeDepPriority(AbstractDepPriority): + """ + Combination of properties Priority Category + + runtime 0 HARD + runtime_post -1 HARD + buildtime -2 SOFT + (none of the above) -2 SOFT + """ + + MAX = 0 + SOFT = -2 + MIN = -2 + + def __int__(self): + if self.runtime: + return 0 + if self.runtime_post: + return -1 + if self.buildtime: + return -2 + return -2 + + def __str__(self): + myvalue = self.__int__() + if myvalue > self.SOFT: + return "hard" + return "soft" + class FakeVartree(portage.vartree): """This is implements an in-memory copy of a vartree instance that provides all the interfaces required for use by the depgraph. The vardb is locked @@ -2527,6 +2571,7 @@ class depgraph(object): usepkgonly = "--usepkgonly" in self.myopts empty = "empty" in self.myparams selective = "selective" in self.myparams + reinstall = False # Behavior of the "selective" parameter depends on # whether or not a package matches an argument atom. # If an installed package provides an old-style @@ -2546,9 +2591,12 @@ class depgraph(object): if existing_node: break if installed and not find_existing_node and \ + (reinstall or not selective) and \ (matched_packages or empty): - # We only need to select an installed package here - # if there is no other choice. + # We only need to select an installed package in the + # following cases: + # 1) there is no other choice + # 2) selective is True continue if hasattr(db, "xmatch"): cpv_list = db.xmatch("match-all", atom) @@ -2656,6 +2704,8 @@ class depgraph(object): self._reinstall_for_flags( forced_flags, old_use, old_iuse, cur_use, cur_iuse) + if reinstall_for_flags: + reinstall = True if not installed: must_reinstall = empty or \ (myarg and not selective) @@ -4944,7 +4994,7 @@ def unmerge(root_config, myopts, unmerge_action, print darkgreen(newline+\ ">>> These are the packages that would be unmerged:") - pkgmap={} + pkgmap = [] numselected=0 for x in candidate_catpkgs: # cycle through all our candidate deps and determine @@ -4970,11 +5020,8 @@ def unmerge(root_config, myopts, unmerge_action, portage.writemsg("\n--- Couldn't find '%s' to %s.\n" % \ (x, unmerge_action), noiselevel=-1) continue - mykey = portage.key_expand( - portage.dep_getkey( - mymatch[0]), mydb=vartree.dbapi, settings=settings) - if not pkgmap.has_key(mykey): - pkgmap[mykey]={"protected":[], "selected":[], "omitted":[] } + pkgmap.append({"protected":[], "selected":[], "omitted":[] }) + mykey = len(pkgmap) - 1 if unmerge_action=="unmerge": for y in mymatch: if y not in pkgmap[mykey]["selected"]: @@ -5044,25 +5091,38 @@ def unmerge(root_config, myopts, unmerge_action, finally: if vdb_lock: portage.locks.unlockdir(vdb_lock) + all_selected = set() for x in pkgmap: - for y in localtree.dep_match(x): + all_selected.update(x["selected"]) + for x in xrange(len(pkgmap)): + selected = pkgmap[x]["selected"] + if not selected: + continue + for mytype, mylist in pkgmap[x].iteritems(): + if mytype == "selected": + continue + pkgmap[x][mytype] = \ + [cpv for cpv in mylist if cpv not in all_selected] + cp = portage.cpv_getkey(selected[0]) + 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"]: + y not in pkgmap[x]["protected"] and \ + y not in all_selected: pkgmap[x]["omitted"].append(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 (x in syslist): - print colorize("BAD","\a\n\n!!! '%s' is part of your system profile." % x) + if not (pkgmap[x]["protected"] or pkgmap[x]["omitted"]) and cp in syslist: + print colorize("BAD","\a\n\n!!! '%s' is part of your system profile." % cp) print colorize("WARN","\a!!! Unmerging it may be damaging to your system.\n") if "--pretend" not in myopts and "--ask" not in myopts: countdown(int(settings["EMERGE_WARNING_DELAY"]), colorize("UNMERGE_WARN", "Press Ctrl-C to Stop")) if "--quiet" not in myopts: - print "\n "+white(x) + print "\n "+bold(cp) else: - print white(x)+": ", + print bold(cp)+": ", for mytype in ["selected","protected","omitted"]: if "--quiet" not in myopts: portage.writemsg_stdout((mytype + ": ").rjust(14), noiselevel=-1) @@ -5109,7 +5169,7 @@ def unmerge(root_config, myopts, unmerge_action, if not autoclean: countdown(int(settings["CLEAN_DELAY"]), ">>> Unmerging") - for x in pkgmap: + for x in xrange(len(pkgmap)): for y in pkgmap[x]["selected"]: print ">>> Unmerging "+y+"..." emergelog(xterm_titles, "=== Unmerging... ("+y+")") @@ -6413,23 +6473,32 @@ def action_depclean(settings, trees, ldpath_mtimes, if "--quiet" not in myopts: print "\nCalculating dependencies ", - soft = 0 - hard = 1 + runtime = UnmergeDepPriority(runtime=True) + runtime_post = UnmergeDepPriority(runtime_post=True) + buildtime = UnmergeDepPriority(buildtime=True) + + priority_map = { + "RDEPEND": runtime, + "PDEPEND": runtime_post, + "DEPEND": buildtime, + } + remaining_atoms = [] if action == "depclean": for atom in worldlist: if vardb.match(atom): - remaining_atoms.append((atom, 'world', hard)) + remaining_atoms.append((atom, 'world', runtime)) for atom in syslist: if vardb.match(atom): - remaining_atoms.append((atom, 'system', hard)) + remaining_atoms.append((atom, 'system', runtime)) elif action == "prune": for atom in syslist: if vardb.match(atom): - remaining_atoms.append((atom, 'system', hard)) + 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', hard) for atom in vardb.cp_all()) + remaining_atoms.extend( + (atom, 'world', runtime) for atom in vardb.cp_all()) if not myfiles: # Try to prune everything that's slotted. for cp in vardb.cp_all(): @@ -6438,14 +6507,15 @@ def action_depclean(settings, trees, ldpath_mtimes, unresolveable = {} aux_keys = ["DEPEND", "RDEPEND", "PDEPEND"] - metadata_keys = ["PROVIDE", "SLOT", "USE"] + 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 not atom.startswith("!") and priority == hard: + if priority > UnmergeDepPriority.SOFT: unresolveable.setdefault(atom, []).append(parent) continue if action == "depclean" and parent == "world" and myfiles: @@ -6470,44 +6540,42 @@ def action_depclean(settings, trees, ldpath_mtimes, filtered_pkgs.append(pkg) pkgs = filtered_pkgs if len(pkgs) > 1: - # Prune all but the best matching slot, since that's all that a - # deep world update would pull in. Don't prune if this atom comes - # directly from world though, since world atoms are greedy when - # they don't specify a slot. - visible_in_portdb = [cpv for cpv in pkgs if portdb.match("="+cpv)] - if visible_in_portdb: - # For consistency with the update algorithm, keep the highest - # visible version and prune any versions that are either masked - # or no longer exist in the portage tree. - pkgs = visible_in_portdb - pkgs = [portage.best(pkgs)] + # 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): + metadata = dict(izip(metadata_keys, + vardb.aux_get(cpv, metadata_keys))) + if visible(settings, cpv, metadata, + built=True, installed=True): + 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) + graph.add(pkg, parent, priority=priority) if fakedb.cpv_exists(pkg): continue spinner.update() fakedb.cpv_inject(pkg) myaux = dict(izip(aux_keys, vardb.aux_get(pkg, aux_keys))) mydeps = [] - if myopts.get("--with-bdeps", "y") == "y": - mydeps.append((myaux["DEPEND"], soft)) - del myaux["DEPEND"] - mydeps.append((" ".join(myaux.values()), hard)) + usedef = vardb.aux_get(pkg, ["USE"])[0].split() - for depstr, priority in mydeps: + for dep_type, depstr in myaux.iteritems(): if not depstr: continue + if not with_bdeps and dep_type == "DEPEND": + continue + + priority = priority_map[dep_type] if "--debug" in myopts: print print "Parent: ", pkg print "Depstring:", depstr - print "Priority:", - if priority == soft: - print "soft" - else: - print "hard" + print "Priority:", priority try: portage.dep._dep_check_strict = False @@ -6525,6 +6593,8 @@ def action_depclean(settings, trees, ldpath_mtimes, print "Candidates:", atoms for atom in atoms: + if atom.startswith("!"): + continue remaining_atoms.append((atom, pkg, priority)) if "--quiet" not in myopts: @@ -6612,6 +6682,75 @@ def action_depclean(settings, trees, ldpath_mtimes, good("--nodeps")) if len(cleanlist): + # 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 + # during pkg_prerm or pkg_postrm phases. + + # Create a new graph to account for dependencies between the + # packages being unmerged. + graph = digraph() + clean_set = set(cleanlist) + del cleanlist[:] + for node in clean_set: + graph.add(node, None) + myaux = dict(izip(aux_keys, vardb.aux_get(node, aux_keys))) + mydeps = [] + usedef = vardb.aux_get(pkg, ["USE"])[0].split() + for dep_type, depstr in myaux.iteritems(): + 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) + finally: + portage.dep._dep_check_strict = True + if not success: + show_invalid_depstring_notice( + ("installed", myroot, node, "nomerge"), + depstr, atoms) + return + + priority = priority_map[dep_type] + for atom in atoms: + if atom.startswith("!"): + continue + matches = vardb.match(atom) + if not matches: + continue + for cpv in matches: + if cpv in clean_set: + graph.add(cpv, node, priority=priority) + + # Order nodes from lowest to highest overall reference count for + # optimal root node selection. + node_refcounts = {} + for node in graph.order: + node_refcounts[node] = len(graph.parent_nodes(node)) + def cmp_reference_count(node1, node2): + return node_refcounts[node1] - node_refcounts[node2] + graph.order.sort(cmp_reference_count) + + ignore_priority_range = [None] + ignore_priority_range.extend( + xrange(UnmergeDepPriority.MIN, UnmergeDepPriority.MAX + 1)) + while not graph.empty(): + for ignore_priority in ignore_priority_range: + nodes = graph.root_nodes(ignore_priority=ignore_priority) + if nodes: + break + if not nodes: + raise AssertionError("no root nodes") + if ignore_priority is not None: + # Some deps have been dropped due to circular dependencies, + # so only pop one node in order do minimize the number that + # are dropped. + del nodes[1:] + for node in nodes: + graph.remove(node) + cleanlist.append(node) + unmerge(root_config, myopts, "unmerge", cleanlist, ldpath_mtimes) diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index ae37127ab..d3ecacee0 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1601,7 +1601,7 @@ class dblink(object): for x in preserve_paths: print "injecting %s into %s" % (x, srcroot) if not os.path.exists(os.path.join(destroot, x.lstrip(os.sep))): - print "%s does not exist so can't be preserved" + print "%s does not exist so can't be preserved" % x missing_paths.append(x) continue mydir = os.path.join(srcroot, os.path.dirname(x).lstrip(os.sep)) diff --git a/pym/portage/sets/dbapi.py b/pym/portage/sets/dbapi.py index a1b902fdc..220ff2283 100644 --- a/pym/portage/sets/dbapi.py +++ b/pym/portage/sets/dbapi.py @@ -126,8 +126,10 @@ class LibraryConsumerSet(PackageSet): link = dblink(mysplit[0], mysplit[1], myroot=self.dbapi.root, \ mysettings=self.dbapi.settings, treetype='vartree', \ vartree=self.dbapi.vartree) - if paths.intersection(link.getcontents().keys()): - rValue.add("/".join(catpkgsplit(cpv)[:2])) + if paths.intersection(link.getcontents()): + cat, pn = catpkgsplit(cpv)[:2] + slot = self.dbapi.aux_get(cpv, ["SLOT"])[0] + rValue.add("%s/%s:%s" % (cat, pn, slot)) return rValue