BlockIt
|
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