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 5import collections 6import os 7import cStringIO 8 9from py_vulcanize import resource_loader 10 11 12def _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 24class 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 79class 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 excluded_scripts=None): 138 modules = [self.loader.LoadModule(module_name=name, 139 excluded_scripts=excluded_scripts) for 140 name in module_names] 141 return self.CalcLoadSequenceForModules(modules) 142 143 def CalcLoadSequenceForModules(self, modules): 144 already_loaded_set = set() 145 load_sequence = [] 146 for m in modules: 147 m.ComputeLoadSequenceRecursive(load_sequence, already_loaded_set) 148 return load_sequence 149 150 def GetDepsGraphFromModuleNames(self, module_names): 151 modules = [self.loader.LoadModule(module_name=name) for 152 name in module_names] 153 return self.GetDepsGraphFromModules(modules) 154 155 def GetDepsGraphFromModules(self, modules): 156 load_sequence = self.CalcLoadSequenceForModules(modules) 157 g = _Graph() 158 for m in load_sequence: 159 g.AddModule(m) 160 161 for dep in m.dependent_modules: 162 g.AddEdge(m, dep.id) 163 164 # FIXME: _GetGraph is not defined. Maybe `return g` is intended? 165 return _GetGraph(load_sequence) 166 167 def GetDominatorGraphForModulesNamed(self, module_names, load_sequence): 168 modules = [self.loader.LoadModule(module_name=name) 169 for name in module_names] 170 return self.GetDominatorGraphForModules(modules, load_sequence) 171 172 def GetDominatorGraphForModules(self, start_modules, load_sequence): 173 modules_by_id = {} 174 for m in load_sequence: 175 modules_by_id[m.id] = m 176 177 module_referrers = collections.defaultdict(list) 178 for m in load_sequence: 179 for dep in m.dependent_modules: 180 module_referrers[dep].append(m) 181 182 # Now start at the top module and reverse. 183 visited = set() 184 g = _Graph() 185 186 pending = collections.deque() 187 pending.extend(start_modules) 188 while len(pending): 189 cur = pending.pop() 190 191 g.AddModule(cur) 192 visited.add(cur) 193 194 for out_dep in module_referrers[cur]: 195 if out_dep in visited: 196 continue 197 g.AddEdge(out_dep, cur) 198 visited.add(out_dep) 199 pending.append(out_dep) 200 201 # Visited -> Dot 202 return g.GetDot() 203 204 205class _Graph(object): 206 207 def __init__(self): 208 self.nodes = [] 209 self.edges = [] 210 211 def AddModule(self, m): 212 f = cStringIO.StringIO() 213 m.AppendJSContentsToFile(f, False, None) 214 215 attrs = { 216 'label': '%s (%i)' % (m.name, f.tell()) 217 } 218 219 f.close() 220 221 attr_items = ['%s="%s"' % (x, y) for x, y in attrs.iteritems()] 222 node = 'M%i [%s];' % (m.id, ','.join(attr_items)) 223 self.nodes.append(node) 224 225 def AddEdge(self, mFrom, mTo): 226 edge = 'M%i -> M%i;' % (mFrom.id, mTo.id) 227 self.edges.append(edge) 228 229 def GetDot(self): 230 return 'digraph deps {\n\n%s\n\n%s\n}\n' % ( 231 '\n'.join(self.nodes), '\n'.join(self.edges)) 232