Simplifying the toposort method used for sorting dependencies. The new version imposes an arbitrary sort order for circular dependencies (instead of bombing out). The merge script now uses this new toposort. Tools only commit.

git-svn-id: http://svn.openlayers.org/trunk/openlayers@11006 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2011-01-05 17:39:44 +00:00
parent 52a105c3aa
commit 0bc34e48c6
2 changed files with 52 additions and 285 deletions

View File

@@ -44,6 +44,10 @@ import sys
SUFFIX_JAVASCRIPT = ".js"
RE_REQUIRE = "@requires:? (.*)\n" # TODO: Ensure in comment?
class MissingImport(Exception):
"""Exception raised when a listed import is not found in the lib."""
class SourceFile:
"""
Represents a Javascript source code file.
@@ -161,8 +165,6 @@ def run (sourceDirectory, outputFilename = None, configFile = None):
files = {}
order = [] # List of filepaths to output, in a dependency satisfying order
## Import file source code
## TODO: Do import when we walk the directories above?
for filepath in allFiles:
@@ -179,41 +181,31 @@ def run (sourceDirectory, outputFilename = None, configFile = None):
resolution_pass = 1
while not complete:
order = [] # List of filepaths to output, in a dependency satisfying order
nodes = []
routes = []
complete = True
## Resolve the dependencies
print "Resolution pass %s... " % resolution_pass
resolution_pass += 1
for filepath, info in files.items():
nodes.append(filepath)
for neededFilePath in info.requires:
routes.append((neededFilePath, filepath))
for dependencyLevel in toposort(nodes, routes):
for filepath in dependencyLevel:
order.append(filepath)
if not files.has_key(filepath):
print "Importing: %s" % filepath
fullpath = os.path.join(sourceDirectory, filepath).strip()
content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
files[filepath] = SourceFile(filepath, content) # TODO: Chop path?
# Double check all dependencies have been met
complete = True
try:
for fp in order:
if max([order.index(rfp) for rfp in files[fp].requires] +
[order.index(fp)]) != order.index(fp):
for path in info.requires:
if not files.has_key(path):
complete = False
except:
complete = False
fullpath = os.path.join(sourceDirectory, path).strip()
if os.path.exists(fullpath):
print "Importing: %s" % path
content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
files[path] = SourceFile(path, content) # TODO: Chop path?
else:
raise MissingImport("File '%s' not found (required by '%s')." % (path, filepath))
print
# create dictionary of dependencies
dependencies = {}
for filepath, info in files.items():
dependencies[filepath] = info.requires
print "Sorting..."
order = toposort(dependencies) #[x for x in toposort(dependencies)]
## Move forced first and last files to the required position
if cfg:

View File

@@ -1,260 +1,35 @@
#
# According to <http://www.vrplumber.com/programming/> this file
# is licensed under a BSD-style license. We only use the section
# originally by Tim Peters.
#
# TODO: The use of this code needs to be okayed by someone.
#
"""
toposort.py
Sorts dictionary keys based on lists of dependencies.
"""
class RecursionError( OverflowError, ValueError ):
'''Unable to calculate result because of recursive structure'''
class MissingDependency(Exception):
"""Exception raised when a listed dependency is not in the dictionary."""
class Sorter(object):
def __init__(self, dependencies):
self.dependencies = dependencies
self.visited = set()
self.sorted = ()
def sort(nodes, routes, noRecursion=1):
'''Passed a list of node IDs and a list of source,dest ID routes
attempt to create a list of stages where each sub list
is one stage in a process.
'''
children, parents = _buildChildrenLists(routes)
# first stage is those nodes
# having no incoming routes...
stage = []
stages = [stage]
taken = []
for node in nodes:
if (not parents.get(node)):
stage.append (node)
if nodes and not stage:
# there is no element which does not depend on
# some other element!!!
stage.append( nodes[0])
taken.extend( stage )
nodes = filter ( lambda x, l=stage: x not in l, nodes )
while nodes:
previousStageChildren = []
nodelen = len(nodes)
# second stage are those nodes
# which are direct children of the first stage
for node in stage:
for child in children.get (node, []):
if child not in previousStageChildren and child not in taken:
previousStageChildren.append(child)
elif child in taken and noRecursion:
raise RecursionError( (child, node) )
# unless they are children of other direct children...
# TODO, actually do that...
stage = previousStageChildren
removes = []
for current in stage:
currentParents = parents.get( current, [] )
for parent in currentParents:
if parent in stage and parent != current:
# might wind up removing current...
if not current in parents.get(parent, []):
# is not mutually dependent...
removes.append( current )
for remove in removes:
while remove in stage:
stage.remove( remove )
stages.append( stage)
taken.extend( stage )
nodes = filter ( lambda x, l=stage: x not in l, nodes )
if nodelen == len(nodes):
if noRecursion:
raise RecursionError( nodes )
else:
stages.append( nodes[:] )
nodes = []
return stages
def _buildChildrenLists (routes):
childrenTable = {}
parentTable = {}
for sourceID,destinationID in routes:
currentChildren = childrenTable.get( sourceID, [])
currentParents = parentTable.get( destinationID, [])
if not destinationID in currentChildren:
currentChildren.append ( destinationID)
if not sourceID in currentParents:
currentParents.append ( sourceID)
childrenTable[sourceID] = currentChildren
parentTable[destinationID] = currentParents
return childrenTable, parentTable
def toposort (nodes, routes, noRecursion=1):
'''Topological sort from Tim Peters, fairly efficient
in comparison (it seems).'''
#first calculate the recursion depth
def sort(self):
for key in self.dependencies:
self._visit(key)
return self.sorted
dependencies = {}
inversedependencies = {}
if not nodes:
return []
if not routes:
return [nodes]
for node in nodes:
dependencies[ node ] = (0, node)
inversedependencies[ node ] = []
for depended, depends in routes:
# is it a null rule
try:
newdependencylevel, object = dependencies.get ( depends, (0, depends))
except TypeError:
print depends
raise
dependencies[ depends ] = (newdependencylevel + 1, depends)
# "dependency (existence) of depended-on"
newdependencylevel,object = dependencies.get ( depended, (0, depended) )
dependencies[ depended ] = (newdependencylevel, depended)
# Inverse dependency set up
dependencieslist = inversedependencies.get ( depended, [])
dependencieslist.append (depends)
inversedependencies[depended] = dependencieslist
### Now we do the actual sorting
# The first task is to create the sortable
# list of dependency-levels
sortinglist = dependencies.values()
sortinglist.sort ()
output = []
while sortinglist:
deletelist = []
generation = []
output.append( generation)
while sortinglist and sortinglist[0][0] == 0:
number, object = sortinglist[0]
generation.append ( object )
deletelist.append( object )
for inverse in inversedependencies.get(object, () ):
try:
oldcount, inverse = dependencies [ inverse]
if oldcount > 0:
# will be dealt with on later pass
dependencies [ inverse] = (oldcount-1, inverse)
else:
# will be dealt with on this pass,
# so needs not to be in the sorting list next time
deletelist.append( inverse )
# just in case a loop comes through
inversedependencies[object] = []
except KeyError:
# dealing with a recursion-breaking run...
pass
del sortinglist [0]
# if no elements could be deleted, then
# there is something which depends upon itself
if not deletelist:
if noRecursion:
raise RecursionError( sortinglist )
else:
# hack so that something gets deleted...
## import pdb
## pdb.set_trace()
dependencies[sortinglist[0][1]] = (0,sortinglist[0][1])
# delete the items that were dealt with
for item in deletelist:
try:
del dependencies [ item ]
except KeyError:
pass
# need to recreate the sortinglist
sortinglist = dependencies.values()
if not generation:
output.remove( generation )
sortinglist.sort ()
return output
def _visit(self, key):
if key not in self.visited:
self.visited.add(key)
if not self.dependencies.has_key(key):
raise MissingDependency(key)
for depends in self.dependencies[key]:
self._visit(depends)
self.sorted += (key,)
if __name__ == "__main__":
nodes = ['a', 'b', 'c', 'd', 'e', 'f']
route = [('a', 'b'), ('b', 'c'), ('b', 'd'), ('e','f')]
for x in toposort( nodes, route):
for a in x:
print a
raise SystemExit
import pprint, traceback
nodes= [ 0,1,2,3,4,5 ]
testingValues = [
[ (0,1),(1,2),(2,3),(3,4),(4,5)],
[ (0,1),(0,2),(1,2),(3,4),(4,5)],
[
(0,1),
(0,2),
(0,2),
(2,4),
(2,5),
(3,2),
(0,3)],
[
(0,1), # 3-element cycle test, no orphan nodes
(1,2),
(2,0),
(2,4),
(2,5),
(3,2),
(0,3)],
[
(0,1),
(1,1),
(1,1),
(1,4),
(1,5),
(1,2),
(3,1),
(2,1),
(2,0)],
[
(0,1),
(1,0),
(0,2),
(0,3),
],
[
(0,1),
(1,0),
(0,2),
(3,1),
],
]
print 'sort, no recursion allowed'
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print ' ', sort( nodes, testingValues[index] )
except:
print 'exception raised'
print 'toposort, no recursion allowed'
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print ' ', toposort( nodes, testingValues[index] )
except:
print 'exception raised'
print 'sort, recursion allowed'
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print ' ', sort( nodes, testingValues[index],0 )
except:
print 'exception raised'
print 'toposort, recursion allowed'
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print ' ', toposort( nodes, testingValues[index],0 )
except:
print 'exception raised'
def toposort(dependencies):
"""Returns a tuple of the dependencies dictionary keys sorted by entries
in the dependency lists. Given circular dependencies, sort will impose
an order. Raises MissingDependency if a key is not found.
"""
s = Sorter(dependencies)
return s.sort()