diff options
author | Zac Medico <zmedico@gentoo.org> | 2010-02-25 21:15:35 +0000 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2010-02-25 21:15:35 +0000 |
commit | 55a8c061f34e15d58fb4d9889f0157c7b7e5b0c9 (patch) | |
tree | 0df455c4e890b4baa6502f9631b74ef94e8fe13a /pym/portage/dep/dep_check.py | |
parent | cec3c56e3ff699e83754ec2e6699efb123455e43 (diff) | |
download | portage-55a8c061f34e15d58fb4d9889f0157c7b7e5b0c9.tar.gz portage-55a8c061f34e15d58fb4d9889f0157c7b7e5b0c9.tar.bz2 portage-55a8c061f34e15d58fb4d9889f0157c7b7e5b0c9.zip |
Move portage.dep_check and related functions to portage.dep.dep_check.
svn path=/main/trunk/; revision=15462
Diffstat (limited to 'pym/portage/dep/dep_check.py')
-rw-r--r-- | pym/portage/dep/dep_check.py | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/pym/portage/dep/dep_check.py b/pym/portage/dep/dep_check.py new file mode 100644 index 000000000..1bc1e40dc --- /dev/null +++ b/pym/portage/dep/dep_check.py @@ -0,0 +1,634 @@ +# Copyright 2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +__all__ = ['dep_check', 'dep_eval', 'dep_wordreduce', 'dep_zapdeps'] + +import logging + +import portage +from portage.dep import Atom, dep_opconvert, match_from_list, paren_reduce, \ + remove_slot, use_reduce +from portage.exception import InvalidAtom, InvalidDependString, ParseError +from portage.localization import _ +from portage.util import writemsg, writemsg_level +from portage.versions import catpkgsplit, cpv_getkey, pkgcmp + +def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/", + trees=None, use_mask=None, use_force=None, **kwargs): + """ + In order to solve bug #141118, recursively expand new-style virtuals so + as to collapse one or more levels of indirection, generating an expanded + search space. In dep_zapdeps, new-style virtuals will be assigned + zero cost regardless of whether or not they are currently installed. Virtual + blockers are supported but only when the virtual expands to a single + atom because it wouldn't necessarily make sense to block all the components + of a compound virtual. When more than one new-style virtual is matched, + the matches are sorted from highest to lowest versions and the atom is + expanded to || ( highest match ... lowest match ).""" + newsplit = [] + mytrees = trees[myroot] + portdb = mytrees["porttree"].dbapi + atom_graph = mytrees.get("atom_graph") + parent = mytrees.get("parent") + virt_parent = mytrees.get("virt_parent") + graph_parent = None + eapi = None + if parent is not None: + if virt_parent is not None: + graph_parent = virt_parent + eapi = virt_parent[0].metadata['EAPI'] + else: + graph_parent = parent + eapi = parent.metadata["EAPI"] + repoman = not mysettings.local_config + if kwargs["use_binaries"]: + portdb = trees[myroot]["bintree"].dbapi + myvirtuals = mysettings.getvirtuals() + pprovideddict = mysettings.pprovideddict + myuse = kwargs["myuse"] + for x in mysplit: + if x == "||": + newsplit.append(x) + continue + elif isinstance(x, list): + newsplit.append(_expand_new_virtuals(x, edebug, mydbapi, + mysettings, myroot=myroot, trees=trees, use_mask=use_mask, + use_force=use_force, **kwargs)) + continue + + if not isinstance(x, Atom): + try: + x = Atom(x) + except InvalidAtom: + if portage.dep._dep_check_strict: + raise ParseError( + _("invalid atom: '%s'") % x) + else: + # Only real Atom instances are allowed past this point. + continue + else: + if x.blocker and x.blocker.overlap.forbid and \ + eapi in ("0", "1") and portage.dep._dep_check_strict: + raise ParseError( + _("invalid atom: '%s'") % (x,)) + if x.use and eapi in ("0", "1") and \ + portage.dep._dep_check_strict: + raise ParseError( + _("invalid atom: '%s'") % (x,)) + + if repoman and x.use and x.use.conditional: + evaluated_atom = remove_slot(x) + if x.slot: + evaluated_atom += ":%s" % x.slot + evaluated_atom += str(x.use._eval_qa_conditionals( + use_mask, use_force)) + x = Atom(evaluated_atom) + + if not repoman and \ + myuse is not None and isinstance(x, Atom) and x.use: + if x.use.conditional: + x = x.evaluate_conditionals(myuse) + + mykey = x.cp + if not mykey.startswith("virtual/"): + newsplit.append(x) + if atom_graph is not None: + atom_graph.add(x, graph_parent) + continue + mychoices = myvirtuals.get(mykey, []) + if x.blocker: + # Virtual blockers are no longer expanded here since + # the un-expanded virtual atom is more useful for + # maintaining a cache of blocker atoms. + newsplit.append(x) + if atom_graph is not None: + atom_graph.add(x, graph_parent) + continue + + if repoman or not hasattr(portdb, 'match_pkgs'): + if portdb.cp_list(x.cp): + newsplit.append(x) + else: + # TODO: Add PROVIDE check for repoman. + a = [] + for y in mychoices: + a.append(Atom(x.replace(x.cp, y.cp, 1))) + if not a: + newsplit.append(x) + elif len(a) == 1: + newsplit.append(a[0]) + else: + newsplit.append(['||'] + a) + continue + + pkgs = [] + # Ignore USE deps here, since otherwise we might not + # get any matches. Choices with correct USE settings + # will be preferred in dep_zapdeps(). + matches = portdb.match_pkgs(x.without_use) + # Use descending order to prefer higher versions. + matches.reverse() + for pkg in matches: + # only use new-style matches + if pkg.cp.startswith("virtual/"): + pkgs.append(pkg) + if not (pkgs or mychoices): + # This one couldn't be expanded as a new-style virtual. Old-style + # virtuals have already been expanded by dep_virtual, so this one + # is unavailable and dep_zapdeps will identify it as such. The + # atom is not eliminated here since it may still represent a + # dependency that needs to be satisfied. + newsplit.append(x) + if atom_graph is not None: + atom_graph.add(x, graph_parent) + continue + + a = [] + for pkg in pkgs: + virt_atom = '=' + pkg.cpv + if x.use: + virt_atom += str(x.use) + virt_atom = Atom(virt_atom) + # According to GLEP 37, RDEPEND is the only dependency + # type that is valid for new-style virtuals. Repoman + # should enforce this. + depstring = pkg.metadata['RDEPEND'] + pkg_kwargs = kwargs.copy() + pkg_kwargs["myuse"] = pkg.use.enabled + if edebug: + writemsg_level(_("Virtual Parent: %s\n") \ + % (pkg,), noiselevel=-1, level=logging.DEBUG) + writemsg_level(_("Virtual Depstring: %s\n") \ + % (depstring,), noiselevel=-1, level=logging.DEBUG) + + # Set EAPI used for validation in dep_check() recursion. + mytrees["virt_parent"] = (pkg, virt_atom) + + try: + mycheck = dep_check(depstring, mydbapi, mysettings, + myroot=myroot, trees=trees, **pkg_kwargs) + finally: + # Restore previous EAPI after recursion. + if virt_parent is not None: + mytrees["virt_parent"] = virt_parent + else: + del mytrees["virt_parent"] + + if not mycheck[0]: + raise ParseError( + "%s: %s '%s'" % (y[0], mycheck[1], depstring)) + + # pull in the new-style virtual + mycheck[1].append(virt_atom) + a.append(mycheck[1]) + if atom_graph is not None: + atom_graph.add(virt_atom, graph_parent) + # Plain old-style virtuals. New-style virtuals are preferred. + if not pkgs: + for y in mychoices: + new_atom = Atom(x.replace(x.cp, y.cp, 1)) + matches = portdb.match(new_atom) + # portdb is an instance of depgraph._dep_check_composite_db, so + # USE conditionals are already evaluated. + if matches and mykey in \ + portdb.aux_get(matches[-1], ['PROVIDE'])[0].split(): + a.append(new_atom) + if atom_graph is not None: + atom_graph.add(new_atom, graph_parent) + + if not a and mychoices: + # Check for a virtual package.provided match. + for y in mychoices: + new_atom = Atom(x.replace(x.cp, y.cp, 1)) + if match_from_list(new_atom, + pprovideddict.get(new_atom.cp, [])): + a.append(new_atom) + if atom_graph is not None: + atom_graph.add(new_atom, graph_parent) + + if not a: + newsplit.append(x) + if atom_graph is not None: + atom_graph.add(x, graph_parent) + elif len(a) == 1: + newsplit.append(a[0]) + else: + newsplit.append(['||'] + a) + + return newsplit + +def dep_eval(deplist): + if not deplist: + return 1 + if deplist[0]=="||": + #or list; we just need one "1" + for x in deplist[1:]: + if isinstance(x, list): + if dep_eval(x)==1: + return 1 + elif x==1: + return 1 + #XXX: unless there's no available atoms in the list + #in which case we need to assume that everything is + #okay as some ebuilds are relying on an old bug. + if len(deplist) == 1: + return 1 + return 0 + else: + for x in deplist: + if isinstance(x, list): + if dep_eval(x)==0: + return 0 + elif x==0 or x==2: + return 0 + return 1 + +def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): + """ + Takes an unreduced and reduced deplist and removes satisfied dependencies. + Returned deplist contains steps that must be taken to satisfy dependencies. + """ + if trees is None: + trees = portage.db + writemsg("ZapDeps -- %s\n" % (use_binaries), 2) + if not reduced or unreduced == ["||"] or dep_eval(reduced): + return [] + + if unreduced[0] != "||": + unresolved = [] + for x, satisfied in zip(unreduced, reduced): + if isinstance(x, list): + unresolved += dep_zapdeps(x, satisfied, myroot, + use_binaries=use_binaries, trees=trees) + elif not satisfied: + unresolved.append(x) + return unresolved + + # We're at a ( || atom ... ) type level and need to make a choice + deps = unreduced[1:] + satisfieds = reduced[1:] + + # Our preference order is for an the first item that: + # a) contains all unmasked packages with the same key as installed packages + # b) contains all unmasked packages + # c) contains masked installed packages + # d) is the first item + + preferred_installed = [] + preferred_in_graph = [] + preferred_any_slot = [] + preferred_non_installed = [] + unsat_use_in_graph = [] + unsat_use_installed = [] + unsat_use_non_installed = [] + other = [] + + # unsat_use_* must come after preferred_non_installed + # for correct ordering in cases like || ( foo[a] foo[b] ). + choice_bins = ( + preferred_in_graph, + preferred_installed, + preferred_any_slot, + preferred_non_installed, + unsat_use_in_graph, + unsat_use_installed, + unsat_use_non_installed, + other, + ) + + # Alias the trees we'll be checking availability against + parent = trees[myroot].get("parent") + priority = trees[myroot].get("priority") + graph_db = trees[myroot].get("graph_db") + vardb = None + if "vartree" in trees[myroot]: + vardb = trees[myroot]["vartree"].dbapi + if use_binaries: + mydbapi = trees[myroot]["bintree"].dbapi + else: + mydbapi = trees[myroot]["porttree"].dbapi + + # Sort the deps into installed, not installed but already + # in the graph and other, not installed and not in the graph + # and other, with values of [[required_atom], availablility] + for x, satisfied in zip(deps, satisfieds): + if isinstance(x, list): + atoms = dep_zapdeps(x, satisfied, myroot, + use_binaries=use_binaries, trees=trees) + else: + atoms = [x] + if vardb is None: + # When called by repoman, we can simply return the first choice + # because dep_eval() handles preference selection. + return atoms + + all_available = True + all_use_satisfied = True + slot_map = {} + cp_map = {} + for atom in atoms: + if atom.blocker: + continue + # Ignore USE dependencies here since we don't want USE + # settings to adversely affect || preference evaluation. + avail_pkg = mydbapi.match(atom.without_use) + if avail_pkg: + avail_pkg = avail_pkg[-1] # highest (ascending order) + avail_slot = Atom("%s:%s" % (atom.cp, + mydbapi.aux_get(avail_pkg, ["SLOT"])[0])) + if not avail_pkg: + all_available = False + all_use_satisfied = False + break + + if atom.use: + avail_pkg_use = mydbapi.match(atom) + if not avail_pkg_use: + all_use_satisfied = False + else: + # highest (ascending order) + avail_pkg_use = avail_pkg_use[-1] + if avail_pkg_use != avail_pkg: + avail_pkg = avail_pkg_use + avail_slot = Atom("%s:%s" % (atom.cp, + mydbapi.aux_get(avail_pkg, ["SLOT"])[0])) + + slot_map[avail_slot] = avail_pkg + pkg_cp = cpv_getkey(avail_pkg) + highest_cpv = cp_map.get(pkg_cp) + if highest_cpv is None or \ + pkgcmp(catpkgsplit(avail_pkg)[1:], + catpkgsplit(highest_cpv)[1:]) > 0: + cp_map[pkg_cp] = avail_pkg + + this_choice = (atoms, slot_map, cp_map, all_available) + if all_available: + # The "all installed" criterion is not version or slot specific. + # If any version of a package is already in the graph then we + # assume that it is preferred over other possible packages choices. + all_installed = True + for atom in set(Atom(atom.cp) for atom in atoms \ + if not atom.blocker): + # New-style virtuals have zero cost to install. + if not vardb.match(atom) and not atom.startswith("virtual/"): + all_installed = False + break + all_installed_slots = False + if all_installed: + all_installed_slots = True + for slot_atom in slot_map: + # New-style virtuals have zero cost to install. + if not vardb.match(slot_atom) and \ + not slot_atom.startswith("virtual/"): + all_installed_slots = False + break + if graph_db is None: + if all_use_satisfied: + if all_installed: + if all_installed_slots: + preferred_installed.append(this_choice) + else: + preferred_any_slot.append(this_choice) + else: + preferred_non_installed.append(this_choice) + else: + if all_installed_slots: + unsat_use_installed.append(this_choice) + else: + unsat_use_non_installed.append(this_choice) + else: + all_in_graph = True + for slot_atom in slot_map: + # New-style virtuals have zero cost to install. + if not graph_db.match(slot_atom) and \ + not slot_atom.startswith("virtual/"): + all_in_graph = False + break + circular_atom = None + if all_in_graph: + if parent is None or priority is None: + pass + elif priority.buildtime: + # Check if the atom would result in a direct circular + # dependency and try to avoid that if it seems likely + # to be unresolvable. This is only relevant for + # buildtime deps that aren't already satisfied by an + # installed package. + cpv_slot_list = [parent] + for atom in atoms: + if atom.blocker: + continue + if vardb.match(atom): + # If the atom is satisfied by an installed + # version then it's not a circular dep. + continue + if atom.cp != parent.cp: + continue + if match_from_list(atom, cpv_slot_list): + circular_atom = atom + break + if circular_atom is not None: + other.append(this_choice) + else: + if all_use_satisfied: + if all_in_graph: + preferred_in_graph.append(this_choice) + elif all_installed: + if all_installed_slots: + preferred_installed.append(this_choice) + else: + preferred_any_slot.append(this_choice) + else: + preferred_non_installed.append(this_choice) + else: + if all_in_graph: + unsat_use_in_graph.append(this_choice) + elif all_installed_slots: + unsat_use_installed.append(this_choice) + else: + unsat_use_non_installed.append(this_choice) + else: + other.append(this_choice) + + # Prefer choices which contain upgrades to higher slots. This helps + # for deps such as || ( foo:1 foo:2 ), where we want to prefer the + # atom which matches the higher version rather than the atom furthest + # to the left. Sorting is done separately for each of choice_bins, so + # as not to interfere with the ordering of the bins. Because of the + # bin separation, the main function of this code is to allow + # --depclean to remove old slots (rather than to pull in new slots). + for choices in choice_bins: + if len(choices) < 2: + continue + for choice_1 in choices[1:]: + atoms_1, slot_map_1, cp_map_1, all_available_1 = choice_1 + cps = set(cp_map_1) + for choice_2 in choices: + if choice_1 is choice_2: + # choice_1 will not be promoted, so move on + break + atoms_2, slot_map_2, cp_map_2, all_available_2 = choice_2 + intersecting_cps = cps.intersection(cp_map_2) + if not intersecting_cps: + continue + has_upgrade = False + has_downgrade = False + for cp in intersecting_cps: + version_1 = cp_map_1[cp] + version_2 = cp_map_2[cp] + difference = pkgcmp(catpkgsplit(version_1)[1:], + catpkgsplit(version_2)[1:]) + if difference != 0: + if difference > 0: + has_upgrade = True + else: + has_downgrade = True + break + if has_upgrade and not has_downgrade: + # promote choice_1 in front of choice_2 + choices.remove(choice_1) + index_2 = choices.index(choice_2) + choices.insert(index_2, choice_1) + break + + for allow_masked in (False, True): + for choices in choice_bins: + for atoms, slot_map, cp_map, all_available in choices: + if all_available or allow_masked: + return atoms + + assert(False) # This point should not be reachable + +def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None, + use_cache=1, use_binaries=0, myroot="/", trees=None): + """Takes a depend string and parses the condition.""" + edebug = mysettings.get("PORTAGE_DEBUG", None) == "1" + #check_config_instance(mysettings) + if trees is None: + trees = globals()["db"] + if use=="yes": + if myuse is None: + #default behavior + myusesplit = mysettings["PORTAGE_USE"].split() + else: + myusesplit = myuse + # We've been given useflags to use. + #print "USE FLAGS PASSED IN." + #print myuse + #if "bindist" in myusesplit: + # print "BINDIST is set!" + #else: + # print "BINDIST NOT set." + else: + #we are being run by autouse(), don't consult USE vars yet. + # WE ALSO CANNOT USE SETTINGS + myusesplit=[] + + #convert parenthesis to sublists + try: + mysplit = paren_reduce(depstring) + except InvalidDependString as e: + return [0, str(e)] + + mymasks = set() + useforce = set() + useforce.add(mysettings["ARCH"]) + if use == "all": + # This masking/forcing is only for repoman. In other cases, relevant + # masking/forcing should have already been applied via + # config.regenerate(). Also, binary or installed packages may have + # been built with flags that are now masked, and it would be + # inconsistent to mask them now. Additionally, myuse may consist of + # flags from a parent package that is being merged to a $ROOT that is + # different from the one that mysettings represents. + mymasks.update(mysettings.usemask) + mymasks.update(mysettings.archlist()) + mymasks.discard(mysettings["ARCH"]) + useforce.update(mysettings.useforce) + useforce.difference_update(mymasks) + try: + mysplit = use_reduce(mysplit, uselist=myusesplit, + masklist=mymasks, matchall=(use=="all"), excludeall=useforce) + except InvalidDependString as e: + return [0, str(e)] + + # Do the || conversions + mysplit = dep_opconvert(mysplit) + + if mysplit == []: + #dependencies were reduced to nothing + return [1,[]] + + # Recursively expand new-style virtuals so as to + # collapse one or more levels of indirection. + try: + mysplit = _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, + use=use, mode=mode, myuse=myuse, + use_force=useforce, use_mask=mymasks, use_cache=use_cache, + use_binaries=use_binaries, myroot=myroot, trees=trees) + except ParseError as e: + return [0, str(e)] + + mysplit2=mysplit[:] + mysplit2=dep_wordreduce(mysplit2,mysettings,mydbapi,mode,use_cache=use_cache) + if mysplit2 is None: + return [0, _("Invalid token")] + + writemsg("\n\n\n", 1) + writemsg("mysplit: %s\n" % (mysplit), 1) + writemsg("mysplit2: %s\n" % (mysplit2), 1) + + try: + selected_atoms = dep_zapdeps(mysplit, mysplit2, myroot, + use_binaries=use_binaries, trees=trees) + except InvalidAtom as e: + if portage.dep._dep_check_strict: + raise # This shouldn't happen. + # dbapi.match() failed due to an invalid atom in + # the dependencies of an installed package. + return [0, _("Invalid atom: '%s'") % (e,)] + + return [1, selected_atoms] + +def dep_wordreduce(mydeplist,mysettings,mydbapi,mode,use_cache=1): + "Reduces the deplist to ones and zeros" + deplist=mydeplist[:] + for mypos, token in enumerate(deplist): + if isinstance(deplist[mypos], list): + #recurse + deplist[mypos]=dep_wordreduce(deplist[mypos],mysettings,mydbapi,mode,use_cache=use_cache) + elif deplist[mypos]=="||": + pass + elif token[:1] == "!": + deplist[mypos] = False + else: + mykey = deplist[mypos].cp + if mysettings and mykey in mysettings.pprovideddict and \ + match_from_list(deplist[mypos], mysettings.pprovideddict[mykey]): + deplist[mypos]=True + elif mydbapi is None: + # Assume nothing is satisfied. This forces dep_zapdeps to + # return all of deps the deps that have been selected + # (excluding those satisfied by package.provided). + deplist[mypos] = False + else: + if mode: + x = mydbapi.xmatch(mode, deplist[mypos]) + if mode.startswith("minimum-"): + mydep = [] + if x: + mydep.append(x) + else: + mydep = x + else: + mydep=mydbapi.match(deplist[mypos],use_cache=use_cache) + if mydep!=None: + tmp=(len(mydep)>=1) + if deplist[mypos][0]=="!": + tmp=False + deplist[mypos]=tmp + else: + #encountered invalid string + return None + return deplist |