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