• 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
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