Bind FEATURES=-test to USE=-test for bug #373209.
[portage.git] / pym / _emerge / depgraph.py
index a669166fd4f9f716641cd6a9fafd6379bcb3a76d..ac70d4315ef6a98d449c606d6acd3d8af5830ee4 100644 (file)
@@ -22,11 +22,14 @@ from portage.dbapi.dep_expand import dep_expand
 from portage.dep import Atom, best_match_to_list, extract_affecting_use, \
        check_required_use, human_readable_required_use, match_from_list, \
        _repo_separator
+from portage.dep._slot_operator import ignore_built_slot_operator_deps
 from portage.eapi import eapi_has_strong_blocks, eapi_has_required_use
-from portage.exception import InvalidAtom, InvalidDependString, PortageException
+from portage.exception import (InvalidAtom, InvalidDependString,
+       PackageNotFound, PortageException)
 from portage.output import colorize, create_color_func, \
        darkgreen, green
 bad = create_color_func("BAD")
+from portage.package.ebuild.config import _feature_flags
 from portage.package.ebuild.getmaskingstatus import \
        _getmaskingstatus, _MaskReason
 from portage._sets import SETPREFIX
@@ -73,6 +76,9 @@ from _emerge.resolver.output import Display
 if sys.hexversion >= 0x3000000:
        basestring = str
        long = int
+       _unicode = str
+else:
+       _unicode = unicode
 
 class _scheduler_graph_config(object):
        def __init__(self, trees, pkg_cache, graph, mergelist):
@@ -110,6 +116,8 @@ class _frozen_depgraph_config(object):
                self._pkg_cache = {}
                self._highest_license_masked = {}
                dynamic_deps = myopts.get("--dynamic-deps", "y") != "n"
+               ignore_built_slot_operator_deps = myopts.get(
+                       "--ignore-built-slot-operator-deps", "n") == "y"
                for myroot in trees:
                        self.trees[myroot] = {}
                        # Create a RootConfig instance that references
@@ -124,7 +132,8 @@ class _frozen_depgraph_config(object):
                                FakeVartree(trees[myroot]["root_config"],
                                        pkg_cache=self._pkg_cache,
                                        pkg_root_config=self.roots[myroot],
-                                       dynamic_deps=dynamic_deps)
+                                       dynamic_deps=dynamic_deps,
+                                       ignore_built_slot_operator_deps=ignore_built_slot_operator_deps)
                        self.pkgsettings[myroot] = portage.config(
                                clone=self.trees[myroot]["vartree"].settings)
 
@@ -366,7 +375,11 @@ class _dynamic_depgraph_config(object):
                # This use used to check if we have accounted for blockers
                # relevant to a package.
                self._traversed_pkg_deps = set()
-               self._slot_collision_info = {}
+               # This should be ordered such that the backtracker will
+               # attempt to solve conflicts which occurred earlier first,
+               # since an earlier conflict can be the cause of a conflict
+               # which occurs later.
+               self._slot_collision_info = OrderedDict()
                # Slot collision nodes are not allowed to block other packages since
                # blocker validation is only able to account for one package per slot.
                self._slot_collision_nodes = set()
@@ -400,6 +413,7 @@ class _dynamic_depgraph_config(object):
                self._needed_license_changes = backtrack_parameters.needed_license_changes
                self._needed_use_config_changes = backtrack_parameters.needed_use_config_changes
                self._runtime_pkg_mask = backtrack_parameters.runtime_pkg_mask
+               self._slot_operator_replace_installed = backtrack_parameters.slot_operator_replace_installed
                self._need_restart = False
                # For conditions that always require user intervention, such as
                # unsatisfied REQUIRED_USE (currently has no autounmask support).
@@ -409,6 +423,8 @@ class _dynamic_depgraph_config(object):
                self._autounmask = depgraph._frozen_config.myopts.get('--autounmask') != 'n'
                self._success_without_autounmask = False
                self._traverse_ignored_deps = False
+               self._complete_mode = False
+               self._slot_operator_deps = {}
 
                for myroot in depgraph._frozen_config.trees:
                        self.sets[myroot] = _depgraph_sets()
@@ -598,11 +614,17 @@ class depgraph(object):
                                "due to non matching USE:\n\n", noiselevel=-1)
 
                for pkg, flags in self._dynamic_config.ignored_binaries.items():
-                       writemsg("    =%s" % pkg.cpv, noiselevel=-1)
+                       flag_display = []
+                       for flag in sorted(flags):
+                               if flag not in pkg.use.enabled:
+                                       flag = "-" + flag
+                               flag_display.append(flag)
+                       flag_display = " ".join(flag_display)
+                       # The user can paste this line into package.use
+                       writemsg("    =%s %s" % (pkg.cpv, flag_display), noiselevel=-1)
                        if pkg.root_config.settings["ROOT"] != "/":
-                               writemsg(" for %s" % (pkg.root,), noiselevel=-1)
-                       writemsg("\n        use flag(s): %s\n" % ", ".join(sorted(flags)),
-                               noiselevel=-1)
+                               writemsg(" # for %s" % (pkg.root,), noiselevel=-1)
+                       writemsg("\n", noiselevel=-1)
 
                msg = [
                        "",
@@ -798,16 +820,26 @@ class depgraph(object):
                        writemsg(line + '\n', noiselevel=-1)
                writemsg('\n', noiselevel=-1)
 
-       def _process_slot_conflicts(self, root, slot_atom):
+       def _process_slot_conflicts(self):
+               """
+               If there are any slot conflicts and backtracking is enabled,
+               _complete_graph should complete the graph before this method
+               is called, so that all relevant reverse dependencies are
+               available for use in backtracking decisions.
+               """
+               for (slot_atom, root), slot_nodes in \
+                       self._dynamic_config._slot_collision_info.items():
+                       self._process_slot_conflict(root, slot_atom, slot_nodes)
+
+       def _process_slot_conflict(self, root, slot_atom, slot_nodes):
                """
                Process slot conflict data to identify specific atoms which
                lead to conflict. These atoms only match a subset of the
                packages that have been pulled into a given slot.
                """
-               slot_nodes = \
-                       self._dynamic_config._slot_collision_info[(slot_atom, root)]
 
-               conflict_atoms = set()
+               debug = "--debug" in self._frozen_config.myopts
+
                slot_parent_atoms = set()
                for pkg in slot_nodes:
                        parent_atoms = self._dynamic_config._parent_atoms.get(pkg)
@@ -815,11 +847,25 @@ class depgraph(object):
                                continue
                        slot_parent_atoms.update(parent_atoms)
 
+               conflict_pkgs = []
+               conflict_atoms = {}
                for pkg in slot_nodes:
+
+                       if self._dynamic_config._allow_backtracking and \
+                               pkg in self._dynamic_config._runtime_pkg_mask:
+                               if debug:
+                                       writemsg_level(
+                                               "!!! backtracking loop detected: %s %s\n" % \
+                                               (pkg,
+                                               self._dynamic_config._runtime_pkg_mask[pkg]),
+                                               level=logging.DEBUG, noiselevel=-1)
+
                        parent_atoms = self._dynamic_config._parent_atoms.get(pkg)
                        if parent_atoms is None:
                                parent_atoms = set()
                                self._dynamic_config._parent_atoms[pkg] = parent_atoms
+
+                       all_match = True
                        for parent_atom in slot_parent_atoms:
                                if parent_atom in parent_atoms:
                                        continue
@@ -832,59 +878,34 @@ class depgraph(object):
                                        modified_use=self._pkg_use_enabled(pkg)):
                                        parent_atoms.add(parent_atom)
                                else:
-                                       conflict_atoms.add(parent_atom)
-
-               return conflict_atoms
-
-       def _handle_slot_conflict(self, existing_node, pkg, dep, arg_atoms):
-
-               debug = "--debug" in self._frozen_config.myopts
-               self._add_slot_conflict(pkg)
-
-               if dep.atom is not None and dep.parent is not None:
-                       self._add_parent_atom(pkg, (dep.parent, dep.atom))
-
-               if arg_atoms:
-                       for parent_atom in arg_atoms:
-                               parent, atom = parent_atom
-                               self._add_parent_atom(pkg, parent_atom)
-
-               conflict_atoms = \
-                       self._process_slot_conflicts(pkg.root, pkg.slot_atom)
-
-               # The existing node should not already be in
-               # runtime_pkg_mask, since that would trigger an
-               # infinite backtracking loop.
-               if self._dynamic_config._allow_backtracking and \
-                       existing_node in self._dynamic_config._runtime_pkg_mask:
-                       if debug:
-                               writemsg_level(
-                                       "!!! backtracking loop detected: %s %s\n" % \
-                                       (existing_node,
-                                       self._dynamic_config._runtime_pkg_mask[existing_node]),
-                                       level=logging.DEBUG, noiselevel=-1)
-               elif self._dynamic_config._allow_backtracking and \
-                       not self._accept_blocker_conflicts() and \
-                       not self.need_restart():
-                       self._slot_confict_backtrack(existing_node, pkg, conflict_atoms)
-                       return False
+                                       all_match = False
+                                       conflict_atoms.setdefault(parent_atom, set()).add(pkg)
 
-               if debug:
-                       writemsg_level(
-                               "%s%s %s\n" % ("Slot Conflict:".ljust(15),
-                               existing_node, pkg_use_display(existing_node,
-                               self._frozen_config.myopts,
-                               modified_use=self._pkg_use_enabled(existing_node))),
-                               level=logging.DEBUG, noiselevel=-1)
+                       if not all_match:
+                               conflict_pkgs.append(pkg)
 
-               return True
+               if conflict_pkgs and \
+                       self._dynamic_config._allow_backtracking and \
+                       not self._accept_blocker_conflicts():
+                       remaining = []
+                       for pkg in conflict_pkgs:
+                               if self._slot_conflict_backtrack_abi(pkg,
+                                       slot_nodes, conflict_atoms):
+                                       backtrack_infos = self._dynamic_config._backtrack_infos
+                                       config = backtrack_infos.setdefault("config", {})
+                                       config.setdefault("slot_conflict_abi", set()).add(pkg)
+                               else:
+                                       remaining.append(pkg)
+                       if remaining:
+                               self._slot_confict_backtrack(root, slot_atom,
+                                       slot_parent_atoms, remaining)
 
-       def _slot_confict_backtrack(self, existing_node, pkg, conflict_atoms):
+       def _slot_confict_backtrack(self, root, slot_atom,
+               all_parents, conflict_pkgs):
 
                debug = "--debug" in self._frozen_config.myopts
+               existing_node = self._dynamic_config._slot_pkg_map[root][slot_atom]
                backtrack_data = []
-               fallback_data = []
-               all_parents = set()
                # The ordering of backtrack_data can make
                # a difference here, because both mask actions may lead
                # to valid, but different, solutions and the one with
@@ -894,40 +915,19 @@ class depgraph(object):
                # existing_node masked. The backtracker reverses the
                # order, so the order it uses is the reverse of the
                # order shown here. See bug #339606.
-               for to_be_selected, to_be_masked in (existing_node, pkg), (pkg, existing_node):
+               if existing_node in conflict_pkgs and \
+                       existing_node is not conflict_pkgs[-1]:
+                       conflict_pkgs.remove(existing_node)
+                       conflict_pkgs.append(existing_node)
+               for to_be_masked in conflict_pkgs:
                        # For missed update messages, find out which
                        # atoms matched to_be_selected that did not
                        # match to_be_masked.
                        parent_atoms = \
-                               self._dynamic_config._parent_atoms.get(to_be_selected, set())
-                       if parent_atoms:
-                               p_conflict_atoms = conflict_atoms.intersection(parent_atoms)
-                               if p_conflict_atoms:
-                                       parent_atoms = p_conflict_atoms
-
-                       all_parents.update(parent_atoms)
-
-                       all_match = True
-                       for parent, atom in parent_atoms:
-                               i = InternalPackageSet(initial_atoms=(atom,),
-                                       allow_repo=True)
-                               if not i.findAtomForPackage(to_be_masked):
-                                       all_match = False
-                                       break
-
-                       fallback_data.append((to_be_masked, parent_atoms))
-
-                       if all_match:
-                               # 'to_be_masked' does not violate any parent atom, which means
-                               # there is no point in masking it.
-                               pass
-                       else:
-                               backtrack_data.append((to_be_masked, parent_atoms))
-
-               if not backtrack_data:
-                       # This shouldn't happen, but fall back to the old
-                       # behavior if this gets triggered somehow.
-                       backtrack_data = fallback_data
+                               self._dynamic_config._parent_atoms.get(to_be_masked, set())
+                       conflict_atoms = set(parent_atom for parent_atom in all_parents \
+                               if parent_atom not in parent_atoms)
+                       backtrack_data.append((to_be_masked, conflict_atoms))
 
                if len(backtrack_data) > 1:
                        # NOTE: Generally, we prefer to mask the higher
@@ -939,30 +939,284 @@ class depgraph(object):
                        # triggered when --update is not enabled.
                        if existing_node.installed:
                                pass
-                       elif pkg > existing_node:
+                       elif any(pkg > existing_node for pkg in conflict_pkgs):
                                backtrack_data.reverse()
 
                to_be_masked = backtrack_data[-1][0]
 
-               self._dynamic_config._backtrack_infos["slot conflict"] = backtrack_data
+               self._dynamic_config._backtrack_infos.setdefault(
+                       "slot conflict", []).append(backtrack_data)
                self._dynamic_config._need_restart = True
                if debug:
                        msg = []
                        msg.append("")
                        msg.append("")
                        msg.append("backtracking due to slot conflict:")
-                       if backtrack_data is fallback_data:
-                               msg.append("!!! backtrack_data fallback")
                        msg.append("   first package:  %s" % existing_node)
-                       msg.append("   second package: %s" % pkg)
                        msg.append("  package to mask: %s" % to_be_masked)
-                       msg.append("      slot: %s" % pkg.slot_atom)
+                       msg.append("      slot: %s" % slot_atom)
                        msg.append("   parents: %s" % ", ".join( \
                                "(%s, '%s')" % (ppkg, atom) for ppkg, atom in all_parents))
                        msg.append("")
                        writemsg_level("".join("%s\n" % l for l in msg),
                                noiselevel=-1, level=logging.DEBUG)
 
+       def _slot_conflict_backtrack_abi(self, pkg, slot_nodes, conflict_atoms):
+               """
+               If one or more conflict atoms have a slot/sub-slot dep that can be resolved
+               by rebuilding the parent package, then schedule the rebuild via
+               backtracking, and return True. Otherwise, return False.
+               """
+
+               found_update = False
+               for parent_atom, conflict_pkgs in conflict_atoms.items():
+                       parent, atom = parent_atom
+                       if atom.slot_operator != "=" or not parent.built:
+                               continue
+
+                       if pkg not in conflict_pkgs:
+                               continue
+
+                       for other_pkg in slot_nodes:
+                               if other_pkg in conflict_pkgs:
+                                       continue
+
+                               dep = Dependency(atom=atom, child=other_pkg,
+                                       parent=parent, root=pkg.root)
+
+                               if self._slot_operator_update_probe(dep):
+                                       self._slot_operator_update_backtrack(dep)
+                                       found_update = True
+
+               return found_update
+
+       def _slot_operator_update_backtrack(self, dep, new_child_slot=None):
+               if new_child_slot is None:
+                       child = dep.child
+               else:
+                       child = new_child_slot
+               if "--debug" in self._frozen_config.myopts:
+                       msg = []
+                       msg.append("")
+                       msg.append("")
+                       msg.append("backtracking due to missed slot abi update:")
+                       msg.append("   child package:  %s" % child)
+                       if new_child_slot is not None:
+                               msg.append("   new child slot package:  %s" % new_child_slot)
+                       msg.append("   parent package: %s" % dep.parent)
+                       msg.append("   atom: %s" % dep.atom)
+                       msg.append("")
+                       writemsg_level("\n".join(msg),
+                               noiselevel=-1, level=logging.DEBUG)
+               backtrack_infos = self._dynamic_config._backtrack_infos
+               config = backtrack_infos.setdefault("config", {})
+
+               # mask unwanted binary packages if necessary
+               abi_masks = {}
+               if new_child_slot is None:
+                       if not child.installed:
+                               abi_masks.setdefault(child, {})["slot_operator_mask_built"] = None
+               if not dep.parent.installed:
+                       abi_masks.setdefault(dep.parent, {})["slot_operator_mask_built"] = None
+               if abi_masks:
+                       config.setdefault("slot_operator_mask_built", {}).update(abi_masks)
+
+               # trigger replacement of installed packages if necessary
+               abi_reinstalls = set()
+               if dep.parent.installed:
+                       abi_reinstalls.add((dep.parent.root, dep.parent.slot_atom))
+               if new_child_slot is None and child.installed:
+                       abi_reinstalls.add((child.root, child.slot_atom))
+               if abi_reinstalls:
+                       config.setdefault("slot_operator_replace_installed",
+                               set()).update(abi_reinstalls)
+
+               self._dynamic_config._need_restart = True
+
+       def _slot_operator_update_probe(self, dep, new_child_slot=False):
+               """
+               slot/sub-slot := operators tend to prevent updates from getting pulled in,
+               since installed packages pull in packages with the slot/sub-slot that they
+               were built against. Detect this case so that we can schedule rebuilds
+               and reinstalls when appropriate.
+               NOTE: This function only searches for updates that involve upgrades
+                       to higher versions, since the logic required to detect when a
+                       downgrade would be desirable is not implemented.
+               """
+
+               if dep.child.installed and \
+                       self._frozen_config.excluded_pkgs.findAtomForPackage(dep.child,
+                       modified_use=self._pkg_use_enabled(dep.child)):
+                       return None
+
+               if dep.parent.installed and \
+                       self._frozen_config.excluded_pkgs.findAtomForPackage(dep.parent,
+                       modified_use=self._pkg_use_enabled(dep.parent)):
+                       return None
+
+               debug = "--debug" in self._frozen_config.myopts
+               want_downgrade = None
+
+               for replacement_parent in self._iter_similar_available(dep.parent,
+                       dep.parent.slot_atom):
+
+                       for atom in replacement_parent.validated_atoms:
+                               if not atom.slot_operator == "=" or \
+                                       atom.blocker or \
+                                       atom.cp != dep.atom.cp:
+                                       continue
+
+                               # Discard USE deps, we're only searching for an approximate
+                               # pattern, and dealing with USE states is too complex for
+                               # this purpose.
+                               atom = atom.without_use
+
+                               if replacement_parent.built and \
+                                       portage.dep._match_slot(atom, dep.child):
+                                       # Our selected replacement_parent appears to be built
+                                       # for the existing child selection. So, discard this
+                                       # parent and search for another.
+                                       break
+
+                               for pkg in self._iter_similar_available(
+                                       dep.child, atom):
+                                       if pkg.slot == dep.child.slot and \
+                                               pkg.sub_slot == dep.child.sub_slot:
+                                               # If slot/sub-slot is identical, then there's
+                                               # no point in updating.
+                                               continue
+                                       if new_child_slot:
+                                               if pkg.slot == dep.child.slot:
+                                                       continue
+                                               if pkg < dep.child:
+                                                       # the new slot only matters if the
+                                                       # package version is higher
+                                                       continue
+                                       else:
+                                               if pkg.slot != dep.child.slot:
+                                                       continue
+                                               if pkg < dep.child:
+                                                       if want_downgrade is None:
+                                                               want_downgrade = self._downgrade_probe(dep.child)
+                                                       # be careful not to trigger a rebuild when
+                                                       # the only version available with a
+                                                       # different slot_operator is an older version
+                                                       if not want_downgrade:
+                                                               continue
+
+                                       if debug:
+                                               msg = []
+                                               msg.append("")
+                                               msg.append("")
+                                               msg.append("slot_operator_update_probe:")
+                                               msg.append("   existing child package:  %s" % dep.child)
+                                               msg.append("   existing parent package: %s" % dep.parent)
+                                               msg.append("   new child package:  %s" % pkg)
+                                               msg.append("   new parent package: %s" % replacement_parent)
+                                               msg.append("")
+                                               writemsg_level("\n".join(msg),
+                                                       noiselevel=-1, level=logging.DEBUG)
+
+                                       return pkg
+
+               if debug:
+                       msg = []
+                       msg.append("")
+                       msg.append("")
+                       msg.append("slot_operator_update_probe:")
+                       msg.append("   existing child package:  %s" % dep.child)
+                       msg.append("   existing parent package: %s" % dep.parent)
+                       msg.append("   new child package:  %s" % None)
+                       msg.append("   new parent package: %s" % None)
+                       msg.append("")
+                       writemsg_level("\n".join(msg),
+                               noiselevel=-1, level=logging.DEBUG)
+
+               return None
+
+       def _downgrade_probe(self, pkg):
+               """
+               Detect cases where a downgrade of the given package is considered
+               desirable due to the current version being masked or unavailable.
+               """
+               available_pkg = None
+               for available_pkg in self._iter_similar_available(pkg,
+                       pkg.slot_atom):
+                       if available_pkg >= pkg:
+                               # There's an available package of the same or higher
+                               # version, so downgrade seems undesirable.
+                               return False
+
+               return available_pkg is not None
+
+       def _iter_similar_available(self, graph_pkg, atom):
+               """
+               Given a package that's in the graph, do a rough check to
+               see if a similar package is available to install. The given
+               graph_pkg itself may be yielded only if it's not installed.
+               """
+
+               usepkgonly = "--usepkgonly" in self._frozen_config.myopts
+               useoldpkg_atoms = self._frozen_config.useoldpkg_atoms
+               use_ebuild_visibility = self._frozen_config.myopts.get(
+                       '--use-ebuild-visibility', 'n') != 'n'
+
+               for pkg in self._iter_match_pkgs_any(
+                       graph_pkg.root_config, atom):
+                       if pkg.cp != graph_pkg.cp:
+                               # discard old-style virtual match
+                               continue
+                       if pkg.installed:
+                               continue
+                       if pkg in self._dynamic_config._runtime_pkg_mask:
+                               continue
+                       if self._frozen_config.excluded_pkgs.findAtomForPackage(pkg,
+                               modified_use=self._pkg_use_enabled(pkg)):
+                               continue
+                       if not self._pkg_visibility_check(pkg):
+                               continue
+                       if pkg.built:
+                               if self._equiv_binary_installed(pkg):
+                                       continue
+                               if not (not use_ebuild_visibility and
+                                       (usepkgonly or useoldpkg_atoms.findAtomForPackage(
+                                       pkg, modified_use=self._pkg_use_enabled(pkg)))) and \
+                                       not self._equiv_ebuild_visible(pkg):
+                                       continue
+                       yield pkg
+
+       def _slot_operator_trigger_reinstalls(self):
+               """
+               Search for packages with slot-operator deps on older slots, and schedule
+               rebuilds if they can link to a newer slot that's in the graph.
+               """
+
+               rebuild_if_new_slot = self._dynamic_config.myparams.get(
+                       "rebuild_if_new_slot", "y") == "y"
+
+               for slot_key, slot_info in self._dynamic_config._slot_operator_deps.items():
+
+                       for dep in slot_info:
+                               if not (dep.child.built and dep.parent and
+                                       isinstance(dep.parent, Package) and dep.parent.built):
+                                       continue
+
+                               # Check for slot update first, since we don't want to
+                               # trigger reinstall of the child package when a newer
+                               # slot will be used instead.
+                               if rebuild_if_new_slot:
+                                       new_child = self._slot_operator_update_probe(dep,
+                                               new_child_slot=True)
+                                       if new_child:
+                                               self._slot_operator_update_backtrack(dep,
+                                                       new_child_slot=new_child)
+                                               break
+
+                               if dep.want_update:
+                                       if self._slot_operator_update_probe(dep):
+                                               self._slot_operator_update_backtrack(dep)
+                                               break
+
        def _reinstall_for_flags(self, pkg, forced_flags,
                orig_use, orig_iuse, cur_use, cur_iuse):
                """Return a set of flags that trigger reinstallation, or None if there
@@ -981,12 +1235,14 @@ class depgraph(object):
                                cur_iuse).difference(forced_flags))
                        flags.update(orig_iuse.intersection(orig_use).symmetric_difference(
                                cur_iuse.intersection(cur_use)))
+                       flags.difference_update(_feature_flags)
                        if flags:
                                return flags
 
                elif changed_use or binpkg_respect_use:
-                       flags = orig_iuse.intersection(orig_use).symmetric_difference(
-                               cur_iuse.intersection(cur_use))
+                       flags = set(orig_iuse.intersection(orig_use).symmetric_difference(
+                               cur_iuse.intersection(cur_use)))
+                       flags.difference_update(_feature_flags)
                        if flags:
                                return flags
                return None
@@ -1235,7 +1491,8 @@ class depgraph(object):
                        required_use_is_sat = check_required_use(
                                pkg.metadata["REQUIRED_USE"],
                                self._pkg_use_enabled(pkg),
-                               pkg.iuse.is_valid_flag)
+                               pkg.iuse.is_valid_flag,
+                               eapi=pkg.metadata["EAPI"])
                        if not required_use_is_sat:
                                if dep.atom is not None and dep.parent is not None:
                                        self._add_parent_atom(pkg, (dep.parent, dep.atom))
@@ -1281,10 +1538,15 @@ class depgraph(object):
                                                                (dep.parent, dep.atom))
                                        return 1
                                else:
-                                       # A slot conflict has occurred. 
-                                       if not self._handle_slot_conflict(
-                                               existing_node, pkg, dep, arg_atoms):
-                                               return False
+                                       self._add_slot_conflict(pkg)
+                                       if debug:
+                                               writemsg_level(
+                                                       "%s%s %s\n" % ("Slot Conflict:".ljust(15),
+                                                       existing_node, pkg_use_display(existing_node,
+                                                       self._frozen_config.myopts,
+                                                       modified_use=self._pkg_use_enabled(existing_node))),
+                                                       level=logging.DEBUG, noiselevel=-1)
+
                                        slot_collision = True
 
                        if slot_collision:
@@ -1343,10 +1605,27 @@ class depgraph(object):
                # Installing package A, we need to make sure package A's deps are met.
                # emerge --deep <pkgspec>; we need to recursively check dependencies of pkgspec
                # If we are in --nodeps (no recursion) mode, we obviously only check 1 level of dependencies.
-               if arg_atoms:
-                       depth = 0
+               if arg_atoms and depth > 0:
+                       for parent, atom in arg_atoms:
+                               if parent.reset_depth:
+                                       depth = 0
+                                       break
+
+               if previously_added and pkg.depth is not None:
+                       depth = min(pkg.depth, depth)
                pkg.depth = depth
                deep = self._dynamic_config.myparams.get("deep", 0)
+               update = "--update" in self._frozen_config.myopts
+
+               dep.want_update = (not self._dynamic_config._complete_mode and
+                       (arg_atoms or update) and
+                       not (deep is not True and depth > deep))
+
+               dep.child = pkg
+               if (not pkg.onlydeps and pkg.built and
+                       dep.atom and dep.atom.slot_operator_built):
+                       self._add_slot_operator_dep(dep)
+
                recurse = deep is True or depth + 1 <= deep
                dep_stack = self._dynamic_config._dep_stack
                if "recurse" not in self._dynamic_config.myparams:
@@ -1378,6 +1657,14 @@ class depgraph(object):
                        self._dynamic_config._parent_atoms[pkg] = parent_atoms
                parent_atoms.add(parent_atom)
 
+       def _add_slot_operator_dep(self, dep):
+               slot_key = (dep.root, dep.child.slot_atom)
+               slot_info = self._dynamic_config._slot_operator_deps.get(slot_key)
+               if slot_info is None:
+                       slot_info = []
+                       self._dynamic_config._slot_operator_deps[slot_key] = slot_info
+               slot_info.append(dep)
+
        def _add_slot_conflict(self, pkg):
                self._dynamic_config._slot_collision_nodes.add(pkg)
                slot_key = (pkg.slot_atom, pkg.root)
@@ -1620,16 +1907,11 @@ class depgraph(object):
                                self._dynamic_config._slot_pkg_map[dep.child.root].get(
                                dep.child.slot_atom) is None:
                                myarg = None
-                               if dep.root == self._frozen_config.target_root:
-                                       try:
-                                               myarg = next(self._iter_atoms_for_pkg(dep.child))
-                                       except StopIteration:
-                                               pass
-                                       except InvalidDependString:
-                                               if not dep.child.installed:
-                                                       # This shouldn't happen since the package
-                                                       # should have been masked.
-                                                       raise
+                               try:
+                                       myarg = next(self._iter_atoms_for_pkg(dep.child), None)
+                               except InvalidDependString:
+                                       if not dep.child.installed:
+                                               raise
 
                                if myarg is None:
                                        # Existing child selection may not be valid unless
@@ -1735,14 +2017,11 @@ class depgraph(object):
                                        self._dynamic_config._slot_pkg_map[dep.child.root].get(
                                        dep.child.slot_atom) is None:
                                        myarg = None
-                                       if dep.root == self._frozen_config.target_root:
-                                               try:
-                                                       myarg = next(self._iter_atoms_for_pkg(dep.child))
-                                               except StopIteration:
-                                                       pass
-                                               except InvalidDependString:
-                                                       if not dep.child.installed:
-                                                               raise
+                                       try:
+                                               myarg = next(self._iter_atoms_for_pkg(dep.child), None)
+                                       except InvalidDependString:
+                                               if not dep.child.installed:
+                                                       raise
 
                                        if myarg is None:
                                                ignored = True
@@ -1836,9 +2115,14 @@ class depgraph(object):
                        # Yield ~, =*, < and <= atoms first, since those are more likely to
                        # cause slot conflicts, and we want those atoms to be displayed
                        # in the resulting slot conflict message (see bug #291142).
+                       # Give similar treatment to slot/sub-slot atoms.
                        conflict_atoms = []
                        normal_atoms = []
+                       abi_atoms = []
                        for atom in cp_atoms:
+                               if atom.slot_operator_built:
+                                       abi_atoms.append(atom)
+                                       continue
                                conflict = False
                                for child_pkg in atom_pkg_graph.child_nodes(atom):
                                        existing_node, matches = \
@@ -1851,7 +2135,7 @@ class depgraph(object):
                                else:
                                        normal_atoms.append(atom)
 
-                       for atom in chain(conflict_atoms, normal_atoms):
+                       for atom in chain(abi_atoms, conflict_atoms, normal_atoms):
                                child_pkgs = atom_pkg_graph.child_nodes(atom)
                                # if more than one child, yield highest version
                                if len(child_pkgs) > 1:
@@ -2251,6 +2535,7 @@ class depgraph(object):
                        args = revised_greedy_args
                        del revised_greedy_args
 
+               args.extend(self._gen_reinstall_sets())
                self._set_args(args)
 
                myfavorites = set(myfavorites)
@@ -2258,7 +2543,8 @@ class depgraph(object):
                        if isinstance(arg, (AtomArg, PackageArg)):
                                myfavorites.add(arg.atom)
                        elif isinstance(arg, SetArg):
-                               myfavorites.add(arg.arg)
+                               if not arg.internal:
+                                       myfavorites.add(arg.arg)
                myfavorites = list(myfavorites)
 
                if debug:
@@ -2268,7 +2554,33 @@ class depgraph(object):
                self._dynamic_config._initial_arg_list = args[:]
        
                return self._resolve(myfavorites)
-       
+
+       def _gen_reinstall_sets(self):
+
+               atom_list = []
+               for root, atom in self._rebuild.rebuild_list:
+                       atom_list.append((root, '__auto_rebuild__', atom))
+               for root, atom in self._rebuild.reinstall_list:
+                       atom_list.append((root, '__auto_reinstall__', atom))
+               for root, atom in self._dynamic_config._slot_operator_replace_installed:
+                       atom_list.append((root, '__auto_slot_operator_replace_installed__', atom))
+
+               set_dict = {}
+               for root, set_name, atom in atom_list:
+                       set_dict.setdefault((root, set_name), []).append(atom)
+
+               for (root, set_name), atoms in set_dict.items():
+                       yield SetArg(arg=(SETPREFIX + set_name),
+                               # Set reset_depth=False here, since we don't want these
+                               # special sets to interact with depth calculations (see
+                               # the emerge --deep=DEPTH option), though we want them
+                               # to behave like normal arguments in most other respects.
+                               pset=InternalPackageSet(initial_atoms=atoms),
+                               force_reinstall=True,
+                               internal=True,
+                               reset_depth=False,
+                               root_config=self._frozen_config.roots[root])
+
        def _resolve(self, myfavorites):
                """Given self._dynamic_config._initial_arg_list, pull in the root nodes, 
                call self._creategraph to process theier deps and return 
@@ -2280,10 +2592,7 @@ class depgraph(object):
                pprovideddict = pkgsettings.pprovideddict
                virtuals = pkgsettings.getvirtuals()
                args = self._dynamic_config._initial_arg_list[:]
-               for root, atom in chain(self._rebuild.rebuild_list,
-                       self._rebuild.reinstall_list):
-                       args.append(AtomArg(arg=atom, atom=atom,
-                               root_config=self._frozen_config.roots[root]))
+
                for arg in self._expand_set_args(args, add_to_digraph=True):
                        for atom in arg.pset.getAtoms():
                                self._spinner_update()
@@ -2393,6 +2702,32 @@ class depgraph(object):
                except self._unknown_internal_error:
                        return False, myfavorites
 
+               if (self._dynamic_config._slot_collision_info and
+                       not self._accept_blocker_conflicts()) or \
+                       (self._dynamic_config._allow_backtracking and
+                       "slot conflict" in self._dynamic_config._backtrack_infos):
+                       return False, myfavorites
+
+               if self._rebuild.trigger_rebuilds():
+                       backtrack_infos = self._dynamic_config._backtrack_infos
+                       config = backtrack_infos.setdefault("config", {})
+                       config["rebuild_list"] = self._rebuild.rebuild_list
+                       config["reinstall_list"] = self._rebuild.reinstall_list
+                       self._dynamic_config._need_restart = True
+                       return False, myfavorites
+
+               if "config" in self._dynamic_config._backtrack_infos and \
+                       ("slot_operator_mask_built" in self._dynamic_config._backtrack_infos["config"] or
+                       "slot_operator_replace_installed" in self._dynamic_config._backtrack_infos["config"]) and \
+                       self.need_restart():
+                       return False, myfavorites
+
+               # Any failures except those due to autounmask *alone* should return
+               # before this point, since the success_without_autounmask flag that's
+               # set below is reserved for cases where there are *zero* other
+               # problems. For reference, see backtrack_depgraph, where it skips the
+               # get_best_run() call when success_without_autounmask is True.
+
                digraph_nodes = self._dynamic_config.digraph.nodes
 
                if any(x in digraph_nodes for x in
@@ -2407,14 +2742,6 @@ class depgraph(object):
                        self._dynamic_config._success_without_autounmask = True
                        return False, myfavorites
 
-               if self._rebuild.trigger_rebuilds():
-                       backtrack_infos = self._dynamic_config._backtrack_infos
-                       config = backtrack_infos.setdefault("config", {})
-                       config["rebuild_list"] = self._rebuild.rebuild_list
-                       config["reinstall_list"] = self._rebuild.reinstall_list
-                       self._dynamic_config._need_restart = True
-                       return False, myfavorites
-
                # We're true here unless we are missing binaries.
                return (True, myfavorites)
 
@@ -2569,6 +2896,22 @@ class depgraph(object):
                """This will raise InvalidDependString if necessary. If trees is
                None then self._dynamic_config._filtered_trees is used."""
 
+               if not isinstance(depstring, list):
+                       eapi = None
+                       is_valid_flag = None
+                       if parent is not None:
+                               eapi = parent.metadata['EAPI']
+                               if not parent.installed:
+                                       is_valid_flag = parent.iuse.is_valid_flag
+                       depstring = portage.dep.use_reduce(depstring,
+                               uselist=myuse, opconvert=True, token_class=Atom,
+                               is_valid_flag=is_valid_flag, eapi=eapi)
+
+               if (self._dynamic_config.myparams.get(
+                       "ignore_built_slot_operator_deps", "n") == "y" and
+                       parent and parent.built):
+                       ignore_built_slot_operator_deps(depstring)
+
                pkgsettings = self._frozen_config.pkgsettings[root]
                if trees is None:
                        trees = self._dynamic_config._filtered_trees
@@ -3039,7 +3382,8 @@ class depgraph(object):
                                                        if not check_required_use(
                                                                pkg.metadata["REQUIRED_USE"],
                                                                self._pkg_use_enabled(pkg),
-                                                               pkg.iuse.is_valid_flag):
+                                                               pkg.iuse.is_valid_flag,
+                                                               eapi=pkg.metadata["EAPI"]):
                                                                required_use_unsatisfied.append(pkg)
                                                                continue
                                                root_slot = (pkg.root, pkg.slot_atom)
@@ -3098,8 +3442,10 @@ class depgraph(object):
                                                new_use.add(flag)
                                        for flag in need_disable:
                                                new_use.discard(flag)
-                                       if check_required_use(required_use, old_use, pkg.iuse.is_valid_flag) and \
-                                               not check_required_use(required_use, new_use, pkg.iuse.is_valid_flag):
+                                       if check_required_use(required_use, old_use,
+                                               pkg.iuse.is_valid_flag, eapi=pkg.metadata["EAPI"]) \
+                                               and not check_required_use(required_use, new_use,
+                                               pkg.iuse.is_valid_flag, eapi=pkg.metadata["EAPI"]):
                                                        required_use_warning = ", this change violates use flag constraints " + \
                                                                "defined by %s: '%s'" % (pkg.cpv, human_readable_required_use(required_use))
 
@@ -3147,8 +3493,12 @@ class depgraph(object):
                                                                new_use.discard(flag)
                                                        else:
                                                                new_use.add(flag)
-                                               if check_required_use(required_use, old_use, myparent.iuse.is_valid_flag) and \
-                                                       not check_required_use(required_use, new_use, myparent.iuse.is_valid_flag):
+                                               if check_required_use(required_use, old_use,
+                                                       myparent.iuse.is_valid_flag,
+                                                       eapi=myparent.metadata["EAPI"]) and \
+                                                       not check_required_use(required_use, new_use,
+                                                       myparent.iuse.is_valid_flag,
+                                                       eapi=myparent.metadata["EAPI"]):
                                                                required_use_warning = ", this change violates use flag constraints " + \
                                                                        "defined by %s: '%s'" % (myparent.cpv, \
                                                                        human_readable_required_use(required_use))
@@ -3237,7 +3587,8 @@ class depgraph(object):
                        reduced_noise = check_required_use(
                                pkg.metadata["REQUIRED_USE"],
                                self._pkg_use_enabled(pkg),
-                               pkg.iuse.is_valid_flag).tounicode()
+                               pkg.iuse.is_valid_flag,
+                               eapi=pkg.metadata["EAPI"]).tounicode()
                        writemsg("    %s\n" % \
                                human_readable_required_use(reduced_noise),
                                noiselevel=-1)
@@ -3497,21 +3848,55 @@ class depgraph(object):
                True if the user has not explicitly requested for this package
                to be replaced (typically via an atom on the command line).
                """
-               if "selective" not in self._dynamic_config.myparams and \
-                       pkg.root == self._frozen_config.target_root:
-                       if self._frozen_config.excluded_pkgs.findAtomForPackage(pkg,
-                               modified_use=self._pkg_use_enabled(pkg)):
-                               return True
-                       try:
-                               next(self._iter_atoms_for_pkg(pkg))
-                       except StopIteration:
-                               pass
-                       except portage.exception.InvalidDependString:
-                               pass
-                       else:
+               if self._frozen_config.excluded_pkgs.findAtomForPackage(pkg,
+                       modified_use=self._pkg_use_enabled(pkg)):
+                       return True
+
+               arg = False
+               try:
+                       for arg, atom in self._iter_atoms_for_pkg(pkg):
+                               if arg.force_reinstall:
+                                       return False
+               except InvalidDependString:
+                       pass
+
+               if "selective" in self._dynamic_config.myparams:
+                       return True
+
+               return not arg
+
+       def _equiv_ebuild_visible(self, pkg, autounmask_level=None):
+               try:
+                       pkg_eb = self._pkg(
+                               pkg.cpv, "ebuild", pkg.root_config, myrepo=pkg.repo)
+               except portage.exception.PackageNotFound:
+                       pkg_eb_visible = False
+                       for pkg_eb in self._iter_match_pkgs(pkg.root_config,
+                               "ebuild", Atom("=%s" % (pkg.cpv,))):
+                               if self._pkg_visibility_check(pkg_eb, autounmask_level):
+                                       pkg_eb_visible = True
+                                       break
+                       if not pkg_eb_visible:
                                return False
+               else:
+                       if not self._pkg_visibility_check(pkg_eb, autounmask_level):
+                               return False
+
                return True
 
+       def _equiv_binary_installed(self, pkg):
+               build_time = pkg.metadata.get('BUILD_TIME')
+               if not build_time:
+                       return False
+
+               try:
+                       inst_pkg = self._pkg(pkg.cpv, "installed",
+                               pkg.root_config, installed=True)
+               except PackageNotFound:
+                       return False
+
+               return build_time == inst_pkg.metadata.get('BUILD_TIME')
+
        class _AutounmaskLevel(object):
                __slots__ = ("allow_use_changes", "allow_unstable_keywords", "allow_license_changes", \
                        "allow_missing_keywords", "allow_unmasks")
@@ -3766,8 +4151,10 @@ class depgraph(object):
                if new_changes != old_changes:
                        #Don't do the change if it violates REQUIRED_USE.
                        required_use = pkg.metadata.get("REQUIRED_USE")
-                       if required_use and check_required_use(required_use, old_use, pkg.iuse.is_valid_flag) and \
-                               not check_required_use(required_use, new_use, pkg.iuse.is_valid_flag):
+                       if required_use and check_required_use(required_use, old_use,
+                               pkg.iuse.is_valid_flag, eapi=pkg.metadata["EAPI"]) and \
+                               not check_required_use(required_use, new_use,
+                               pkg.iuse.is_valid_flag, eapi=pkg.metadata["EAPI"]):
                                return old_use
 
                        if any(x in pkg.use.mask for x in new_changes) or \
@@ -3942,37 +4329,24 @@ class depgraph(object):
                                                                if not use_ebuild_visibility and (usepkgonly or useoldpkg):
                                                                        if pkg.installed and pkg.masks:
                                                                                continue
-                                                               else:
-                                                                       try:
-                                                                               pkg_eb = self._pkg(
-                                                                                       pkg.cpv, "ebuild", root_config, myrepo=pkg.repo)
-                                                                       except portage.exception.PackageNotFound:
-                                                                               pkg_eb_visible = False
-                                                                               for pkg_eb in self._iter_match_pkgs(pkg.root_config,
-                                                                                       "ebuild", Atom("=%s" % (pkg.cpv,))):
-                                                                                       if self._pkg_visibility_check(pkg_eb, autounmask_level):
-                                                                                               pkg_eb_visible = True
-                                                                                               break
-                                                                               if not pkg_eb_visible:
-                                                                                       continue
-                                                                       else:
-                                                                               if not self._pkg_visibility_check(pkg_eb, autounmask_level):
-                                                                                       continue
+                                                               elif not self._equiv_ebuild_visible(pkg,
+                                                                       autounmask_level=autounmask_level):
+                                                                       continue
 
                                        # Calculation of USE for unbuilt ebuilds is relatively
                                        # expensive, so it is only performed lazily, after the
                                        # above visibility checks are complete.
 
                                        myarg = None
-                                       if root == self._frozen_config.target_root:
-                                               try:
-                                                       myarg = next(self._iter_atoms_for_pkg(pkg))
-                                               except StopIteration:
-                                                       pass
-                                               except portage.exception.InvalidDependString:
-                                                       if not installed:
-                                                               # masked by corruption
-                                                               continue
+                                       try:
+                                               for myarg, myarg_atom in self._iter_atoms_for_pkg(pkg):
+                                                       if myarg.force_reinstall:
+                                                               reinstall = True
+                                                               break
+                                       except InvalidDependString:
+                                               if not installed:
+                                                       # masked by corruption
+                                                       continue
                                        if not installed and myarg:
                                                found_available_arg = True
 
@@ -4291,9 +4665,19 @@ class depgraph(object):
                        "recurse" not in self._dynamic_config.myparams:
                        return 1
 
+               complete_if_new_use = self._dynamic_config.myparams.get(
+                       "complete_if_new_use", "y") == "y"
+               complete_if_new_ver = self._dynamic_config.myparams.get(
+                       "complete_if_new_ver", "y") == "y"
+               rebuild_if_new_slot = self._dynamic_config.myparams.get(
+                       "rebuild_if_new_slot", "y") == "y"
+               complete_if_new_slot = rebuild_if_new_slot
+
                if "complete" not in self._dynamic_config.myparams and \
-                       self._dynamic_config.myparams.get("complete_if_new_ver", "y") == "y":
-                       # Enable complete mode if an installed package version will change.
+                       (complete_if_new_use or
+                       complete_if_new_ver or complete_if_new_slot):
+                       # Enable complete mode if an installed package will change somehow.
+                       use_change = False
                        version_change = False
                        for node in self._dynamic_config.digraph:
                                if not isinstance(node, Package) or \
@@ -4301,12 +4685,35 @@ class depgraph(object):
                                        continue
                                vardb = self._frozen_config.roots[
                                        node.root].trees["vartree"].dbapi
-                               inst_pkg = vardb.match_pkgs(node.slot_atom)
-                               if inst_pkg and (inst_pkg[0] > node or inst_pkg[0] < node):
-                                       version_change = True
-                                       break
 
-                       if version_change:
+                               if complete_if_new_use or complete_if_new_ver:
+                                       inst_pkg = vardb.match_pkgs(node.slot_atom)
+                                       if inst_pkg and inst_pkg[0].cp == node.cp:
+                                               inst_pkg = inst_pkg[0]
+                                               if complete_if_new_ver and \
+                                                       (inst_pkg < node or node < inst_pkg):
+                                                       version_change = True
+                                                       break
+
+                                               # Intersect enabled USE with IUSE, in order to
+                                               # ignore forced USE from implicit IUSE flags, since
+                                               # they're probably irrelevant and they are sensitive
+                                               # to use.mask/force changes in the profile.
+                                               if complete_if_new_use and \
+                                                       (node.iuse.all != inst_pkg.iuse.all or
+                                                       self._pkg_use_enabled(node).intersection(node.iuse.all) !=
+                                                       self._pkg_use_enabled(inst_pkg).intersection(inst_pkg.iuse.all)):
+                                                       use_change = True
+                                                       break
+
+                               if complete_if_new_slot:
+                                       cp_list = vardb.match_pkgs(Atom(node.cp))
+                                       if (cp_list and cp_list[0].cp == node.cp and
+                                               not any(node.slot == pkg.slot for pkg in cp_list)):
+                                               version_change = True
+                                               break
+
+                       if use_change or version_change:
                                self._dynamic_config.myparams["complete"] = True
 
                if "complete" not in self._dynamic_config.myparams:
@@ -4320,6 +4727,7 @@ class depgraph(object):
                # scheduled for replacement. Also, toggle the "deep"
                # parameter so that all dependencies are traversed and
                # accounted for.
+               self._dynamic_config._complete_mode = True
                self._select_atoms = self._select_atoms_from_graph
                if "remove" in self._dynamic_config.myparams:
                        self._select_package = self._select_pkg_from_installed
@@ -4935,9 +5343,20 @@ class depgraph(object):
                                root_config.root]["root_config"] = root_config
 
        def _resolve_conflicts(self):
+
+               if "complete" not in self._dynamic_config.myparams and \
+                       self._dynamic_config._allow_backtracking and \
+                       self._dynamic_config._slot_collision_nodes and \
+                       not self._accept_blocker_conflicts():
+                       self._dynamic_config.myparams["complete"] = True
+
                if not self._complete_graph():
                        raise self._unknown_internal_error()
 
+               self._process_slot_conflicts()
+
+               self._slot_operator_trigger_reinstalls()
+
                if not self._validate_blockers():
                        self._dynamic_config._skip_restart = True
                        raise self._unknown_internal_error()
@@ -6396,6 +6815,9 @@ class depgraph(object):
                                continue
                        if arg.root_config.root != root_config.root:
                                continue
+                       if arg.internal:
+                               # __auto_* sets
+                               continue
                        k = arg.name
                        if k in ("selected", "world") or \
                                not root_config.sets[k].world_candidate:
@@ -6407,9 +6829,13 @@ class depgraph(object):
                all_added.extend(added_favorites)
                all_added.sort()
                for a in all_added:
+                       if a.startswith(SETPREFIX):
+                               filename = "world_sets"
+                       else:
+                               filename = "world"
                        writemsg_stdout(
-                               ">>> Recording %s in \"world\" favorites file...\n" % \
-                               colorize("INFORM", str(a)), noiselevel=-1)
+                               ">>> Recording %s in \"%s\" favorites file...\n" %
+                               (colorize("INFORM", _unicode(a)), filename), noiselevel=-1)
                if all_added:
                        world_set.update(all_added)
 
@@ -6788,13 +7214,8 @@ class _dep_check_composite_db(dbapi):
                return ret[:]
 
        def _visible(self, pkg):
-               if pkg.installed and "selective" not in self._depgraph._dynamic_config.myparams:
-                       try:
-                               arg = next(self._depgraph._iter_atoms_for_pkg(pkg))
-                       except (StopIteration, portage.exception.InvalidDependString):
-                               arg = None
-                       if arg:
-                               return False
+               if pkg.installed and not self._depgraph._want_installed_pkg(pkg):
+                       return False
                if pkg.installed and \
                        (pkg.masks or not self._depgraph._pkg_visibility_check(pkg)):
                        # Account for packages with masks (like KEYWORDS masks)
@@ -6810,24 +7231,8 @@ class _dep_check_composite_db(dbapi):
                        if not avoid_update:
                                if not use_ebuild_visibility and usepkgonly:
                                        return False
-                               else:
-                                       try:
-                                               pkg_eb = self._depgraph._pkg(
-                                                       pkg.cpv, "ebuild", pkg.root_config,
-                                                       myrepo=pkg.repo)
-                                       except portage.exception.PackageNotFound:
-                                               pkg_eb_visible = False
-                                               for pkg_eb in self._depgraph._iter_match_pkgs(
-                                                       pkg.root_config, "ebuild",
-                                                       Atom("=%s" % (pkg.cpv,))):
-                                                       if self._depgraph._pkg_visibility_check(pkg_eb):
-                                                               pkg_eb_visible = True
-                                                               break
-                                               if not pkg_eb_visible:
-                                                       return False
-                                       else:
-                                               if not self._depgraph._pkg_visibility_check(pkg_eb):
-                                                       return False
+                               elif not self._depgraph._equiv_ebuild_visible(pkg):
+                                       return False
 
                in_graph = self._depgraph._dynamic_config._slot_pkg_map[
                        self._root].get(pkg.slot_atom)