Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 288 lines (222 sloc) 8.59 KB
#!/usr/bin/env python
import subprocess
import argparse
import sys
from os import path
import inspect
import yaml
from termcolor import colored
import glob
BREW_PREFIX = subprocess.check_output(['brew', '--prefix']).strip()
compiler_search_path = path.join(BREW_PREFIX, 'Cellar/closure-compiler', '*', 'libexec/build/compiler.jar')
compilers = glob.glob(compiler_search_path)
if len(compilers) == 1:
COMPILER_PATH = compilers[0]
else:
raise Exception("Couldn't find one compiler in '%s'" % compiler_search_path)
def get_closurebuilder_path(closure_path):
return path.join(closure_path, 'closure', 'bin', 'build', 'closurebuilder.py')
def get_depswriter_path(closure_path):
return path.join(closure_path, 'closure', 'bin', 'build', 'depswriter.py')
def get_goog_basejs_path(closure_path):
return path.join(closure_path, 'closure', 'goog', 'base.js')
def get_closure_roots(closure_path):
roots = [path.join('third_party', 'closure', 'goog'), path.join('closure', 'goog')]
return [path.join(closure_path, p) for p in roots]
def get_compiler_flags(closure_path, debug=False, closure_entry_point=None, language_in=None, extra_annotations=[]):
compilerflags = []
compilerflags += ["--compilation_level=ADVANCED_OPTIMIZATIONS"]
compilerflags += ["--summary_detail_level=3"]
compilerflags += ["--warning_level=VERBOSE"]
compilerflags += ['--generate_exports']
for error in ['ambiguousFunctionDecl',
'checkProvides',
'checkRegExp',
'checkTypes',
'checkVars',
'const',
'constantProperty',
'duplicateMessage',
'es5Strict',
'invalidCasts',
'misplacedTypeAnnotation',
'missingProperties',
'undefinedVars',
'uselessCode',
'visibility']:
compilerflags += ['--jscomp_error=%s' % error]
for warning in [
'accessControls',
'ambiguousFunctionDecl',
'deprecated',
'externsValidation',
'fileoverviewTags',
'globalThis',
'nonStandardJsDocs',
'suspiciousCode',
'strictModuleDepCheck',
'typeInvalidation',
'undefinedNames',
'unknownDefines'
]:
compilerflags += ['--jscomp_warning=%s' % warning]
if debug:
compilerflags += ['--debug', '--formatting=PRETTY_PRINT', '--formatting=PRINT_INPUT_DELIMITER']
if closure_entry_point:
compilerflags += ['--closure_entry_point=%s' % closure_entry_point]
if language_in:
compilerflags += ['--language_in=%s' % language_in]
if any(extra_annotations):
for annotation in extra_annotations:
compilerflags += ['--extra_annotation_name=%s' % annotation]
# to eliminate errors about unfound closure dependencies
# http://groups.google.com/group/closure-compiler-discuss/msg/7d089cc101a2cf00?pli=1
# http://code.google.com/p/closure-library/issues/detail?id=142
deps_path = path.join(closure_path, 'closure', 'goog', 'deps.js')
if path.isfile(deps_path):
compilerflags += ['--js=%s' % deps_path]
return compilerflags
def _get_deps_pair(base_file_path, root_path):
base_dir_path = path.dirname(base_file_path)
relative_path = path.relpath(root_path, base_dir_path)
return '{0} {1}'.format(root_path, relative_path)
def make_deps(closure_path, js_paths, deps_path):
command = ['python', get_depswriter_path(closure_path)]
for p in js_paths:
if path.isdir(p):
command += ['--root_with_prefix=%s' % _get_deps_pair(get_goog_basejs_path(closure_path), p)]
elif path.isfile(p):
command += [p]
command += ["--output_file", deps_path]
return command
def fix(fix_paths, fix_exclude_paths=[]):
# TODO: actually check to see if fixjsstyle exists?
command = ['fixjsstyle', '--strict']
fix_files = []
fix_dirs = []
for p in fix_paths:
if path.isfile(p):
fix_files += [p]
elif path.isdir(p):
fix_dirs += ['-r', p]
exclude_dirs = []
exclude_files = []
for p in fix_exclude_paths:
if path.isfile(p):
exclude_files += [p]
elif path.isdir(p):
exclude_dirs += [p]
if any(exclude_files):
command += ['-x', ','.join(exclude_files)]
if any(exclude_dirs):
command += ['-e', ','.join(exclude_dirs)]
command += fix_dirs
command += fix_files
return command
def lint(fix_paths, fix_exclude_paths=[]):
# TODO: actually check to see if gjslint exists?
command = ['gjslint']
for p in fix_paths:
if path.isfile(p):
command += [p]
elif path.isdir(p):
command += ['-r', p]
exclude_dirs = []
exclude_files = []
for p in fix_exclude_paths:
if path.isfile(p):
exclude_files += [p]
elif path.isdir(p):
exclude_dirs += [p]
if any(exclude_files):
command += ['-x', ','.join(exclude_files)]
if any(exclude_dirs):
command += ['-e', ','.join(exclude_dirs)]
return command
def compile(closure_path, js_paths, inputs, compile_path, externs=[], debug=False, closure_entry_point=None, language_in=None, extra_annotations=[]):
command = ['python', get_closurebuilder_path(closure_path)]
command += ['-o', 'compiled']
command += ['--compiler_jar', COMPILER_PATH]
for flag in get_compiler_flags(closure_path, debug, closure_entry_point, language_in, extra_annotations):
command += ['-f', flag]
for extern in externs:
command += ['-f', "--externs=%s" % extern]
for p in get_closure_roots(closure_path):
command += ['--root', p]
for p in js_paths:
command += ['--root', p]
for p in inputs:
command += ['-i', p]
command += ["--output_file", compile_path]
return command
def invoke_command(function, args, show_command=False):
print colored('kbuild: ' + function.func_name, 'green')
args = args.copy()
argspec = inspect.getargspec(function)
defined_args = argspec.args
# remove args that are not needed by the desired command
for key in args.keys():
if key not in defined_args:
del args[key]
# arguments requiring values are in the range
# 0 to (n-k)
# where n is the length of the args
# and k is the number of args with default values
if argspec.defaults:
required_args = defined_args[0:-len(argspec.defaults)]
else:
required_args = defined_args
# make sure required args are present
# or exit!
for key in required_args:
if key not in args:
raise Exception("'{0}' requires '{1}', which is missing from the config file".format(function.func_name, key))
# Call function with the right args and only the right args
command = function(**args)
if show_command:
print colored(' '.join(command), 'yellow')
subprocess.Popen(command, stdout=sys.stdout).communicate()
def main():
parser = argparse.ArgumentParser(
description='Prepare a closure-based javascript set.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('target',
default='deps',
nargs='?',
choices=['deps', 'compile', 'build', 'fix', 'lint', 'fb'],
help='Specify to either create a [deps] file, generate [compile]d output, run [fix]jsstyle, run gjs[lint], [build] deps then compile, or [fb] to fix then build.')
parser.add_argument('--buildfile',
default='config.kb',
help='The yaml config file to use.')
parser.add_argument('--debug', '-d',
action='store_true',
help='Enable debug output when in compile mode.')
parser.add_argument('--language_in',
default='ECMASCRIPT5_STRICT',
choices=['ECMASCRIPT3', 'ECMASCRIPT5', 'ECMASCRIPT5_STRICT'],
help='Sets what language spec that input sources conform.')
parser.add_argument('--show_command', '-sc',
action='store_true',
dest='show_command',
help='Show the command line that is run for the given options.')
args = parser.parse_args()
with open(args.buildfile) as bf:
config_values = yaml.load(bf)
config_values['debug'] = args.debug
config_values['language_in'] = args.language_in
funcs = {
'deps': [make_deps],
'compile': [compile],
'build': [make_deps, compile],
'fb': [fix, make_deps, compile],
'fix': [fix],
'lint': [lint]
}[args.target]
for func in funcs:
invoke_command(func, config_values, args.show_command)
if __name__ == '__main__':
try:
main()
except Exception as err:
print colored(err, 'red')
sys.exit(1)