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