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