mirror of
https://github.com/kevingruesser/bootstrap-vz.git
synced 2025-08-22 09:50:37 +00:00
Cycle detection and topological sorting
This commit is contained in:
parent
1e4af40b3c
commit
0f29b3d0e2
3 changed files with 87 additions and 18 deletions
12
base/task.py
12
base/task.py
|
@ -1,7 +1,6 @@
|
|||
from functools import total_ordering
|
||||
from common.exceptions import TaskListError
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Task(object):
|
||||
description = None
|
||||
|
||||
|
@ -42,15 +41,10 @@ class Task(object):
|
|||
msg = ("The task {self} is specified as running before {other}, "
|
||||
"but its phase {phase} lies after the phase {other_phase}"
|
||||
.format(self, other, self.phase, other.phase))
|
||||
raise TaskOrderError(msg)
|
||||
raise TaskListError(msg)
|
||||
for task in self.after:
|
||||
if self.phase < task.phase:
|
||||
msg = ("The task {self} is specified as running after {other}, "
|
||||
"but its phase {phase} lies before the phase {other_phase}"
|
||||
.format(self=self, other=other, phase=self.phase, other_phase=other.phase))
|
||||
raise TaskOrderError(msg)
|
||||
conflict = next(iter(set(self.before) & set(self.after)), None)
|
||||
if conflict is not None:
|
||||
msg = ("The task {self} is specified as running both before and after {conflict}"
|
||||
.format(self=self, conflict=conflict))
|
||||
raise TaskOrderError(msg)
|
||||
raise TaskListError(msg)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from common.exceptions import TaskListError
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -17,14 +18,89 @@ class TaskList(object):
|
|||
self.remove(task)
|
||||
self.add(replacement)
|
||||
|
||||
def get(self, task):
|
||||
def get(self, ref):
|
||||
return next(task for task in self.tasks if type(task) is ref)
|
||||
|
||||
def run(self, bootstrap_info):
|
||||
log.debug('Tasklist before:\n{list}'.format(list=',\n'.join(str(task) for task in self.tasks) ))
|
||||
task_list = sorted(self.tasks)
|
||||
log.debug('Tasklist:\n{list}'.format(list=',\n'.join(str(task) for task in task_list) ))
|
||||
return
|
||||
task_list = self.create_list()
|
||||
for task in tasks:
|
||||
log.info(task)
|
||||
task.run(bootstrap_info)
|
||||
|
||||
def create_list(self):
|
||||
graph = {}
|
||||
for task in self.tasks:
|
||||
graph[task] = [self.get(succ) for succ in task.before]
|
||||
graph[task].extend([succ for succ in self.tasks if type(task) in succ.after])
|
||||
|
||||
components = self.strongly_connected_components(graph)
|
||||
cycles_found = 0
|
||||
for component in components:
|
||||
if len(component) > 1:
|
||||
cycles_found += 1
|
||||
log.debug('Cycle: {list}\n'.format(list=', '.join(str(task) for task in component)))
|
||||
if cycles_found > 0:
|
||||
msg = ('{0} cycles were found in the tasklist, '
|
||||
'consult the logfile for more information.'.format(cycles_found))
|
||||
raise TaskListError(msg)
|
||||
|
||||
sorted_tasks = self.topological_sort(graph)
|
||||
log.debug('Tasklist:\n\t{list}\n'.format(list='\n\t'.join(str(task) for task in sorted_tasks)))
|
||||
|
||||
|
||||
def strongly_connected_components(self, graph):
|
||||
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
|
||||
# Find the strongly connected components in a graph using Tarjan's algorithm.
|
||||
# graph should be a dictionary mapping node names to lists of successor nodes.
|
||||
|
||||
result = [ ]
|
||||
stack = [ ]
|
||||
low = { }
|
||||
|
||||
def visit(node):
|
||||
if node in low: return
|
||||
|
||||
num = len(low)
|
||||
low[node] = num
|
||||
stack_pos = len(stack)
|
||||
stack.append(node)
|
||||
|
||||
for successor in graph[node]:
|
||||
# print successor
|
||||
visit(successor)
|
||||
low[node] = min(low[node], low[successor])
|
||||
|
||||
if num == low[node]:
|
||||
component = tuple(stack[stack_pos:])
|
||||
del stack[stack_pos:]
|
||||
result.append(component)
|
||||
for item in component:
|
||||
low[item] = len(graph)
|
||||
|
||||
for node in graph:
|
||||
visit(node)
|
||||
|
||||
return result
|
||||
|
||||
def topological_sort(self, graph):
|
||||
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
|
||||
count = { }
|
||||
for node in graph:
|
||||
count[node] = 0
|
||||
for node in graph:
|
||||
for successor in graph[node]:
|
||||
count[successor] += 1
|
||||
|
||||
ready = [ node for node in graph if count[node] == 0 ]
|
||||
|
||||
result = [ ]
|
||||
while ready:
|
||||
node = ready.pop(-1)
|
||||
result.append(node)
|
||||
|
||||
for successor in graph[node]:
|
||||
count[successor] -= 1
|
||||
if count[successor] == 0:
|
||||
ready.append(successor)
|
||||
|
||||
return result
|
||||
|
|
|
@ -9,9 +9,8 @@ class ManifestError(Exception):
|
|||
return "Error in manifest {0}: {1}".format(self.manifest.path, self.message)
|
||||
|
||||
|
||||
class TaskOrderError(Exception):
|
||||
def __init__(self, message, task):
|
||||
class TaskListError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
self.task = task
|
||||
def __str__(self):
|
||||
return "Error in task order: {1}".format(self.message)
|
||||
return "Error in tasklist: {0}".format(self.message)
|
||||
|
|
Loading…
Add table
Reference in a new issue