Commit e473d2ae by Sébastien Eustace

Fix conflict resolution in mixology

parent f75b1cb7
...@@ -84,7 +84,9 @@ class DependencyGraph: ...@@ -84,7 +84,9 @@ class DependencyGraph:
dot_edges = [] dot_edges = []
for n, v in self.vertices.items(): for n, v in self.vertices.items():
dot_vertices.append(' {} [label="{}|{}"]'.format(n, n, v.payload)) dot_vertices.append(
' {} [label="{}|{}"]'.format(n, n, v.payload or '')
)
for e in v.outgoing_edges: for e in v.outgoing_edges:
label = e.requirement label = e.requirement
dot_edges.append( dot_edges.append(
......
...@@ -37,7 +37,7 @@ class AddEdgeNoCircular(Action): ...@@ -37,7 +37,7 @@ class AddEdgeNoCircular(Action):
def down(self, graph): def down(self, graph):
edge = self.make_edge(graph) edge = self.make_edge(graph)
self._delete_first(edge.origin.outgoing_edges, edge) self._delete_first(edge.origin.outgoing_edges, edge)
self._delete_first(edge.origin.incoming_edges, edge) self._delete_first(edge.destination.incoming_edges, edge)
def make_edge(self, graph): def make_edge(self, graph):
return Edge( return Edge(
...@@ -55,5 +55,4 @@ class AddEdgeNoCircular(Action): ...@@ -55,5 +55,4 @@ class AddEdgeNoCircular(Action):
except ValueError: except ValueError:
return return
if index != -1: del elements[index]
elements.pop(index)
...@@ -55,5 +55,4 @@ class DeleteEdge(Action): ...@@ -55,5 +55,4 @@ class DeleteEdge(Action):
except ValueError: except ValueError:
return return
if index != -1: del elements[index]
elements.pop(index)
...@@ -21,6 +21,8 @@ class DetachVertexNamed(Action): ...@@ -21,6 +21,8 @@ class DetachVertexNamed(Action):
if self._name not in graph.vertices: if self._name not in graph.vertices:
return [] return []
print('Detaching', graph.vertices[self._name])
self._vertex = graph.vertices[self._name] self._vertex = graph.vertices[self._name]
del graph.vertices[self._name] del graph.vertices[self._name]
removed_vertices = [self._vertex] removed_vertices = [self._vertex]
......
...@@ -20,6 +20,9 @@ class Edge: ...@@ -20,6 +20,9 @@ class Edge:
def requirement(self): def requirement(self):
return self._requirement return self._requirement
def __eq__(self, other):
return self._origin == other.origin and self._destination == other.destination
def __repr__(self): def __repr__(self):
return '<Edge {} -> {}>'.format( return '<Edge {} -> {}>'.format(
self._origin.name, self._destination.name self._origin.name, self._destination.name
......
from ..utils import unique
class Vertex: class Vertex:
def __init__(self, name, payload): def __init__(self, name, payload):
...@@ -14,9 +16,9 @@ class Vertex: ...@@ -14,9 +16,9 @@ class Vertex:
@property @property
def requirements(self): def requirements(self):
return [ return unique([
edge.requirement for edge in self.incoming_edges edge.requirement for edge in self.incoming_edges
] + self._explicit_requirements ] + self._explicit_requirements)
@property @property
def predecessors(self): def predecessors(self):
...@@ -92,4 +94,4 @@ class Vertex: ...@@ -92,4 +94,4 @@ class Vertex:
return other.path_to(self) return other.path_to(self)
def __repr__(self): def __repr__(self):
return '<Vertex {}>'.format(self.name) return '<Vertex {} ({})>'.format(self.name, self.payload)
...@@ -10,7 +10,7 @@ class PossibilitySet: ...@@ -10,7 +10,7 @@ class PossibilitySet:
return self.possibilities[-1] return self.possibilities[-1]
def __str__(self): def __str__(self):
return '[{}]'.format(', '.join([repr(p) for p in self.possibilities])) return '[{}]'.format(', '.join([str(p) for p in self.possibilities]))
def __repr__(self): def __repr__(self):
return f'<PossibilitySet {str(self)}>' return f'<PossibilitySet {str(self)}>'
...@@ -99,15 +99,15 @@ class Resolution: ...@@ -99,15 +99,15 @@ class Resolution:
""" """
self._started_at = datetime.now() self._started_at = datetime.now()
self._handle_missing_or_push_dependency_state(self._initial_state())
self._debug( self._debug(
f'Starting resolution ({self._started_at})\n' f'Starting resolution ({self._started_at})\n'
f'Requested dependencies: {self._original_requested}' f'Requested dependencies: '
f'{[str(d) for d in self._original_requested]}'
) )
self._ui.before_resolution() self._ui.before_resolution()
self._handle_missing_or_push_dependency_state(self._initial_state())
def _resolve_activated_specs(self) -> DependencyGraph: def _resolve_activated_specs(self) -> DependencyGraph:
for vertex in self.activated.vertices.values(): for vertex in self.activated.vertices.values():
if not vertex.payload: if not vertex.payload:
...@@ -235,16 +235,14 @@ class Resolution: ...@@ -235,16 +235,14 @@ class Resolution:
conflicts = self.state.conflicts conflicts = self.state.conflicts
sliced_states = self._states[details_for_unwind.state_index + 1:] sliced_states = self._states[details_for_unwind.state_index + 1:]
if details_for_unwind.state_index == -1: self._states = self._states[:details_for_unwind.state_index + 1]
self._states = []
else:
self._states = self._states[:details_for_unwind.state_index]
self._raise_error_unless_state(conflicts) self._raise_error_unless_state(conflicts)
if sliced_states: if sliced_states:
self.activated.rewind_to( self.activated.rewind_to(
sliced_states[0] or 'initial_state' sliced_states[0] or 'initial_state'
) )
self.state.conflicts = conflicts self.state.conflicts = conflicts
self.state.unused_unwind_options = unwind_options self.state.unused_unwind_options = unwind_options
self._filter_possibilities_after_unwind(details_for_unwind) self._filter_possibilities_after_unwind(details_for_unwind)
...@@ -367,10 +365,11 @@ class Resolution: ...@@ -367,10 +365,11 @@ class Resolution:
parent_r = self._parent_of(r) parent_r = self._parent_of(r)
if parent_r is None: if parent_r is None:
continue continue
partial_tree.insert(0, parent_r) partial_tree.insert(0, parent_r)
requirement_state = self._find_state_for(parent_r) requirement_state = self._find_state_for(parent_r)
possibilities = [ possibilities = [
r in set_.dependencies r.name in map(lambda x: x.name, set_.dependencies)
for set_ in requirement_state.possibilities for set_ in requirement_state.possibilities
] ]
if any(possibilities): if any(possibilities):
...@@ -393,7 +392,7 @@ class Resolution: ...@@ -393,7 +392,7 @@ class Resolution:
partial_tree.insert(0, grandparent_r) partial_tree.insert(0, grandparent_r)
requirement_state = self._find_state_for(grandparent_r) requirement_state = self._find_state_for(grandparent_r)
possibilities = [ possibilities = [
parent_r in set_.dependencies parent_r.name in map(lambda x: x.name, set_.dependencies)
for set_ in requirement_state.possibilities for set_ in requirement_state.possibilities
] ]
if any(possibilities): if any(possibilities):
...@@ -514,7 +513,9 @@ class Resolution: ...@@ -514,7 +513,9 @@ class Resolution:
unwinds_to_state.append(unwind_details) unwinds_to_state.append(unwind_details)
primary_unwinds = unique([ primary_unwinds = unique([
uw for uw in unwinds_to_state if uw.unwinding_to_primary_requirement uw
for uw in unwinds_to_state
if uw.unwinding_to_primary_requirement()
]) ])
parent_unwinds = unique(unwinds_to_state) parent_unwinds = unique(unwinds_to_state)
parent_unwinds = [uw for uw in parent_unwinds if uw not in primary_unwinds] parent_unwinds = [uw for uw in parent_unwinds if uw not in primary_unwinds]
...@@ -530,10 +531,10 @@ class Resolution: ...@@ -530,10 +531,10 @@ class Resolution:
]): ]):
allowed_possibility_sets.append(possibility_set) allowed_possibility_sets.append(possibility_set)
requirements_to_avoid = flat_map( requirements_to_avoid = list(flat_map(
parent_unwinds, parent_unwinds,
lambda x: x.sub_dependencies_to_avoid lambda x: x.sub_dependencies_to_avoid
) ))
possibilities = [] possibilities = []
for possibility_set in self.state.possibilities: for possibility_set in self.state.possibilities:
...@@ -637,12 +638,14 @@ class Resolution: ...@@ -637,12 +638,14 @@ class Resolution:
if not self._parents_of[requirement]: if not self._parents_of[requirement]:
return return
index = self._parents_of[requirement][-1] try:
if not index: index = self._parents_of[requirement][-1]
except ValueError:
return return
parent_state = self._states[index] try:
if not parent_state: parent_state = self._states[index]
except ValueError:
return return
return parent_state.requirement return parent_state.requirement
...@@ -706,7 +709,7 @@ class Resolution: ...@@ -706,7 +709,7 @@ class Resolution:
@property @property
def requirement_trees(self): def requirement_trees(self):
vertex = self.activated.vertex_named(self.name) vertex = self.activated.vertex_named(self.state.name)
return [self._requirement_tree_for(r) for r in vertex.requirements] return [self._requirement_tree_for(r) for r in vertex.requirements]
def _requirement_tree_for(self, requirement): def _requirement_tree_for(self, requirement):
......
...@@ -42,7 +42,8 @@ class ResolutionState: ...@@ -42,7 +42,8 @@ class ResolutionState:
return cls(None, [], DependencyGraph(), None, None, 0, {}, []) return cls(None, [], DependencyGraph(), None, None, 0, {}, [])
def __repr__(self): def __repr__(self):
return f'<{self.__class__.__name__} {self._name}>' return f'<{self.__class__.__name__} {self._name} ' \
f'({str(self.requirement)})>'
class PossibilityState(ResolutionState): class PossibilityState(ResolutionState):
......
{
"name": "resolves a single dependency",
"index": "django",
"requested": {
"django": "~1.4.0",
"django-debug-toolbar": ""
},
"base": [],
"resolved": [
{
"name": "django",
"version": "1.4.3",
"dependencies": []
}, {
"name": "django-debug-toolbar",
"version": "1.3.2",
"dependencies": [
{
"name": "django",
"version": "1.4.1",
"dependencies": [
]
}
]
}
],
"conflicts": []
}
{
"django": [
{
"name": "django",
"version": "1.4.1",
"dependencies": {
}
},
{
"name": "django",
"version": "1.4.3",
"dependencies": {
}
}, {
"name": "django",
"version": "2.0.1",
"dependencies": {
}
}
],
"django-debug-toolbar": [
{
"name": "django-debug-toolbar",
"version": "1.3.2",
"dependencies": {
"django": ">=1.4.2"
}
},
{
"name": "django-debug-toolbar",
"version": "1.6.1",
"dependencies": {
"django": ">=1.8"
}
}, {
"name": "django-debug-toolbar",
"version": "1.7.2",
"dependencies": {
"django": ">=1.9"
}
}
]
}
...@@ -116,6 +116,8 @@ def assert_graph(dg, result): ...@@ -116,6 +116,8 @@ def assert_graph(dg, result):
packages = sorted(dg.vertices.values(), key=lambda x: x.name) packages = sorted(dg.vertices.values(), key=lambda x: x.name)
expected_packages = sorted(result.vertices.values(), key=lambda x: x.name) expected_packages = sorted(result.vertices.values(), key=lambda x: x.name)
print(packages)
print(expected_packages)
assert packages == expected_packages assert packages == expected_packages
...@@ -127,11 +129,12 @@ def assert_graph(dg, result): ...@@ -127,11 +129,12 @@ def assert_graph(dg, result):
'simple_with_base', 'simple_with_base',
'simple_with_dependencies', 'simple_with_dependencies',
'simple_with_shared_dependencies', 'simple_with_shared_dependencies',
'django',
] ]
) )
def test_resolver(fixture): def test_resolver(fixture):
c = case(fixture) c = case(fixture)
resolver = Resolver(c.index, UI()) resolver = Resolver(c.index, UI(True))
dg = resolver.resolve(c.requested, base=c.base) dg = resolver.resolve(c.requested, base=c.base)
assert_graph(dg, c.result) assert_graph(dg, c.result)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment