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                                      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 
    205 class _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