compile_modules.py revision 5f1c94371a64b3196d4be9466099bb892df9b88e
15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#!/usr/bin/env python
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# found in the LICENSE file.
5effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import argparse
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from checker import Checker as Checker
8effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport os
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import sys
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)try:
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  import json
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)except:
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  import simplejson as json
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class Module(object):
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self, name, sources, depends=[], externs=[]):
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.name = name
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.sources = sources
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # TODO(dbeam): support depending on other modules/dependency flattening.
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.depends = depends
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.externs = externs
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  @staticmethod
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def from_dict(d):
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    keys = d.keys()
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    required = ["name", "sources"]
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    assert all(r in keys for r in required), "Module missing name or sources"
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    allowed = required + ["depends", "externs"]
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    assert all(k in allowed for k in keys), "Module has unknown key"
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    depends = d["depends"] if "depends" in d else []
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    externs = d["externs"] if "externs" in d else []
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return Module(d["name"], d["sources"], depends=depends, externs=externs)
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# TODO(dbeam): should ModuleParser be internal to ModuleCompiler or should we
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# pass Modules into ModuleCompiler.compile()? Maybe this is fine?
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class ModuleParser(object):
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _cache = {}
44
45  def __init__(self, verbose=False):
46    self._verbose = verbose
47
48  def parse(self, file_path):
49    if file_path in self._cache:
50      print "(INFO) Found module file %s in the cache" % file_path
51      return self._cache[file_path]
52
53    file = open(file_path, "r")
54    data = json.load(file)
55    file.close()
56
57    if self._verbose:
58      pretty_json = json.dumps(data, indent=2, separators=(',', ': ')).strip()
59      print "(INFO) Layout: " + os.linesep + pretty_json + os.linesep
60
61    self._cache[file_path] = [Module.from_dict(m) for m in data]
62    return self._cache[file_path]
63
64
65class ModuleCompiler(object):
66  _checker = None
67  _parser = None
68
69  def __init__(self, verbose=False):
70    self._verbose = verbose
71
72  def _debug(self, msg, prefix="(INFO) ", suffix=""):
73    if self._verbose:
74      print prefix + msg.strip() + suffix
75
76  def compile(self, module_file):
77    self._debug("MODULE FILE: " + module_file, prefix="")
78
79    # NOTE: It's possible but unlikely that |_checker| or |_parser|'s verbosity
80    # isn't the same as |self._verbose| due to this class being called with
81    # verbose=False then verbose=True in the same program.
82    self._parser = self._parser or ModuleParser(verbose=self._verbose)
83    self._checker = self._checker or Checker(verbose=self._verbose)
84
85    current_dir = os.getcwd()
86    module_dir = os.path.dirname(module_file)
87    rel_path = lambda f: f
88
89    if current_dir and module_dir:
90      here_to_module_dir = os.path.relpath(module_dir, current_dir)
91      if here_to_module_dir:
92        rel_path = lambda f: os.path.join(here_to_module_dir, f)
93
94    modules = self._parser.parse(module_file)
95
96    for m in modules:
97      self._debug("MODULE: " + m.name, prefix="", suffix=os.linesep)
98
99      for s in m.sources:
100        depends = [rel_path(d) for d in m.depends]
101        externs = [rel_path(e) for e in m.externs]
102        exit_code, _ = self._checker.check(rel_path(s), depends=depends,
103                                           externs=externs)
104        if exit_code:
105          sys.exit(exit_code)
106
107        if s != m.sources[-1]:
108          self._debug(os.linesep, prefix="")
109
110      if m != modules[-1]:
111        self._debug(os.linesep, prefix="")
112
113
114def main(opts):
115  module_compiler = ModuleCompiler(verbose=opts.verbose)
116  for module_file in opts.module_file:
117    module_compiler.compile(module_file)
118
119
120if __name__ == "__main__":
121  parser = argparse.ArgumentParser(
122      description="Typecheck JavaScript using Closure compiler")
123  parser.add_argument("-v", "--verbose", action="store_true",
124                      help="Show more information as this script runs")
125  parser.add_argument("module_file", nargs=argparse.ONE_OR_MORE,
126                      help="Path to a modules file to check")
127  main(parser.parse_args())
128