| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import collections |
| import os |
| import cStringIO |
| |
| from py_vulcanize import resource_loader |
| |
| |
| def _FindAllFilesRecursive(source_paths): |
| all_filenames = set() |
| for source_path in source_paths: |
| for dirpath, _, filenames in os.walk(source_path): |
| for f in filenames: |
| if f.startswith('.'): |
| continue |
| x = os.path.abspath(os.path.join(dirpath, f)) |
| all_filenames.add(x) |
| return all_filenames |
| |
| |
| class AbsFilenameList(object): |
| |
| def __init__(self, willDirtyCallback): |
| self._willDirtyCallback = willDirtyCallback |
| self._filenames = [] |
| self._filenames_set = set() |
| |
| def _WillBecomeDirty(self): |
| if self._willDirtyCallback: |
| self._willDirtyCallback() |
| |
| def append(self, filename): |
| assert os.path.isabs(filename) |
| self._WillBecomeDirty() |
| self._filenames.append(filename) |
| self._filenames_set.add(filename) |
| |
| def extend(self, iterable): |
| self._WillBecomeDirty() |
| for filename in iterable: |
| assert os.path.isabs(filename) |
| self._filenames.append(filename) |
| self._filenames_set.add(filename) |
| |
| def appendRel(self, basedir, filename): |
| assert os.path.isabs(basedir) |
| self._WillBecomeDirty() |
| n = os.path.abspath(os.path.join(basedir, filename)) |
| self._filenames.append(n) |
| self._filenames_set.add(n) |
| |
| def extendRel(self, basedir, iterable): |
| self._WillBecomeDirty() |
| assert os.path.isabs(basedir) |
| for filename in iterable: |
| n = os.path.abspath(os.path.join(basedir, filename)) |
| self._filenames.append(n) |
| self._filenames_set.add(n) |
| |
| def __contains__(self, x): |
| return x in self._filenames_set |
| |
| def __len__(self): |
| return self._filenames.__len__() |
| |
| def __iter__(self): |
| return iter(self._filenames) |
| |
| def __repr__(self): |
| return repr(self._filenames) |
| |
| def __str__(self): |
| return str(self._filenames) |
| |
| |
| class Project(object): |
| |
| py_vulcanize_path = os.path.abspath(os.path.join( |
| os.path.dirname(__file__), '..')) |
| |
| def __init__(self, source_paths=None): |
| """ |
| source_paths: A list of top-level directories in which modules and raw |
| scripts can be found. Module paths are relative to these directories. |
| """ |
| self._loader = None |
| self._frozen = False |
| self.source_paths = AbsFilenameList(self._WillPartOfPathChange) |
| |
| if source_paths is not None: |
| self.source_paths.extend(source_paths) |
| |
| def Freeze(self): |
| self._frozen = True |
| |
| def _WillPartOfPathChange(self): |
| if self._frozen: |
| raise Exception('The project is frozen. You cannot edit it now') |
| self._loader = None |
| |
| @staticmethod |
| def FromDict(d): |
| return Project(d['source_paths']) |
| |
| def AsDict(self): |
| return { |
| 'source_paths': list(self.source_paths) |
| } |
| |
| def __repr__(self): |
| return "Project(%s)" % repr(self.source_paths) |
| |
| def AddSourcePath(self, path): |
| self.source_paths.append(path) |
| |
| @property |
| def loader(self): |
| if self._loader is None: |
| self._loader = resource_loader.ResourceLoader(self) |
| return self._loader |
| |
| def ResetLoader(self): |
| self._loader = None |
| |
| def _Load(self, filenames): |
| return [self.loader.LoadModule(module_filename=filename) for |
| filename in filenames] |
| |
| def LoadModule(self, module_name=None, module_filename=None): |
| return self.loader.LoadModule(module_name=module_name, |
| module_filename=module_filename) |
| |
| def CalcLoadSequenceForModuleNames(self, module_names, |
| excluded_scripts=None): |
| modules = [self.loader.LoadModule(module_name=name, |
| excluded_scripts=excluded_scripts) for |
| name in module_names] |
| return self.CalcLoadSequenceForModules(modules) |
| |
| def CalcLoadSequenceForModules(self, modules): |
| already_loaded_set = set() |
| load_sequence = [] |
| for m in modules: |
| m.ComputeLoadSequenceRecursive(load_sequence, already_loaded_set) |
| return load_sequence |
| |
| def GetDepsGraphFromModuleNames(self, module_names): |
| modules = [self.loader.LoadModule(module_name=name) for |
| name in module_names] |
| return self.GetDepsGraphFromModules(modules) |
| |
| def GetDepsGraphFromModules(self, modules): |
| load_sequence = self.CalcLoadSequenceForModules(modules) |
| g = _Graph() |
| for m in load_sequence: |
| g.AddModule(m) |
| |
| for dep in m.dependent_modules: |
| g.AddEdge(m, dep.id) |
| |
| # FIXME: _GetGraph is not defined. Maybe `return g` is intended? |
| return _GetGraph(load_sequence) |
| |
| def GetDominatorGraphForModulesNamed(self, module_names, load_sequence): |
| modules = [self.loader.LoadModule(module_name=name) |
| for name in module_names] |
| return self.GetDominatorGraphForModules(modules, load_sequence) |
| |
| def GetDominatorGraphForModules(self, start_modules, load_sequence): |
| modules_by_id = {} |
| for m in load_sequence: |
| modules_by_id[m.id] = m |
| |
| module_referrers = collections.defaultdict(list) |
| for m in load_sequence: |
| for dep in m.dependent_modules: |
| module_referrers[dep].append(m) |
| |
| # Now start at the top module and reverse. |
| visited = set() |
| g = _Graph() |
| |
| pending = collections.deque() |
| pending.extend(start_modules) |
| while len(pending): |
| cur = pending.pop() |
| |
| g.AddModule(cur) |
| visited.add(cur) |
| |
| for out_dep in module_referrers[cur]: |
| if out_dep in visited: |
| continue |
| g.AddEdge(out_dep, cur) |
| visited.add(out_dep) |
| pending.append(out_dep) |
| |
| # Visited -> Dot |
| return g.GetDot() |
| |
| |
| class _Graph(object): |
| |
| def __init__(self): |
| self.nodes = [] |
| self.edges = [] |
| |
| def AddModule(self, m): |
| f = cStringIO.StringIO() |
| m.AppendJSContentsToFile(f, False, None) |
| |
| attrs = { |
| 'label': '%s (%i)' % (m.name, f.tell()) |
| } |
| |
| f.close() |
| |
| attr_items = ['%s="%s"' % (x, y) for x, y in attrs.iteritems()] |
| node = 'M%i [%s];' % (m.id, ','.join(attr_items)) |
| self.nodes.append(node) |
| |
| def AddEdge(self, mFrom, mTo): |
| edge = 'M%i -> M%i;' % (mFrom.id, mTo.id) |
| self.edges.append(edge) |
| |
| def GetDot(self): |
| return 'digraph deps {\n\n%s\n\n%s\n}\n' % ( |
| '\n'.join(self.nodes), '\n'.join(self.edges)) |