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