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