| # Copyright 2010-2020 Gentoo Authors |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| __all__ = ["dep_check", "dep_eval", "dep_wordreduce", "dep_zapdeps"] |
| |
| import collections |
| import itertools |
| import logging |
| import operator |
| |
| import portage |
| from portage.dep import Atom, match_from_list, use_reduce |
| from portage.dep._dnf import ( |
| dnf_convert as _dnf_convert, |
| contains_disjunction as _contains_disjunction, |
| ) |
| from portage.exception import InvalidDependString, ParseError |
| from portage.localization import _ |
| from portage.util import writemsg, writemsg_level |
| from portage.util.digraph import digraph |
| from portage.util.SlotObject import SlotObject |
| from portage.versions import vercmp |
| |
| |
| 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 ). |
| |
| The result is normalized in the same way as use_reduce, having a top-level |
| conjuction, and no redundant nested lists. |
| """ |
| newsplit = [] |
| mytrees = trees[myroot] |
| portdb = mytrees["porttree"].dbapi |
| pkg_use_enabled = mytrees.get("pkg_use_enabled") |
| # Atoms are stored in the graph as (atom, id(atom)) tuples |
| # since each atom is considered to be a unique entity. For |
| # example, atoms that appear identical may behave differently |
| # in USE matching, depending on their unevaluated form. Also, |
| # specially generated virtual atoms may appear identical while |
| # having different _orig_atom attributes. |
| atom_graph = mytrees.get("atom_graph") |
| parent = mytrees.get("parent") |
| virt_parent = mytrees.get("virt_parent") |
| graph_parent = None |
| if parent is not None: |
| if virt_parent is not None: |
| graph_parent = virt_parent |
| parent = virt_parent |
| else: |
| graph_parent = parent |
| repoman = not mysettings.local_config |
| if kwargs["use_binaries"]: |
| portdb = trees[myroot]["bintree"].dbapi |
| pprovideddict = mysettings.pprovideddict |
| myuse = kwargs["myuse"] |
| is_disjunction = mysplit and mysplit[0] == "||" |
| for x in mysplit: |
| if x == "||": |
| newsplit.append(x) |
| continue |
| elif isinstance(x, list): |
| assert x, "Normalization error, empty conjunction found in %s" % (mysplit,) |
| if is_disjunction: |
| assert ( |
| x[0] != "||" |
| ), "Normalization error, nested disjunction found in %s" % (mysplit,) |
| else: |
| assert ( |
| x[0] == "||" |
| ), "Normalization error, nested conjunction found in %s" % (mysplit,) |
| x_exp = _expand_new_virtuals( |
| x, |
| edebug, |
| mydbapi, |
| mysettings, |
| myroot=myroot, |
| trees=trees, |
| use_mask=use_mask, |
| use_force=use_force, |
| **kwargs |
| ) |
| if is_disjunction: |
| if len(x_exp) == 1: |
| x = x_exp[0] |
| if isinstance(x, list): |
| # Due to normalization, a conjunction must not be |
| # nested directly in another conjunction, so this |
| # must be a disjunction. |
| assert ( |
| x and x[0] == "||" |
| ), "Normalization error, nested conjunction found in %s" % ( |
| x_exp, |
| ) |
| newsplit.extend(x[1:]) |
| else: |
| newsplit.append(x) |
| else: |
| newsplit.append(x_exp) |
| else: |
| newsplit.extend(x_exp) |
| continue |
| |
| if not isinstance(x, Atom): |
| raise ParseError(_("invalid token: '%s'") % x) |
| |
| if repoman: |
| x = x._eval_qa_conditionals(use_mask, use_force) |
| |
| mykey = x.cp |
| if not mykey.startswith("virtual/"): |
| newsplit.append(x) |
| if atom_graph is not None: |
| atom_graph.add((x, id(x)), graph_parent) |
| continue |
| |
| 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, id(x)), graph_parent) |
| continue |
| |
| if repoman or not hasattr(portdb, "match_pkgs") or pkg_use_enabled is None: |
| if portdb.cp_list(x.cp): |
| newsplit.append(x) |
| else: |
| a = [] |
| myvartree = mytrees.get("vartree") |
| if myvartree is not None: |
| mysettings._populate_treeVirtuals_if_needed(myvartree) |
| mychoices = mysettings.getvirtuals().get(mykey, []) |
| for y in mychoices: |
| a.append(Atom(x.replace(x.cp, y.cp, 1))) |
| if not a: |
| newsplit.append(x) |
| elif is_disjunction: |
| newsplit.extend(a) |
| 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) |
| |
| mychoices = [] |
| if not pkgs and not portdb.cp_list(x.cp): |
| myvartree = mytrees.get("vartree") |
| if myvartree is not None: |
| mysettings._populate_treeVirtuals_if_needed(myvartree) |
| mychoices = mysettings.getvirtuals().get(mykey, []) |
| |
| 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, id(x)), graph_parent) |
| continue |
| |
| a = [] |
| for pkg in pkgs: |
| virt_atom = "=" + pkg.cpv |
| if x.unevaluated_atom.use: |
| virt_atom += str(x.unevaluated_atom.use) |
| virt_atom = Atom(virt_atom) |
| if parent is None: |
| if myuse is None: |
| virt_atom = virt_atom.evaluate_conditionals( |
| mysettings.get("PORTAGE_USE", "").split() |
| ) |
| else: |
| virt_atom = virt_atom.evaluate_conditionals(myuse) |
| else: |
| virt_atom = virt_atom.evaluate_conditionals(pkg_use_enabled(parent)) |
| else: |
| virt_atom = Atom(virt_atom) |
| |
| # Allow the depgraph to map this atom back to the |
| # original, in order to avoid distortion in places |
| # like display or conflict resolution code. |
| virt_atom.__dict__["_orig_atom"] = x |
| |
| # 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(pkg) |
| 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 |
| |
| 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'" % (pkg, mycheck[1], depstring)) |
| |
| # Replace the original atom "x" with "virt_atom" which refers |
| # to the specific version of the virtual whose deps we're |
| # expanding. The virt_atom._orig_atom attribute is used |
| # by depgraph to map virt_atom back to the original atom. |
| # We specifically exclude the original atom "x" from the |
| # the expanded output here, since otherwise it could trigger |
| # incorrect dep_zapdeps behavior (see bug #597752). |
| mycheck[1].append(virt_atom) |
| a.append(mycheck[1]) |
| if atom_graph is not None: |
| virt_atom_node = (virt_atom, id(virt_atom)) |
| atom_graph.add(virt_atom_node, graph_parent) |
| atom_graph.add(pkg, virt_atom_node) |
| atom_graph.add((x, id(x)), 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, id(new_atom)), graph_parent) |
| |
| if not a: |
| newsplit.append(x) |
| if atom_graph is not None: |
| atom_graph.add((x, id(x)), graph_parent) |
| elif is_disjunction: |
| newsplit.extend(a) |
| elif len(a) == 1: |
| newsplit.extend(a[0]) |
| else: |
| newsplit.append(["||"] + a) |
| |
| # For consistency with related functions like use_reduce, always |
| # normalize the result to have a top-level conjunction. |
| if is_disjunction: |
| newsplit = [newsplit] |
| |
| 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 |
| 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 |
| |
| |
| class _dep_choice(SlotObject): |
| __slots__ = ( |
| "atoms", |
| "slot_map", |
| "cp_map", |
| "all_available", |
| "all_installed_slots", |
| "new_slot_count", |
| "want_update", |
| "all_in_graph", |
| ) |
| |
| |
| def dep_zapdeps( |
| unreduced, reduced, myroot, use_binaries=0, trees=None, minimize_slots=False |
| ): |
| """ |
| 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, |
| minimize_slots=minimize_slots, |
| ) |
| 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 |
| |
| no_new_slots = [] |
| preferred_in_graph = [] |
| preferred_installed = preferred_in_graph |
| preferred_any_slot = preferred_in_graph |
| preferred_non_installed = [] |
| unsat_use_in_graph = [] |
| unsat_use_installed = [] |
| unsat_use_non_installed = [] |
| other_installed = [] |
| other_installed_some = [] |
| other_installed_any_slot = [] |
| other = [] |
| |
| # unsat_use_* must come after preferred_non_installed |
| # for correct ordering in cases like || ( foo[a] foo[b] ). |
| choice_bins = ( |
| no_new_slots, |
| preferred_in_graph, |
| preferred_non_installed, |
| unsat_use_in_graph, |
| unsat_use_installed, |
| unsat_use_non_installed, |
| other_installed, |
| other_installed_some, |
| other_installed_any_slot, |
| other, |
| ) |
| |
| # Alias the trees we'll be checking availability against |
| parent = trees[myroot].get("parent") |
| virt_parent = trees[myroot].get("virt_parent") |
| priority = trees[myroot].get("priority") |
| graph_db = trees[myroot].get("graph_db") |
| graph = trees[myroot].get("graph") |
| pkg_use_enabled = trees[myroot].get("pkg_use_enabled") |
| graph_interface = trees[myroot].get("graph_interface") |
| downgrade_probe = trees[myroot].get("downgrade_probe") |
| circular_dependency = trees[myroot].get("circular_dependency") |
| 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 |
| |
| try: |
| mydbapi_match_pkgs = mydbapi.match_pkgs |
| except AttributeError: |
| |
| def mydbapi_match_pkgs(atom): |
| return [mydbapi._pkg_str(cpv, atom.repo) for cpv in mydbapi.match(atom)] |
| |
| # 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, |
| minimize_slots=minimize_slots, |
| ) |
| 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 |
| all_use_unmasked = True |
| conflict_downgrade = False |
| installed_downgrade = False |
| slot_atoms = collections.defaultdict(list) |
| slot_map = {} |
| cp_map = {} |
| for atom in atoms: |
| if atom.blocker: |
| continue |
| |
| # It's not a downgrade if parent is replacing child. |
| replacing = ( |
| parent |
| and graph_interface |
| and graph_interface.will_replace_child(parent, myroot, atom) |
| ) |
| # Ignore USE dependencies here since we don't want USE |
| # settings to adversely affect || preference evaluation. |
| avail_pkg = mydbapi_match_pkgs(atom.without_use) |
| if not avail_pkg and replacing: |
| avail_pkg = [replacing] |
| if avail_pkg: |
| avail_pkg = avail_pkg[-1] # highest (ascending order) |
| avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) |
| if not avail_pkg: |
| all_available = False |
| all_use_satisfied = False |
| break |
| |
| if not replacing and graph_db is not None and downgrade_probe is not None: |
| slot_matches = graph_db.match_pkgs(avail_slot) |
| if ( |
| len(slot_matches) > 1 |
| and avail_pkg < slot_matches[-1] |
| and not downgrade_probe(avail_pkg) |
| ): |
| # If a downgrade is not desirable, then avoid a |
| # choice that pulls in a lower version involved |
| # in a slot conflict (bug #531656). |
| conflict_downgrade = True |
| |
| if atom.use: |
| avail_pkg_use = mydbapi_match_pkgs(atom) |
| if not avail_pkg_use: |
| all_use_satisfied = False |
| |
| if pkg_use_enabled is not None: |
| # Check which USE flags cause the match to fail, |
| # so we can prioritize choices that do not |
| # require changes to use.mask or use.force |
| # (see bug #515584). |
| violated_atom = atom.violated_conditionals( |
| pkg_use_enabled(avail_pkg), avail_pkg.iuse.is_valid_flag |
| ) |
| |
| # Note that violated_atom.use can be None here, |
| # since evaluation can collapse conditional USE |
| # deps that cause the match to fail due to |
| # missing IUSE (match uses atom.unevaluated_atom |
| # to detect such missing IUSE). |
| if violated_atom.use is not None: |
| for flag in violated_atom.use.enabled: |
| if flag in avail_pkg.use.mask: |
| all_use_unmasked = False |
| break |
| else: |
| for flag in violated_atom.use.disabled: |
| if ( |
| flag in avail_pkg.use.force |
| and flag not in avail_pkg.use.mask |
| ): |
| all_use_unmasked = False |
| break |
| 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, avail_pkg.slot)) |
| |
| if not replacing and downgrade_probe is not None and graph is not None: |
| highest_in_slot = mydbapi_match_pkgs(avail_slot) |
| highest_in_slot = highest_in_slot[-1] if highest_in_slot else None |
| if ( |
| avail_pkg |
| and highest_in_slot |
| and avail_pkg < highest_in_slot |
| and not downgrade_probe(avail_pkg) |
| and (highest_in_slot.installed or highest_in_slot in graph) |
| ): |
| installed_downgrade = True |
| |
| slot_map[avail_slot] = avail_pkg |
| slot_atoms[avail_slot].append(atom) |
| highest_cpv = cp_map.get(avail_pkg.cp) |
| all_match_current = None |
| all_match_previous = None |
| if highest_cpv is not None and highest_cpv.slot == avail_pkg.slot: |
| # If possible, make the package selection internally |
| # consistent by choosing a package that satisfies all |
| # atoms which match a package in the same slot. Later on, |
| # the package version chosen here is used in the |
| # has_upgrade/has_downgrade logic to prefer choices with |
| # upgrades, and a package choice that is not internally |
| # consistent will lead the has_upgrade/has_downgrade logic |
| # to produce invalid results (see bug 600346). |
| all_match_current = all( |
| a.match(avail_pkg) for a in slot_atoms[avail_slot] |
| ) |
| all_match_previous = all( |
| a.match(highest_cpv) for a in slot_atoms[avail_slot] |
| ) |
| if all_match_previous and not all_match_current: |
| continue |
| |
| current_higher = ( |
| highest_cpv is None |
| or vercmp(avail_pkg.version, highest_cpv.version) > 0 |
| ) |
| |
| if current_higher or (all_match_current and not all_match_previous): |
| cp_map[avail_pkg.cp] = avail_pkg |
| |
| want_update = False |
| if graph_interface is None or graph_interface.removal_action: |
| new_slot_count = len(slot_map) |
| else: |
| new_slot_count = 0 |
| for slot_atom, avail_pkg in slot_map.items(): |
| if parent is not None and graph_interface.want_update_pkg( |
| parent, avail_pkg |
| ): |
| want_update = True |
| if not slot_atom.cp.startswith("virtual/") and not graph_db.match_pkgs( |
| slot_atom |
| ): |
| new_slot_count += 1 |
| |
| this_choice = _dep_choice( |
| atoms=atoms, |
| slot_map=slot_map, |
| cp_map=cp_map, |
| all_available=all_available, |
| all_installed_slots=False, |
| new_slot_count=new_slot_count, |
| all_in_graph=False, |
| want_update=want_update, |
| ) |
| 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 |
| this_choice.all_installed_slots = all_installed_slots |
| 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 not all_use_unmasked: |
| other.append(this_choice) |
| elif all_installed_slots: |
| unsat_use_installed.append(this_choice) |
| else: |
| unsat_use_non_installed.append(this_choice) |
| elif conflict_downgrade or installed_downgrade: |
| other.append(this_choice) |
| else: |
| all_in_graph = True |
| for atom in atoms: |
| # New-style virtuals have zero cost to install. |
| if atom.blocker or atom.cp.startswith("virtual/"): |
| continue |
| # We check if the matched package has actually been |
| # added to the digraph, in order to distinguish between |
| # those packages and installed packages that may need |
| # to be uninstalled in order to resolve blockers. |
| if not any(pkg in graph for pkg in graph_db.match_pkgs(atom)): |
| all_in_graph = False |
| break |
| this_choice.all_in_graph = all_in_graph |
| |
| circular_atom = None |
| if parent and parent.onlydeps: |
| # Check if the atom would result in a direct circular |
| # dependency and avoid that for --onlydeps arguments |
| # since it can defeat the purpose of --onlydeps. |
| # This check should only be used for --onlydeps |
| # arguments, since it can interfere with circular |
| # dependency backtracking choices, causing the test |
| # case for bug 756961 to fail. |
| 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 None and circular_dependency is not None: |
| for circular_child in itertools.chain( |
| circular_dependency.get(parent, []), |
| circular_dependency.get(virt_parent, []), |
| ): |
| for atom in atoms: |
| if not atom.blocker and atom.match(circular_child): |
| circular_atom = atom |
| break |
| if circular_atom is not None: |
| break |
| |
| if circular_atom is not None: |
| other.append(this_choice) |
| else: |
| if all_use_satisfied: |
| if new_slot_count == 0 and not want_update: |
| no_new_slots.append(this_choice) |
| elif 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 not all_use_unmasked: |
| other.append(this_choice) |
| elif 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: |
| all_installed = True |
| some_installed = False |
| for atom in atoms: |
| if not atom.blocker: |
| if vardb.match(atom): |
| some_installed = True |
| else: |
| all_installed = False |
| |
| if all_installed: |
| this_choice.all_installed_slots = True |
| other_installed.append(this_choice) |
| elif some_installed: |
| other_installed_some.append(this_choice) |
| |
| # Use Atom(atom.cp) for a somewhat "fuzzy" match, since |
| # the whole atom may be too specific. For example, see |
| # bug #522652, where using the whole atom leads to an |
| # unsatisfiable choice. |
| elif any(vardb.match(Atom(atom.cp)) for atom in atoms if not atom.blocker): |
| other_installed_any_slot.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 |
| |
| if minimize_slots: |
| # Prefer choices having fewer new slots. When used with DNF form, |
| # this can eliminate unecessary packages that depclean would |
| # ultimately eliminate (see bug 632026). Only use this behavior |
| # when deemed necessary by the caller, since this will discard the |
| # order specified in the ebuild, and the preferences specified |
| # there can serve as a crucial sources of guidance (see bug 645002). |
| |
| # NOTE: Under some conditions, new_slot_count value may have some |
| # variance from one calculation to the next because it depends on |
| # the order that packages are added to the graph. This variance can |
| # contribute to outcomes that appear to be random. Meanwhile, |
| # the order specified in the ebuild is without variance, so it |
| # does not have this problem. |
| choices.sort(key=operator.attrgetter("new_slot_count")) |
| |
| for choice_1 in choices[1:]: |
| cps = set(choice_1.cp_map) |
| for choice_2 in choices: |
| if choice_1 is choice_2: |
| # choice_1 will not be promoted, so move on |
| break |
| if ( |
| choice_1.all_installed_slots |
| and not choice_2.all_installed_slots |
| and not choice_2.want_update |
| ): |
| # 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 |
| |
| intersecting_cps = cps.intersection(choice_2.cp_map) |
| has_upgrade = False |
| has_downgrade = False |
| for cp in intersecting_cps: |
| version_1 = choice_1.cp_map[cp] |
| version_2 = choice_2.cp_map[cp] |
| difference = vercmp(version_1.version, version_2.version) |
| if difference != 0: |
| if difference > 0: |
| has_upgrade = True |
| else: |
| has_downgrade = True |
| |
| if ( |
| # Prefer upgrades. |
| (has_upgrade and not has_downgrade) |
| # Prefer choices where all packages have been pulled into |
| # the graph, except for choices that eliminate upgrades. |
| or ( |
| choice_1.all_in_graph |
| and not choice_2.all_in_graph |
| and not (has_downgrade and not has_upgrade) |
| ) |
| ): |
| # 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 choice in choices: |
| if choice.all_available or allow_masked: |
| return choice.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=None, |
| trees=None, |
| ): |
| """ |
| Takes a depend string, parses it, and selects atoms. |
| The myroot parameter is unused (use mysettings['EROOT'] instead). |
| """ |
| myroot = mysettings["EROOT"] |
| 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 = [] |
| |
| mymasks = set() |
| useforce = set() |
| if use == "all": |
| # This is only for repoman, in order to constrain the use_reduce |
| # matchall behavior to account for profile use.mask/force. The |
| # ARCH/archlist code here may be redundant, since the profile |
| # really should be handling ARCH masking/forcing itself. |
| arch = mysettings.get("ARCH") |
| mymasks.update(mysettings.usemask) |
| mymasks.update(mysettings.archlist()) |
| if arch: |
| mymasks.discard(arch) |
| useforce.add(arch) |
| useforce.update(mysettings.useforce) |
| useforce.difference_update(mymasks) |
| |
| # eapi code borrowed from _expand_new_virtuals() |
| mytrees = trees[myroot] |
| parent = mytrees.get("parent") |
| virt_parent = mytrees.get("virt_parent") |
| current_parent = None |
| eapi = None |
| if parent is not None: |
| if virt_parent is not None: |
| current_parent = virt_parent |
| else: |
| current_parent = parent |
| |
| if current_parent is not None: |
| # Don't pass the eapi argument to use_reduce() for installed packages |
| # since previous validation will have already marked them as invalid |
| # when necessary and now we're more interested in evaluating |
| # dependencies so that things like --depclean work as well as possible |
| # in spite of partial invalidity. |
| if not current_parent.installed: |
| eapi = current_parent.eapi |
| |
| if isinstance(depstring, list): |
| mysplit = depstring |
| else: |
| try: |
| mysplit = use_reduce( |
| depstring, |
| uselist=myusesplit, |
| masklist=mymasks, |
| matchall=(use == "all"), |
| excludeall=useforce, |
| opconvert=True, |
| token_class=Atom, |
| eapi=eapi, |
| ) |
| except InvalidDependString as e: |
| return [0, "%s" % (e,)] |
| |
| 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, "%s" % (e,)] |
| |
| dnf = False |
| if mysettings.local_config: # if not repoman |
| orig_split = mysplit |
| mysplit = _overlap_dnf(mysplit) |
| dnf = mysplit is not orig_split |
| |
| mysplit2 = dep_wordreduce(mysplit, 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) |
| |
| selected_atoms = dep_zapdeps( |
| mysplit, |
| mysplit2, |
| myroot, |
| use_binaries=use_binaries, |
| trees=trees, |
| minimize_slots=dnf, |
| ) |
| |
| return [1, selected_atoms] |
| |
| |
| def _overlap_dnf(dep_struct): |
| """ |
| Combine overlapping || groups using disjunctive normal form (DNF), in |
| order to minimize the number of packages chosen to satisfy cases like |
| "|| ( foo bar ) || ( bar baz )" as in bug #632026. Non-overlapping |
| groups are excluded from the conversion, since DNF leads to exponential |
| explosion of the formula. |
| |
| When dep_struct does not contain any overlapping groups, no DNF |
| conversion will be performed, and dep_struct will be returned as-is. |
| Callers can detect this case by checking if the returned object has |
| the same identity as dep_struct. If the identity is different, then |
| DNF conversion was performed. |
| """ |
| if not _contains_disjunction(dep_struct): |
| return dep_struct |
| |
| # map atom.cp to disjunctions |
| cp_map = collections.defaultdict(list) |
| # graph atom.cp, with edges connecting atoms in the same disjunction |
| overlap_graph = digraph() |
| # map id(disjunction) to index in dep_struct, for deterministic output |
| order_map = {} |
| order_key = lambda x: order_map[id(x)] |
| result = [] |
| for i, x in enumerate(dep_struct): |
| if isinstance(x, list): |
| assert ( |
| x and x[0] == "||" |
| ), "Normalization error, nested conjunction found in %s" % (dep_struct,) |
| order_map[id(x)] = i |
| prev_cp = None |
| for atom in _iter_flatten(x): |
| if isinstance(atom, Atom) and not atom.blocker: |
| cp_map[atom.cp].append(x) |
| overlap_graph.add(atom.cp, parent=prev_cp) |
| prev_cp = atom.cp |
| if prev_cp is None: # only contains blockers |
| result.append(x) |
| else: |
| result.append(x) |
| |
| # group together disjunctions having atom.cp overlap |
| traversed = set() |
| overlap = False |
| for cp in overlap_graph: |
| if cp in traversed: |
| continue |
| disjunctions = {} |
| stack = [cp] |
| while stack: |
| cp = stack.pop() |
| traversed.add(cp) |
| for x in cp_map[cp]: |
| disjunctions[id(x)] = x |
| for other_cp in itertools.chain( |
| overlap_graph.child_nodes(cp), overlap_graph.parent_nodes(cp) |
| ): |
| if other_cp not in traversed: |
| stack.append(other_cp) |
| |
| if len(disjunctions) > 1: |
| overlap = True |
| # convert overlapping disjunctions to DNF |
| result.extend(_dnf_convert(sorted(disjunctions.values(), key=order_key))) |
| else: |
| # pass through non-overlapping disjunctions |
| result.append(disjunctions.popitem()[1]) |
| |
| return result if overlap else dep_struct |
| |
| |
| def _iter_flatten(dep_struct): |
| """ |
| Yield nested elements of dep_struct. |
| """ |
| for x in dep_struct: |
| if isinstance(x, list): |
| for x in _iter_flatten(x): |
| yield x |
| else: |
| yield x |
| |
| |
| 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 |