BlockIt
funcs.py
Go to the documentation of this file.
00001 #................................................................................
00002 # Copyright (c) 2009,2010,2011 David Car, david.car7@gmail.com
00003 # Copyright (c) 2009,2010,2011 Michael List, michael.list@gmail.com
00004 #
00005 # This program is free software; you can redistribute it and/or modify it under
00006 # the terms of the GNU General Public License as published by the Free Software
00007 # Foundation; either version 2 of the License, or (at your option) any later
00008 # version.
00009 #
00010 # This program is distributed in the hope that it will be useful, but WITHOUT
00011 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00012 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
00013 #
00014 # You should have received a copy of the GNU General Public License along with
00015 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
00016 # Place, Suite 330, Boston, MA 02111-1307 USA
00017 #..........................................................................
00018 __all__=['pack',
00019          'tab',
00020          'indent', 'indentNoStrip',
00021          'And', 'Or', 'Not', 'isLeaf', 'startsWith', 'endsWith',
00022          'genBlockGraph',
00023          'depGraph',
00024          'topoSort']
00025 
00026 from sets import ASet
00027 import copy
00028 import os
00029 
00030 #................................................................................
00031 # Global functions and defs
00032 #................................................................................
00033 #.................... Lambda Function defintions ................................
00034 # Remove all white space from a string
00035 pack = lambda x: x.strip().replace(' ','')
00036 
00037 # Determine indentation of a line
00038 tab = lambda x: len(x)-len(x.lstrip(' '))
00039 
00040 #................................................................................
00041 def indent(nspaces, s):
00042     '''Function padding a string with leading spaces.  Existing leading spaces
00043     of string are removed prior to padding.
00044 
00045     '''
00046     return ' '*nspaces+s.lstrip(' ')
00047 
00048 def indentNoStrip(nspaces, s):
00049     '''Function padding a string with leading spaces.
00050 
00051     '''
00052     return ' '*nspaces+s
00053 
00054 #................................................................................
00055 # Filter function (return True/False given a pair) Combinators
00056 #................................................................................
00057 
00058 # Returns filter function of logical AND of two filter functions
00059 def And(f1, f2):
00060     return lambda (x,v): f1((x,v)) and f2((x,v))
00061 
00062 # Returns filter function of logical OR of two filter functions
00063 def Or(f1, f2):
00064     return lambda (x,v): f1((x,v)) or f2((x,v))
00065 
00066 # Returns filter function of logical NOT of filter function
00067 def Not(f1):
00068     return lambda (x,v): (not f1((x,v)))
00069 
00070 #................................................................................
00071 # Filter functions
00072 #................................................................................
00073 isLeaf = lambda (x,v): v.children().isEmpty()
00074 
00075 def startsWith(s):
00076     return lambda (x,v): x.startswith(s)
00077 
00078 def endsWith(s):
00079     return lambda (x,v): x.endswith(s)
00080 
00081 #................................................................................
00082 # Generate a graphviz graph of a block tree
00083 #................................................................................
00084 def genBlockGraph(blk):
00085     '''Generate a graphviz graph of the given block and return it.
00086 
00087     blk : a Block instance
00088 
00089     '''
00090 
00091     try:
00092         import pygraphviz as gv
00093     except:
00094         print "You need the pygraphviz module for this function"
00095         return
00096 
00097     def _makeName(blk):
00098         return ' '.join([blk._blockType,blk.name()])
00099 
00100     def _traverse(gph, blk):
00101         _name = _makeName(blk)
00102         gph.add_node(_name)
00103         for c in blk.children().values():
00104             _cname = _makeName(c)
00105             gph.add_edge(_name, _cname)
00106             _traverse(gph, c)
00107         
00108         return gph
00109 
00110     gph = gv.AGraph(directed=False)
00111     gph.graph_attr['orientation']='portrait'
00112     gph.node_attr['shape'] = 'box'
00113     gph.node_attr['margin'] = '0.,0.'
00114     gph.node_attr['fontsize'] = '8'
00115 
00116     return _traverse(gph, blk)
00117     
00118 #................................................................................
00119 # Helper functions (not sure where these should go for the moment)
00120 #................................................................................
00121 def depGraph(blocks, table, silent=True):
00122     '''Generate dependency graph which is simply a dictionary where each entry is
00123     the node name and contains a list of sets.  The first set is the nodes
00124     connected to incoming edges, the second set is the nodes connected to
00125     outgoing edges.  The graph is generated for the dependencies of the types
00126     in the block list.  For example, if the list of blocks are of type AFile,
00127     then the dependencies found are between AFiles; if modules, then modules,
00128     etc...  Use the output of this function with topoSort() to get an
00129     non-unique ordering of dependencies.
00130 
00131     Input
00132     -----
00133     blocks : list of blocks to find the build order for
00134     table : global symbol table to use
00135 
00136     '''
00137 
00138     if len(blocks) == 0: return
00139     theType = type(blocks[0])
00140     diGraph = {}.fromkeys([blk.name() for blk in blocks])
00141     for blk in blocks:
00142         diGraph[blk.name()] = (ASet(),ASet())
00143 
00144     jc = table.joinCharacter()
00145     for blk in blocks:
00146         depList = copy.copy(blk.dependencies())
00147         visited = copy.copy(depList)
00148         while len(depList) > 0:
00149             dep = depList.pop()
00150             _dep = blk.resolveSymbol(dep)
00151             root = table.filter(startsWith(_dep+jc)).values()
00152                 
00153             if len(root) > 1:
00154                 msg = "Ambiguous dependency for "
00155                 msg += "build order of %s%s"%(blk.name(), os.linesep)
00156                 for x in root:
00157                     childName = str( x.name() )
00158                     parentName = str( x.parent().name() )
00159                     msg += "Found (%s) in (%s)%s"%( childName, parentName, 
00160                                                   os.linesep )
00161                     
00162                 raise Exception(msg)
00163             elif len(root) == 0:
00164                 if not silent:
00165                     print "Could not find symbol %s in table"%str(_dep,)
00166                 continue
00167 
00168             # Add root's dependencies to depList and continue
00169             root = root[0].upTo(theType)
00170 
00171             if root is blk:
00172                 if blk.contains(_dep) : continue
00173                 msg = "Cyclic dependency detected:"+os.linesep
00174                 msg += "%s <-> %s%s"%(blk.name(), _dep, os.linesep)
00175                 msg += "%s%s"%(str(blk.dependencies()), os.linesep)
00176                 print "%sWARNING!!!!! %s%s"%(5*os.linesep, msg, 5*os.linesep)
00177                 raise Exception('Cyclic dependency!!!')
00178                 
00179             # Dependency is not among group
00180             if root is None or root not in blocks: continue  
00181 
00182             # Add root's name to the blkSet and it's dependencies to the depList
00183             diGraph[blk.name()][0].add(root.name()) # incoming for blk
00184             diGraph[root.name()][1].add(blk.name()) # outgoing for root
00185             depList.update([x for x in root.dependencies()
00186                             if x not in visited])
00187             visited.update(root.dependencies())
00188 
00189             # Remove dep again just in case it also existed in root's
00190             # dependency list
00191             depList.discard(dep); depList.discard(_dep) 
00192         
00193     return diGraph
00194 
00195 #................................................................................
00196 def topoSort(diGraph, prune=True):
00197     '''Topological sort of a given diGraph.  The diGraph is represented as a
00198     dictionary whose keys are the node names and each key entry contains a
00199     2-tuple of lists/sets.  The first is the keys of nodes connected on
00200     incoming edges, the second is the keys of nodes connected on outgoing
00201     edges.
00202     
00203     Example:
00204     -------
00205     {'node1':(['a','b','c'],['r','s','t']}
00206 
00207     means 'node1' has nodes ['a','b','c','d'] connected on 'incoming' edges,
00208     i.e. 'node1' depends on these nodes.  Likewise, nodes ['r','s','t'] are
00209     dependent on 'node1' because they are connected on 'outgoing' edges.
00210     
00211     Input
00212     -----
00213     diGraph : the diGraph
00214     prune : boolean flag to prune isolated nodes from the list
00215 
00216     '''
00217     # Determine an order (not unique). Sort based on the digraph.  Filter out
00218     # isolated nodes on the graph if prune=True, i.e. nodes that do not have
00219     # any incoming or outgoing edges.
00220     noDeps = []
00221     orderList = []
00222 
00223     # Incoming edge counter
00224     diGraph_ = copy.deepcopy(diGraph)
00225     incoming = {}.fromkeys(diGraph.keys(),0)
00226     for name,pair in diGraph.items():
00227         incoming[name] = len(pair[0])
00228 
00229     for name,pair in diGraph.items():
00230         if len(pair[0]) == 0:
00231             if not prune:
00232                 noDeps.append(name)
00233             elif len(pair[1]) != 0:
00234                 noDeps.append(name)
00235 
00236     while len(noDeps) > 0:
00237         node = noDeps.pop()
00238         orderList.append(node)
00239         for other in diGraph[node][1]:
00240             # Decrement the incoming edge counter
00241             diGraph_[other][0].discard(node)
00242             incoming[other] -= 1
00243             if incoming[other] == 0:
00244                 noDeps.append(other)
00245 
00246     #................................................................................
00247     # Sanity Check:  If the incoming edge entries do not sum to zero,
00248     # then we have some sort of cyclic dependency going on.  Display the nodes
00249     # that have this condition and throw a big fat WARNING!
00250     #................................................................................
00251     if sum(incoming.values()) != 0:
00252         print '*'*80
00253         print '*'+'WARNING!!!'.center(78)+'*'
00254         print '*'*80
00255         print 'You appear to have a cyclic dependency in your build'
00256         print 'Please look at the relationships between the following files:'
00257         for f,n in incoming.items():
00258             if n != 0:
00259                 print ' '.join([f, 'has remaining connections:'])
00260                 dep = diGraph_[f][0]
00261                 for other in dep:
00262                     print '\t %s'%(other,)
00263         print '*'*80
00264     #................................................................................
00265                 
00266     return orderList
00267 
 All Classes Namespaces Files Functions Variables Properties