From 0d2d255eaf89919f72d153645bd4bfa20285dfd9 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Mon, 1 Dec 2008 21:14:48 +0000 Subject: [PATCH] Merged from trunk -r12117:12126 | 12120 | Add 'automatically resolved' blockers to the --pretend docs. | | zmedico | | | 12122 | Add a sanity check inside depgraph._add_pkg() to ensure that | | zmedico | the dependencies of the same package are never processed | | | more than once. | | 12124 | Change depgraph._slot_collision_info() from a set to a dict | | zmedico | that contains sets of packages pulled into a given slot. | | | This will make the data easier to analyze when implementing | | | a fix for bug #249185. | | 12125 | Bug #249185 - For more useful output in cases when one or | | zmedico | more USE deps trigger "SLOT conflicts", show the specific | | | atoms that triggered the conflict. TODO: Distiguish between | | | various possible causes and tailor messages to suit them. | | 12126 | Fix inconsistencies between the "clean" and "cleanrm" | | zmedico | phases. | svn path=/main/branches/prefix/; revision=12133 --- bin/ebuild.sh | 6 +- man/emerge.1 | 3 +- pym/_emerge/__init__.py | 203 ++++++++++++++++++++++++++++------------ 3 files changed, 147 insertions(+), 65 deletions(-) diff --git a/bin/ebuild.sh b/bin/ebuild.sh index d614d3b8e..c6e3cc365 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -1810,7 +1810,7 @@ if [[ -n ${QA_INTERCEPTORS} ]] ; then unset BIN_PATH BIN BODY FUNC_SRC fi -if ! hasq ${EBUILD_PHASE} clean depend && \ +if ! hasq "$EBUILD_PHASE" clean cleanrm depend && \ [ -f "${T}"/environment ] ; then # The environment may have been extracted from environment.bz2 or # may have come from another version of ebuild.sh or something. @@ -1856,7 +1856,7 @@ if ! hasq ${EBUILD_PHASE} clean depend && \ source_all_bashrcs fi -if ! hasq ${EBUILD_PHASE} clean && \ +if ! hasq "$EBUILD_PHASE" clean cleanrm && \ ( hasq ${EBUILD_PHASE} depend || \ [ ! -f "${T}"/environment ] || \ @@ -1903,7 +1903,7 @@ fi [[ -n ${EAPI/prefix/} ]] || EAPI="${EAPI}${EAPI:+ }0" # enable bashrc support for the clean phase -[[ ${EBUILD_PHASE} == clean ]] && source_all_bashrcs +hasq "$EBUILD_PHASE" clean cleanrm && source_all_bashrcs # unset USE_EXPAND variables that contain only the special "*" token for x in ${USE_EXPAND} ; do diff --git a/man/emerge.1 b/man/emerge.1 index 47df88c48..391ec0395 100644 --- a/man/emerge.1 +++ b/man/emerge.1 @@ -405,7 +405,8 @@ R replacing (remerging same version)) F fetch restricted (must be manually downloaded) f fetch restricted (already downloaded) I interactive (requires user input) -B blocked by an already installed package +B blocked by another package (unresolved conflict) +b blocked by another package (automatically resolved conflict) .TE .TP .BR "\-\-quiet " (\fB\-q\fR) diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index bf81246e8..75979b339 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -4346,10 +4346,12 @@ class depgraph(object): self._irrelevant_blockers = digraph() # Contains only unsolvable Package -> Blocker edges self._unsolvable_blockers = digraph() - self._slot_collision_info = set() + self._slot_collision_info = {} # 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() + self._parent_atoms = {} + self._slot_conflict_parent_atoms = set() self._serialized_tasks_cache = None self._scheduler_graph = None self._displayed_list = None @@ -4373,7 +4375,25 @@ class depgraph(object): the packages. In some cases it may be possible to resolve this automatically, but support for backtracking (removal nodes that have already been selected) will be required in order to handle all possible - cases.""" + cases. + + When a slot conflict occurs due to USE deps, there are a few + different cases to consider: + + 1) New USE are correctly set but --newuse wasn't requested so an + installed package with incorrect USE happened to get pulled + into graph before the new one. + + 2) New USE are incorrectly set but an installed package has correct + USE so it got pulled into the graph, and a new instance also got + pulled in due to --newuse or an upgrade. + + 3) Multiple USE deps exist that can't be satisfied simultaneously, + and multiple package instances got pulled into the same slot to + satisfy the conflicting deps. + + TODO: Distinguish the above cases and tailor messages to suit them. + """ if not self._slot_collision_info: return @@ -4388,46 +4408,64 @@ class depgraph(object): indent = " " # Max number of parents shown, to avoid flooding the display. max_parents = 3 - for slot_atom, root in self._slot_collision_info: + for (slot_atom, root), slot_nodes \ + in self._slot_collision_info.iteritems(): msg.append(str(slot_atom)) msg.append("\n\n") - slot_nodes = [] - for node in self._slot_collision_nodes: - if node.slot_atom == slot_atom: - slot_nodes.append(node) - slot_nodes.append(self._slot_pkg_map[root][slot_atom]) + for node in slot_nodes: msg.append(indent) msg.append(str(node)) - parents = self.digraph.parent_nodes(node) - if parents: - omitted_parents = 0 - if len(parents) > max_parents: - pruned_list = [] + parent_atoms = self._parent_atoms.get(node) + if parent_atoms: + pruned_list = set() + # Prefer conflict atoms over others. + for parent_atom in parent_atoms: + if len(pruned_list) >= max_parents: + break + if parent_atom in self._slot_conflict_parent_atoms: + pruned_list.add(parent_atom) + + # If this package was pulled in by conflict atoms then + # show those alone since those are the most interesting. + if not pruned_list: # When generating the pruned list, prefer instances # of DependencyArg over instances of Package. - for parent in parents: + for parent_atom in parent_atoms: + if len(pruned_list) >= max_parents: + break + parent, atom = parent_atom if isinstance(parent, DependencyArg): - pruned_list.append(parent) + pruned_list.add(parent_atom) # Prefer Packages instances that themselves have been # pulled into collision slots. - for parent in parents: + for parent_atom in parent_atoms: + if len(pruned_list) >= max_parents: + break + parent, atom = parent_atom if isinstance(parent, Package) and \ (parent.slot_atom, parent.root) \ in self._slot_collision_info: - pruned_list.append(parent) - for parent in parents: + pruned_list.add(parent_atom) + for parent_atom in parent_atoms: if len(pruned_list) >= max_parents: break - if not isinstance(parent, DependencyArg) and \ - parent not in pruned_list: - pruned_list.append(parent) - omitted_parents = len(parents) - len(pruned_list) - parents = pruned_list + pruned_list.add(parent_atom) + omitted_parents = len(parent_atoms) - len(pruned_list) + parent_atoms = pruned_list msg.append(" pulled in by\n") - for parent in parents: + for parent_atom in parent_atoms: + parent, atom = parent_atom msg.append(2*indent) - msg.append(str(parent)) + if isinstance(parent, + (PackageArg, AtomArg)): + # For PackageArg and AtomArg types, it's + # redundant to display the atom attribute. + msg.append(str(parent)) + else: + # Display the specific atom from SetArg or + # Package types. + msg.append("%s required by %s" % (atom, parent)) msg.append("\n") if omitted_parents: msg.append(2*indent) @@ -4467,6 +4505,40 @@ class depgraph(object): f.end_paragraph(1) f.writer.flush() + def _process_slot_conflicts(self): + """ + 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. + """ + for (slot_atom, root), slot_nodes \ + in self._slot_collision_info.iteritems(): + + all_parent_atoms = set() + for pkg in slot_nodes: + parent_atoms = self._parent_atoms.get(pkg) + if not parent_atoms: + continue + all_parent_atoms.update(parent_atoms) + + for pkg in slot_nodes: + parent_atoms = self._parent_atoms.get(pkg) + if parent_atoms is None: + parent_atoms = set() + self._parent_atoms[pkg] = parent_atoms + for parent_atom in all_parent_atoms: + if parent_atom in parent_atoms: + continue + # Use package set for matching since it will match via + # PROVIDE when necessary, while match_from_list does not. + parent, atom = parent_atom + atom_set = InternalPackageSet( + initial_atoms=(atom,)) + if atom_set.findAtomForPackage(pkg): + parent_atoms.add(parent_atom) + else: + self._slot_conflict_parent_atoms.add(parent_atom) + def _reinstall_for_flags(self, forced_flags, orig_use, orig_iuse, cur_use, cur_iuse): """Return a set of flags that trigger reinstallation, or None if there @@ -4579,12 +4651,14 @@ class depgraph(object): #IUSE-aware emerge -> USE DEP aware depgraph #"no downgrade" emerge """ + # Ensure that the dependencies of the same package + # are never processed more than once. + previously_added = pkg in self.digraph # select the correct /var database that we'll be checking against vardbapi = self.trees[pkg.root]["vartree"].dbapi pkgsettings = self.pkgsettings[pkg.root] - args = None arg_atoms = None if True: try: @@ -4595,8 +4669,6 @@ class depgraph(object): pkg, pkg.metadata["PROVIDE"], str(e)) return 0 del e - else: - args = [arg for arg, atom in arg_atoms] if not pkg.onlydeps: if not pkg.installed and \ @@ -4626,10 +4698,12 @@ class depgraph(object): existing_node_matches = False if existing_node_matches: # The existing node can be reused. - if args: - for arg in args: - self.digraph.add(existing_node, arg, + if arg_atoms: + for parent_atom in arg_atoms: + parent, atom = parent_atom + self.digraph.add(existing_node, parent, priority=priority) + self._add_parent_atom(existing_node, parent_atom) # If a direct circular dependency is not an unsatisfied # buildtime dependency then drop it here since otherwise # it can skew the merge order calculation in an unwanted @@ -4638,32 +4712,16 @@ class depgraph(object): (priority.buildtime and not priority.satisfied): self.digraph.addnode(existing_node, myparent, priority=priority) + if dep.atom is not None and dep.parent is not None: + self._add_parent_atom(existing_node, + (dep.parent, dep.atom)) return 1 else: - if pkg.cpv == existing_node.cpv and \ - dep.atom is not None and \ - dep.atom.use: - # Multiple different instances of the same version - # (typically one installed and another not yet - # installed) have been pulled into the graph due - # to a USE dependency. The "slot collision" display - # is not helpful in a case like this, so display it - # as an unsatisfied dependency. - self._unsatisfied_deps_for_display.append( - ((dep.root, dep.atom), {"myparent":dep.parent})) - self._slot_collision_info.add((pkg.slot_atom, pkg.root)) - self._slot_collision_nodes.add(pkg) - self.digraph.addnode(pkg, myparent, priority=priority) - return 0 - - if pkg in self._slot_collision_nodes: - return 1 # A slot collision has occurred. Sometimes this coincides # with unresolvable blockers, so the slot collision will be # shown later if there are no unresolvable blockers. - self._slot_collision_info.add((pkg.slot_atom, pkg.root)) - self._slot_collision_nodes.add(pkg) + self._add_slot_conflict(pkg) slot_collision = True if slot_collision: @@ -4682,8 +4740,6 @@ class depgraph(object): self._slot_pkg_map[pkg.root][pkg.slot_atom] = pkg self.mydbapi[pkg.root].cpv_inject(pkg) - self.digraph.addnode(pkg, myparent, priority=priority) - if not pkg.installed: # Allow this package to satisfy old-style virtuals in case it # doesn't already. Any pre-existing providers will be preferred @@ -4701,17 +4757,21 @@ class depgraph(object): del e return 0 - if args: + if arg_atoms: self._set_nodes.add(pkg) # Do this even when addme is False (--onlydeps) so that the # parent/child relationship is always known in case # self._show_slot_collision_notice() needs to be called later. - if pkg.onlydeps: - self.digraph.add(pkg, myparent, priority=priority) - if args: - for arg in args: - self.digraph.add(pkg, arg, priority=priority) + self.digraph.add(pkg, myparent, priority=priority) + 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.digraph.add(pkg, parent, priority=priority) + self._add_parent_atom(pkg, parent_atom) """ This section determines whether we go deeper into dependencies or not. We want to go deeper on a few occasions: @@ -4728,12 +4788,30 @@ class depgraph(object): self.spinner.update() - if args: + if arg_atoms: depth = 0 pkg.depth = depth - dep_stack.append(pkg) + if not previously_added: + dep_stack.append(pkg) return 1 + def _add_parent_atom(self, pkg, parent_atom): + parent_atoms = self._parent_atoms.get(pkg) + if parent_atoms is None: + parent_atoms = set() + self._parent_atoms[pkg] = parent_atoms + parent_atoms.add(parent_atom) + + def _add_slot_conflict(self, pkg): + self._slot_collision_nodes.add(pkg) + slot_key = (pkg.slot_atom, pkg.root) + slot_nodes = self._slot_collision_info.get(slot_key) + if slot_nodes is None: + slot_nodes = set() + slot_nodes.add(self._slot_pkg_map[pkg.root][pkg.slot_atom]) + self._slot_collision_info[slot_key] = slot_nodes + slot_nodes.add(pkg) + def _add_pkg_deps(self, pkg, allow_unsatisfied=False): mytype = pkg.type_name @@ -6288,6 +6366,9 @@ class depgraph(object): if not self.validate_blockers(): raise self._unknown_internal_error() + if self._slot_collision_info: + self._process_slot_conflicts() + def _serialize_tasks(self): scheduler_graph = self.digraph.copy() mygraph=self.digraph.copy() -- 2.26.2