# Copyright 2010-2011 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import copy class BacktrackParameter(object): __slots__ = ( "needed_unstable_keywords", "runtime_pkg_mask", "needed_use_config_changes", "needed_license_changes", "rebuild_list", "reinstall_list", "needed_p_mask_changes" ) def __init__(self): self.needed_unstable_keywords = set() self.needed_p_mask_changes = set() self.runtime_pkg_mask = {} self.needed_use_config_changes = {} self.needed_license_changes = {} self.rebuild_list = set() self.reinstall_list = set() def __deepcopy__(self, memo=None): if memo is None: memo = {} result = BacktrackParameter() memo[id(self)] = result #Shallow copies are enough here, as we only need to ensure that nobody adds stuff #to our sets and dicts. The existing content is immutable. result.needed_unstable_keywords = copy.copy(self.needed_unstable_keywords) result.needed_p_mask_changes = copy.copy(self.needed_p_mask_changes) result.runtime_pkg_mask = copy.copy(self.runtime_pkg_mask) result.needed_use_config_changes = copy.copy(self.needed_use_config_changes) result.needed_license_changes = copy.copy(self.needed_license_changes) result.rebuild_list = copy.copy(self.rebuild_list) result.reinstall_list = copy.copy(self.reinstall_list) return result def __eq__(self, other): return self.needed_unstable_keywords == other.needed_unstable_keywords and \ self.needed_p_mask_changes == other.needed_p_mask_changes and \ self.runtime_pkg_mask == other.runtime_pkg_mask and \ self.needed_use_config_changes == other.needed_use_config_changes and \ self.needed_license_changes == other.needed_license_changes and \ self.rebuild_list == other.rebuild_list and \ self.reinstall_list == other.reinstall_list class _BacktrackNode(object): __slots__ = ( "parameter", "depth", "mask_steps", "terminal", ) def __init__(self, parameter=BacktrackParameter(), depth=0, mask_steps=0, terminal=True): self.parameter = parameter self.depth = depth self.mask_steps = mask_steps self.terminal = terminal def __eq__(self, other): return self.parameter == other.parameter class Backtracker(object): __slots__ = ( "_max_depth", "_unexplored_nodes", "_current_node", "_nodes", "_root", ) def __init__(self, max_depth): self._max_depth = max_depth self._unexplored_nodes = [] self._current_node = None self._nodes = [] self._root = _BacktrackNode() self._add(self._root) def _add(self, node, explore=True): """ Adds a newly computed backtrack parameter. Makes sure that it doesn't already exist and that we don't backtrack deeper than we are allowed by --backtrack. """ if not self._check_runtime_pkg_mask(node.parameter.runtime_pkg_mask): return if node.mask_steps <= self._max_depth and node not in self._nodes: if explore: self._unexplored_nodes.append(node) self._nodes.append(node) def get(self): """ Returns a backtrack parameter. The backtrack graph is explored with depth first. """ if self._unexplored_nodes: node = self._unexplored_nodes.pop() self._current_node = node return copy.deepcopy(node.parameter) else: return None def __len__(self): return len(self._unexplored_nodes) def _check_runtime_pkg_mask(self, runtime_pkg_mask): """ If a package gets masked that caused other packages to be masked before, we revert the mask for other packages (bug 375573). """ for pkg in runtime_pkg_mask: if "missing dependency" in runtime_pkg_mask[pkg]: continue entry_is_valid = False for ppkg, patom in runtime_pkg_mask[pkg].get("slot conflict", set()): if ppkg not in runtime_pkg_mask: entry_is_valid = True break if not entry_is_valid: return False return True def _feedback_slot_conflict(self, conflict_data): for pkg, parent_atoms in conflict_data: new_node = copy.deepcopy(self._current_node) new_node.depth += 1 new_node.mask_steps += 1 new_node.terminal = False new_node.parameter.runtime_pkg_mask.setdefault( pkg, {})["slot conflict"] = parent_atoms self._add(new_node) def _feedback_missing_dep(self, dep): new_node = copy.deepcopy(self._current_node) new_node.depth += 1 new_node.mask_steps += 1 new_node.terminal = False new_node.parameter.runtime_pkg_mask.setdefault( dep.parent, {})["missing dependency"] = \ set([(dep.parent, dep.root, dep.atom)]) self._add(new_node) def _feedback_config(self, changes, explore=True): """ Handle config changes. Don't count config changes for the maximum backtrack depth. """ new_node = copy.deepcopy(self._current_node) new_node.depth += 1 para = new_node.parameter for change, data in changes.items(): if change == "needed_unstable_keywords": para.needed_unstable_keywords.update(data) elif change == "needed_p_mask_changes": para.needed_p_mask_changes.update(data) elif change == "needed_license_changes": for pkg, missing_licenses in data: para.needed_license_changes.setdefault(pkg, set()).update(missing_licenses) elif change == "needed_use_config_changes": for pkg, (new_use, new_changes) in data: para.needed_use_config_changes[pkg] = (new_use, new_changes) elif change == "rebuild_list": para.rebuild_list.update(data) elif change == "reinstall_list": para.reinstall_list.update(data) self._add(new_node, explore=explore) self._current_node = new_node def feedback(self, infos): """ Takes information from the depgraph and computes new backtrack parameters to try. """ assert self._current_node is not None, "call feedback() only after get() was called" #Not all config changes require a restart, that's why they can appear together #with other conflicts. if "config" in infos: self._feedback_config(infos["config"], explore=(len(infos)==1)) #There is at most one of the following types of conflicts for a given restart. if "slot conflict" in infos: self._feedback_slot_conflict(infos["slot conflict"]) elif "missing dependency" in infos: self._feedback_missing_dep(infos["missing dependency"]) def backtracked(self): """ If we didn't backtrack, there is only the root. """ return len(self._nodes) > 1 def get_best_run(self): """ Like, get() but returns the backtrack parameter that has as many config changes as possible, but has no masks. This makes --autounmask effective, but prevents confusing error messages with "masked by backtracking". """ best_node = self._root for node in self._nodes: if node.terminal and node.depth > best_node.depth: best_node = node return copy.deepcopy(best_node.parameter)