1 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import collections 6 import os 7 import cStringIO 8 9 from py_vulcanize import resource_loader 10 11 12 def _FindAllFilesRecursive(source_paths): 13 all_filenames = set() 14 for source_path in source_paths: 15 for dirpath, _, filenames in os.walk(source_path): 16 for f in filenames: 17 if f.startswith('.'): 18 continue 19 x = os.path.abspath(os.path.join(dirpath, f)) 20 all_filenames.add(x) 21 return all_filenames 22 23 24 class AbsFilenameList(object): 25 26 def __init__(self, willDirtyCallback): 27 self._willDirtyCallback = willDirtyCallback 28 self._filenames = [] 29 self._filenames_set = set() 30 31 def _WillBecomeDirty(self): 32 if self._willDirtyCallback: 33 self._willDirtyCallback() 34 35 def append(self, filename): 36 assert os.path.isabs(filename) 37 self._WillBecomeDirty() 38 self._filenames.append(filename) 39 self._filenames_set.add(filename) 40 41 def extend(self, iterable): 42 self._WillBecomeDirty() 43 for filename in iterable: 44 assert os.path.isabs(filename) 45 self._filenames.append(filename) 46 self._filenames_set.add(filename) 47 48 def appendRel(self, basedir, filename): 49 assert os.path.isabs(basedir) 50 self._WillBecomeDirty() 51 n = os.path.abspath(os.path.join(basedir, filename)) 52 self._filenames.append(n) 53 self._filenames_set.add(n) 54 55 def extendRel(self, basedir, iterable): 56 self._WillBecomeDirty() 57 assert os.path.isabs(basedir) 58 for filename in iterable: 59 n = os.path.abspath(os.path.join(basedir, filename)) 60 self._filenames.append(n) 61 self._filenames_set.add(n) 62 63 def __contains__(self, x): 64 return x in self._filenames_set 65 66 def __len__(self): 67 return self._filenames.__len__() 68 69 def __iter__(self): 70 return iter(self._filenames) 71 72 def __repr__(self): 73 return repr(self._filenames) 74 75 def __str__(self): 76 return str(self._filenames) 77 78 79 class Project(object): 80 81 py_vulcanize_path = os.path.abspath(os.path.join( 82 os.path.dirname(__file__), '..')) 83 84 def __init__(self, source_paths=None): 85 """ 86 source_paths: A list of top-level directories in which modules and raw 87 scripts can be found. Module paths are relative to these directories. 88 """ 89 self._loader = None 90 self._frozen = False 91 self.source_paths = AbsFilenameList(self._WillPartOfPathChange) 92 93 if source_paths is not None: 94 self.source_paths.extend(source_paths) 95 96 def Freeze(self): 97 self._frozen = True 98 99 def _WillPartOfPathChange(self): 100 if self._frozen: 101 raise Exception('The project is frozen. You cannot edit it now') 102 self._loader = None 103 104 @staticmethod 105 def FromDict(d): 106 return Project(d['source_paths']) 107 108 def AsDict(self): 109 return { 110 'source_paths': list(self.source_paths) 111 } 112 113 def __repr__(self): 114 return "Project(%s)" % repr(self.source_paths) 115 116 def AddSourcePath(self, path): 117 self.source_paths.append(path) 118 119 @property 120 def loader(self): 121 if self._loader is None: 122 self._loader = resource_loader.ResourceLoader(self) 123 return self._loader 124 125 def ResetLoader(self): 126 self._loader = None 127 128 def _Load(self, filenames): 129 return [self.loader.LoadModule(module_filename=filename) for 130 filename in filenames] 131 132 def LoadModule(self, module_name=None, module_filename=None): 133 return self.loader.LoadModule(module_name=module_name, 134 module_filename=module_filename) 135 136 def CalcLoadSequenceForModuleNames(self, module_names): 137 modules = [self.loader.LoadModule(module_name=name) for 138 name in module_names] 139 return self.CalcLoadSequenceForModules(modules) 140 141 def CalcLoadSequenceForModules(self, modules): 142 already_loaded_set = set() 143 load_sequence = [] 144 for m in modules: 145 m.ComputeLoadSequenceRecursive(load_sequence, already_loaded_set) 146 return load_sequence 147 148 def GetDepsGraphFromModuleNames(self, module_names): 149 modules = [self.loader.LoadModule(module_name=name) for 150 name in module_names] 151 return self.GetDepsGraphFromModules(modules) 152 153 def GetDepsGraphFromModules(self, modules): 154 load_sequence = self.CalcLoadSequenceForModules(modules) 155 g = _Graph() 156 for m in load_sequence: 157 g.AddModule(m) 158 159 for dep in m.dependent_modules: 160 g.AddEdge(m, dep.id) 161 162 # FIXME: _GetGraph is not defined. Maybe `return g` is intended? 163 return _GetGraph(load_sequence) 164 165 def GetDominatorGraphForModulesNamed(self, module_names, load_sequence): 166 modules = [self.loader.LoadModule(module_name=name) 167 for name in module_names] 168 return self.GetDominatorGraphForModules(modules, load_sequence) 169 170 def GetDominatorGraphForModules(self, start_modules, load_sequence): 171 modules_by_id = {} 172 for m in load_sequence: 173 modules_by_id[m.id] = m 174 175 module_referrers = collections.defaultdict(list) 176 for m in load_sequence: 177 for dep in m.dependent_modules: 178 module_referrers[dep].append(m) 179 180 # Now start at the top module and reverse. 181 visited = set() 182 g = _Graph() 183 184 pending = collections.deque() 185 pending.extend(start_modules) 186 while len(pending): 187 cur = pending.pop() 188 189 g.AddModule(cur) 190 visited.add(cur) 191 192 for out_dep in module_referrers[cur]: 193 if out_dep in visited: 194 continue 195 g.AddEdge(out_dep, cur) 196 visited.add(out_dep) 197 pending.append(out_dep) 198 199 # Visited -> Dot 200 return g.GetDot() 201 202 203 class _Graph(object): 204 205 def __init__(self): 206 self.nodes = [] 207 self.edges = [] 208 209 def AddModule(self, m): 210 f = cStringIO.StringIO() 211 m.AppendJSContentsToFile(f, False, None) 212 213 attrs = { 214 'label': '%s (%i)' % (m.name, f.tell()) 215 } 216 217 f.close() 218 219 attr_items = ['%s="%s"' % (x, y) for x, y in attrs.iteritems()] 220 node = 'M%i [%s];' % (m.id, ','.join(attr_items)) 221 self.nodes.append(node) 222 223 def AddEdge(self, mFrom, mTo): 224 edge = 'M%i -> M%i;' % (mFrom.id, mTo.id) 225 self.edges.append(edge) 226 227 def GetDot(self): 228 return 'digraph deps {\n\n%s\n\n%s\n}\n' % ( 229 '\n'.join(self.nodes), '\n'.join(self.edges)) 230