
Change log:95336cb92b..191d55580e
Full diff:95336cb92b..191d55580e
Roll chromium third_party 4e16929f46..3a8f2a9e1e Change log:4e16929f46..3a8f2a9e1e
Changed dependencies: * src/tools:c44a3f5eca..f524a53b81
DEPS diff:95336cb92b..191d55580e
/DEPS No update to Clang. TBR=titovartem@google.com, BUG=None CQ_INCLUDE_TRYBOTS=master.internal.tryserver.corp.webrtc:linux_internal Change-Id: Ic9c4a62b050383646e9fcf5cc07a5653c14ac06e Reviewed-on: https://webrtc-review.googlesource.com/76120 Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Reviewed-by: Artem Titov <titovartem@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/master@{#23205}
312 lines
10 KiB
Python
Executable File
312 lines
10 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Copyright 2015 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.
|
|
|
|
"""Runs Closure compiler on JavaScript files to check for errors and produce
|
|
minified output."""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import processor
|
|
|
|
|
|
_CURRENT_DIR = os.path.join(os.path.dirname(__file__))
|
|
|
|
|
|
class Checker(object):
|
|
"""Runs the Closure compiler on given source files to typecheck them
|
|
and produce minified output."""
|
|
|
|
_JAR_COMMAND = [
|
|
"java",
|
|
"-jar",
|
|
"-Xms1024m",
|
|
"-client",
|
|
"-XX:+TieredCompilation"
|
|
]
|
|
|
|
_POLYMER_EXTERNS = os.path.join(_CURRENT_DIR, "externs", "polymer-1.0.js")
|
|
|
|
def __init__(self, verbose=False):
|
|
"""
|
|
Args:
|
|
verbose: Whether this class should output diagnostic messages.
|
|
"""
|
|
self._compiler_jar = os.path.join(_CURRENT_DIR, "compiler", "compiler.jar")
|
|
self._target = None
|
|
self._temp_files = []
|
|
self._verbose = verbose
|
|
|
|
def _nuke_temp_files(self):
|
|
"""Deletes any temp files this class knows about."""
|
|
if not self._temp_files:
|
|
return
|
|
|
|
self._log_debug("Deleting temp files: %s" % ", ".join(self._temp_files))
|
|
for f in self._temp_files:
|
|
os.remove(f)
|
|
self._temp_files = []
|
|
|
|
def _log_debug(self, msg, error=False):
|
|
"""Logs |msg| to stdout if --verbose/-v is passed when invoking this script.
|
|
|
|
Args:
|
|
msg: A debug message to log.
|
|
"""
|
|
if self._verbose:
|
|
print "(INFO) %s" % msg
|
|
|
|
def _log_error(self, msg):
|
|
"""Logs |msg| to stderr regardless of --flags.
|
|
|
|
Args:
|
|
msg: An error message to log.
|
|
"""
|
|
print >> sys.stderr, "(ERROR) %s" % msg
|
|
|
|
def run_jar(self, jar, args):
|
|
"""Runs a .jar from the command line with arguments.
|
|
|
|
Args:
|
|
jar: A file path to a .jar file
|
|
args: A list of command line arguments to be passed when running the .jar.
|
|
|
|
Return:
|
|
(exit_code, stderr) The exit code of the command (e.g. 0 for success) and
|
|
the stderr collected while running |jar| (as a string).
|
|
"""
|
|
shell_command = " ".join(self._JAR_COMMAND + [jar] + args)
|
|
self._log_debug("Running jar: %s" % shell_command)
|
|
|
|
devnull = open(os.devnull, "w")
|
|
kwargs = {"stdout": devnull, "stderr": subprocess.PIPE, "shell": True}
|
|
process = subprocess.Popen(shell_command, **kwargs)
|
|
_, stderr = process.communicate()
|
|
return process.returncode, stderr
|
|
|
|
def _get_line_number(self, match):
|
|
"""When chrome is built, it preprocesses its JavaScript from:
|
|
|
|
<include src="blah.js">
|
|
alert(1);
|
|
|
|
to:
|
|
|
|
/* contents of blah.js inlined */
|
|
alert(1);
|
|
|
|
Because Closure Compiler requires this inlining already be done (as
|
|
<include> isn't valid JavaScript), this script creates temporary files to
|
|
expand all the <include>s.
|
|
|
|
When type errors are hit in temporary files, a developer doesn't know the
|
|
original source location to fix. This method maps from /tmp/file:300 back to
|
|
/original/source/file:100 so fixing errors is faster for developers.
|
|
|
|
Args:
|
|
match: A re.MatchObject from matching against a line number regex.
|
|
|
|
Returns:
|
|
The fixed up /file and :line number.
|
|
"""
|
|
real_file = self._processor.get_file_from_line(match.group(1))
|
|
return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number)
|
|
|
|
def _clean_up_error(self, error):
|
|
"""Reverse the effects that funky <include> preprocessing steps have on
|
|
errors messages.
|
|
|
|
Args:
|
|
error: A Closure compiler error (2 line string with error and source).
|
|
|
|
Return:
|
|
The fixed up error string.
|
|
"""
|
|
assert self._target
|
|
assert self._expanded_file
|
|
expanded_file = self._expanded_file
|
|
fixed = re.sub("%s:(\d+)" % expanded_file, self._get_line_number, error)
|
|
return fixed.replace(expanded_file, os.path.abspath(self._target))
|
|
|
|
def _format_errors(self, errors):
|
|
"""Formats Closure compiler errors to easily spot compiler output.
|
|
|
|
Args:
|
|
errors: A list of strings extracted from the Closure compiler's output.
|
|
|
|
Returns:
|
|
A formatted output string.
|
|
"""
|
|
contents = "\n## ".join("\n\n".join(errors).splitlines())
|
|
return "## %s" % contents if contents else ""
|
|
|
|
def _create_temp_file(self, contents):
|
|
"""Creates an owned temporary file with |contents|.
|
|
|
|
Args:
|
|
content: A string of the file contens to write to a temporary file.
|
|
|
|
Return:
|
|
The filepath of the newly created, written, and closed temporary file.
|
|
"""
|
|
with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file:
|
|
self._temp_files.append(tmp_file.name)
|
|
tmp_file.write(contents)
|
|
return tmp_file.name
|
|
|
|
def check(self, sources, out_file, closure_args=None,
|
|
custom_sources=False, custom_includes=False):
|
|
"""Closure compile |sources| while checking for errors.
|
|
|
|
Args:
|
|
sources: Files to check. sources[0] is the typically the target file.
|
|
sources[1:] are externs and dependencies in topological order. Order
|
|
is not guaranteed if custom_sources is True.
|
|
out_file: A file where the compiled output is written to.
|
|
closure_args: Arguments passed directly to the Closure compiler.
|
|
custom_sources: Whether |sources| was customized by the target (e.g. not
|
|
in GYP dependency order).
|
|
custom_includes: Whether <include>s are processed when |custom_sources|
|
|
is True.
|
|
|
|
Returns:
|
|
(found_errors, stderr) A boolean indicating whether errors were found and
|
|
the raw Closure compiler stderr (as a string).
|
|
"""
|
|
is_extern = lambda f: 'externs' in f
|
|
externs_and_deps = [self._POLYMER_EXTERNS]
|
|
|
|
if custom_sources:
|
|
if custom_includes:
|
|
# TODO(dbeam): this is fairly hacky. Can we just remove custom_sources
|
|
# soon when all the things kept on life support using it die?
|
|
self._target = sources.pop()
|
|
externs_and_deps += sources
|
|
else:
|
|
self._target = sources[0]
|
|
externs_and_deps += sources[1:]
|
|
|
|
externs = filter(is_extern, externs_and_deps)
|
|
deps = filter(lambda f: not is_extern(f), externs_and_deps)
|
|
|
|
assert externs or deps or self._target
|
|
|
|
self._log_debug("Externs: %s" % externs)
|
|
self._log_debug("Dependencies: %s" % deps)
|
|
self._log_debug("Target: %s" % self._target)
|
|
|
|
js_args = deps + ([self._target] if self._target else [])
|
|
|
|
process_includes = custom_includes or not custom_sources
|
|
if process_includes:
|
|
# TODO(dbeam): compiler.jar automatically detects "@externs" in a --js arg
|
|
# and moves these files to a different AST tree. However, because we use
|
|
# one big funky <include> meta-file, it thinks all the code is one big
|
|
# externs. Just use --js when <include> dies.
|
|
|
|
cwd, tmp_dir = os.getcwd(), tempfile.gettempdir()
|
|
rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f)
|
|
contents = ['<include src="%s">' % rel_path(f) for f in js_args]
|
|
meta_file = self._create_temp_file("\n".join(contents))
|
|
self._log_debug("Meta file: %s" % meta_file)
|
|
|
|
self._processor = processor.Processor(meta_file)
|
|
self._expanded_file = self._create_temp_file(self._processor.contents)
|
|
self._log_debug("Expanded file: %s" % self._expanded_file)
|
|
|
|
js_args = [self._expanded_file]
|
|
|
|
closure_args = closure_args or []
|
|
closure_args += ["summary_detail_level=3", "continue_after_errors"]
|
|
|
|
args = ["--externs=%s" % e for e in externs] + \
|
|
["--js=%s" % s for s in js_args] + \
|
|
["--%s" % arg for arg in closure_args]
|
|
|
|
assert out_file
|
|
|
|
out_dir = os.path.dirname(out_file)
|
|
if not os.path.exists(out_dir):
|
|
os.makedirs(out_dir)
|
|
|
|
checks_only = 'checks_only' in closure_args
|
|
|
|
if not checks_only:
|
|
args += ["--js_output_file=%s" % out_file]
|
|
|
|
self._log_debug("Args: %s" % " ".join(args))
|
|
|
|
return_code, stderr = self.run_jar(self._compiler_jar, args)
|
|
|
|
errors = stderr.strip().split("\n\n")
|
|
maybe_summary = errors.pop()
|
|
|
|
summary = re.search("(?P<error_count>\d+).*error.*warning", maybe_summary)
|
|
if summary:
|
|
self._log_debug("Summary: %s" % maybe_summary)
|
|
else:
|
|
# Not a summary. Running the jar failed. Bail.
|
|
self._log_error(stderr)
|
|
self._nuke_temp_files()
|
|
sys.exit(1)
|
|
|
|
if summary.group('error_count') != "0":
|
|
if os.path.exists(out_file):
|
|
os.remove(out_file)
|
|
elif checks_only and return_code == 0:
|
|
# Compile succeeded but --checks_only disables --js_output_file from
|
|
# actually writing a file. Write a file ourselves so incremental builds
|
|
# still work.
|
|
with open(out_file, 'w') as f:
|
|
f.write('')
|
|
|
|
if process_includes:
|
|
errors = map(self._clean_up_error, errors)
|
|
output = self._format_errors(errors)
|
|
|
|
if errors:
|
|
prefix = "\n" if output else ""
|
|
self._log_error("Error in: %s%s%s" % (self._target, prefix, output))
|
|
elif output:
|
|
self._log_debug("Output: %s" % output)
|
|
|
|
self._nuke_temp_files()
|
|
return bool(errors) or return_code > 0, stderr
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Typecheck JavaScript using Closure compiler")
|
|
parser.add_argument("sources", nargs=argparse.ONE_OR_MORE,
|
|
help="Path to a source file to typecheck")
|
|
parser.add_argument("--custom_sources", action="store_true",
|
|
help="Whether this rules has custom sources.")
|
|
parser.add_argument("--custom_includes", action="store_true",
|
|
help="If present, <include>s are processed when "
|
|
"using --custom_sources.")
|
|
parser.add_argument("-o", "--out_file", required=True,
|
|
help="A file where the compiled output is written to")
|
|
parser.add_argument("-c", "--closure_args", nargs=argparse.ZERO_OR_MORE,
|
|
help="Arguments passed directly to the Closure compiler")
|
|
parser.add_argument("-v", "--verbose", action="store_true",
|
|
help="Show more information as this script runs")
|
|
opts = parser.parse_args()
|
|
|
|
checker = Checker(verbose=opts.verbose)
|
|
|
|
found_errors, stderr = checker.check(opts.sources, out_file=opts.out_file,
|
|
closure_args=opts.closure_args,
|
|
custom_sources=opts.custom_sources,
|
|
custom_includes=opts.custom_includes)
|
|
|
|
if found_errors:
|
|
if opts.custom_sources:
|
|
print stderr
|
|
sys.exit(1)
|