Home | History | Annotate | Download | only in py_vulcanize
      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 
      5 import collections
      6 import os
      7 import cStringIO
      8 
      9 from py_vulcanize import resource_loader
     10 
     11 
     12 def _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 class 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 
     79 class 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 
    203 class _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