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