mirror of
https://github.com/kevingruesser/bootstrap-vz.git
synced 2025-08-24 07:26:29 +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):
|
class Task(object):
|
||||||
description = None
|
description = None
|
||||||
|
|
||||||
|
@ -42,15 +41,10 @@ class Task(object):
|
||||||
msg = ("The task {self} is specified as running before {other}, "
|
msg = ("The task {self} is specified as running before {other}, "
|
||||||
"but its phase {phase} lies after the phase {other_phase}"
|
"but its phase {phase} lies after the phase {other_phase}"
|
||||||
.format(self, other, self.phase, other.phase))
|
.format(self, other, self.phase, other.phase))
|
||||||
raise TaskOrderError(msg)
|
raise TaskListError(msg)
|
||||||
for task in self.after:
|
for task in self.after:
|
||||||
if self.phase < task.phase:
|
if self.phase < task.phase:
|
||||||
msg = ("The task {self} is specified as running after {other}, "
|
msg = ("The task {self} is specified as running after {other}, "
|
||||||
"but its phase {phase} lies before the phase {other_phase}"
|
"but its phase {phase} lies before the phase {other_phase}"
|
||||||
.format(self=self, other=other, phase=self.phase, other_phase=other.phase))
|
.format(self=self, other=other, phase=self.phase, other_phase=other.phase))
|
||||||
raise TaskOrderError(msg)
|
raise TaskListError(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)
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from common.exceptions import TaskListError
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -17,14 +18,89 @@ class TaskList(object):
|
||||||
self.remove(task)
|
self.remove(task)
|
||||||
self.add(replacement)
|
self.add(replacement)
|
||||||
|
|
||||||
def get(self, task):
|
def get(self, ref):
|
||||||
return next(task for task in self.tasks if type(task) is ref)
|
return next(task for task in self.tasks if type(task) is ref)
|
||||||
|
|
||||||
def run(self, bootstrap_info):
|
def run(self, bootstrap_info):
|
||||||
log.debug('Tasklist before:\n{list}'.format(list=',\n'.join(str(task) for task in self.tasks) ))
|
task_list = self.create_list()
|
||||||
task_list = sorted(self.tasks)
|
|
||||||
log.debug('Tasklist:\n{list}'.format(list=',\n'.join(str(task) for task in task_list) ))
|
|
||||||
return
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
log.info(task)
|
log.info(task)
|
||||||
task.run(bootstrap_info)
|
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)
|
return "Error in manifest {0}: {1}".format(self.manifest.path, self.message)
|
||||||
|
|
||||||
|
|
||||||
class TaskOrderError(Exception):
|
class TaskListError(Exception):
|
||||||
def __init__(self, message, task):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.task = task
|
|
||||||
def __str__(self):
|
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