blob: ded97e19d33744a2ad718a2295f4d59612f2c2f7 [file] [log] [blame]
# Copyright 2010 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",
)
def __init__(self):
self.needed_unstable_keywords = set()
self.runtime_pkg_mask = {}
self.needed_use_config_changes = {}
self.needed_license_changes = {}
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.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)
return result
def __eq__(self, other):
return self.needed_unstable_keywords == other.needed_unstable_keywords 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
class _BacktrackNode:
__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 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 paramater. 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 _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
for other_pkg, other_parent_atoms in conflict_data:
if other_pkg is pkg:
continue
new_node.parameter.runtime_pkg_mask.setdefault(
other_pkg, {})["slot conflict"] = other_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_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)
self._add(new_node, explore=explore)
self._current_node = new_node
def feedback(self, infos):
"""
Takes infomration 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 dind'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 maskes --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)