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