Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
|
rothwell
2014/12/18 22:30:47
are there unittests for this file?
Dan Beam
2015/02/25 03:36:49
this file mainly just runs a .jar file. the major
| |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Runs Closure compiler on a JavaScript file to check for errors.""" | 6 """Runs Closure compiler on a JavaScript file to check for errors.""" |
| 7 | 7 |
| 8 import argparse | 8 import argparse |
| 9 import os | 9 import os |
| 10 import re | 10 import re |
| 11 import subprocess | 11 import subprocess |
| 12 import sys | 12 import sys |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 52 ] | 52 ] |
| 53 | 53 |
| 54 _JAR_COMMAND = [ | 54 _JAR_COMMAND = [ |
| 55 "java", | 55 "java", |
| 56 "-jar", | 56 "-jar", |
| 57 "-Xms1024m", | 57 "-Xms1024m", |
| 58 "-client", | 58 "-client", |
| 59 "-XX:+TieredCompilation" | 59 "-XX:+TieredCompilation" |
| 60 ] | 60 ] |
| 61 | 61 |
| 62 _found_java = False | 62 _found_java = False |
|
rothwell
2014/12/18 22:30:47
why is this a class variable and not an instance v
rothwell
2014/12/18 22:30:47
add a comment?
Dan Beam
2015/02/25 03:36:49
I've just removed checking for java on the user's
| |
| 63 | 63 |
| 64 def __init__(self, verbose=False): | 64 def __init__(self, verbose=False): |
|
rothwell
2014/12/18 22:30:47
no docstring?
Dan Beam
2015/02/25 03:36:48
Done.
| |
| 65 current_dir = os.path.join(os.path.dirname(__file__)) | 65 current_dir = os.path.join(os.path.dirname(__file__)) |
| 66 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") | 66 self._compiler_jar = os.path.join(current_dir, "lib", "compiler.jar") |
| 67 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") | 67 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") |
| 68 self._temp_files = [] | 68 self._temp_files = [] |
| 69 self._verbose = verbose | 69 self._verbose = verbose |
| 70 self._error_filter = error_filter.PromiseErrorFilter() | 70 self._error_filter = error_filter.PromiseErrorFilter() |
| 71 | 71 |
| 72 def _clean_up(self): | 72 def _clean_up(self): |
| 73 if not self._temp_files: | 73 if not self._temp_files: |
| 74 return | 74 return |
| 75 | 75 |
| 76 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) | 76 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) |
| 77 for f in self._temp_files: | 77 for f in self._temp_files: |
| 78 os.remove(f) | 78 os.remove(f) |
| 79 self._temp_files = [] | 79 self._temp_files = [] |
| 80 | 80 |
| 81 def _debug(self, msg, error=False): | 81 def _debug(self, msg, error=False): |
| 82 if self._verbose: | 82 if self._verbose: |
| 83 print "(INFO) %s" % msg | 83 print "(INFO) %s" % msg |
|
rothwell
2014/12/18 22:30:48
there's no logging module that you could/should be
Dan Beam
2015/02/25 03:36:49
this CL is getting kinda big. can I do this separ
| |
| 84 | 84 |
| 85 def _error(self, msg): | 85 def _error(self, msg): |
|
rothwell
2014/12/18 22:30:47
maybe this should be called _log_error - I would e
Dan Beam
2015/02/25 03:36:49
Done.
| |
| 86 print >> sys.stderr, "(ERROR) %s" % msg | 86 print >> sys.stderr, "(ERROR) %s" % msg |
| 87 self._clean_up() | 87 self._clean_up() |
|
rothwell
2014/12/18 22:30:47
this seems like a confusing side effect of this me
Dan Beam
2015/02/25 03:36:48
This originally had a reason to be here, but now I
| |
| 88 | 88 |
| 89 def _run_command(self, cmd): | 89 def _run_command(self, cmd): |
| 90 """Runs a shell command. | 90 """Runs a shell command. |
| 91 | 91 |
| 92 Args: | 92 Args: |
| 93 cmd: A list of tokens to be joined into a shell command. | 93 cmd: A list of tokens to be joined into a shell command. |
|
rothwell
2014/12/18 22:30:47
minor nit but indention here differs from google3
Dan Beam
2015/02/25 03:36:49
Done.
| |
| 94 | 94 |
| 95 Return: | 95 Return: |
| 96 True if the exit code was 0, else False. | 96 True if the exit code was 0, else False. |
|
rothwell
2014/12/18 22:30:47
this doesn't look correct - it appears to return t
Dan Beam
2015/02/25 03:36:49
Done.
| |
| 97 """ | 97 """ |
| 98 cmd_str = " ".join(cmd) | 98 cmd_str = " ".join(cmd) |
| 99 self._debug("Running command: %s" % cmd_str) | 99 self._debug("Running command: %s" % cmd_str) |
| 100 | 100 |
| 101 devnull = open(os.devnull, "w") | 101 devnull = open(os.devnull, "w") |
| 102 return subprocess.Popen( | 102 return subprocess.Popen( |
| 103 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) | 103 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) |
| 104 | 104 |
| 105 def _check_java_path(self): | 105 def _check_java_path(self): |
|
rothwell
2014/12/18 22:30:47
maybe this should be a classmethod as it modifies
Dan Beam
2015/02/25 03:36:49
Removed.
| |
| 106 """Checks that `java` is on the system path.""" | 106 """Checks that `java` is on the system path.""" |
| 107 if not self._found_java: | 107 if not self._found_java: |
| 108 proc = self._run_command(["which", "java"]) | 108 proc = self._run_command(["which", "java"]) |
| 109 proc.communicate() | 109 proc.communicate() |
| 110 if proc.returncode == 0: | 110 if proc.returncode == 0: |
| 111 self._found_java = True | 111 self._found_java = True |
| 112 else: | 112 else: |
| 113 self._error("Cannot find java (`which java` => %s)" % proc.returncode) | 113 self._error("Cannot find java (`which java` => %s)" % proc.returncode) |
| 114 | 114 |
| 115 return self._found_java | 115 return self._found_java |
| 116 | 116 |
| 117 def _run_jar(self, jar, args=None): | 117 def _run_jar(self, jar, args=None): |
| 118 args = args or [] | 118 args = args or [] |
| 119 self._check_java_path() | 119 self._check_java_path() |
| 120 return self._run_command(self._JAR_COMMAND + [jar] + args) | 120 return self._run_command(self._JAR_COMMAND + [jar] + args) |
| 121 | 121 |
| 122 def _fix_line_number(self, match): | 122 def _fix_line_number(self, match): |
| 123 """Changes a line number from /tmp/file:300 to /orig/file:100. | 123 """Changes a line number from /tmp/file:300 to /orig/file:100. |
|
rothwell
2014/12/18 22:30:47
maybe elaborate on why this conversion is necessar
Dan Beam
2015/02/25 03:36:48
Done.
| |
| 124 | 124 |
| 125 Args: | 125 Args: |
| 126 match: A re.MatchObject from matching against a line number regex. | 126 match: A re.MatchObject from matching against a line number regex. |
| 127 | 127 |
| 128 Returns: | 128 Returns: |
| 129 The fixed up /file and :line number. | 129 The fixed up /file and :line number. |
| 130 """ | 130 """ |
| 131 real_file = self._processor.get_file_from_line(match.group(1)) | 131 real_file = self._processor.get_file_from_line(match.group(1)) |
| 132 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) | 132 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) |
| 133 | 133 |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 163 def check(self, source_file, depends=None, externs=None): | 163 def check(self, source_file, depends=None, externs=None): |
| 164 """Closure compile a file and check for errors. | 164 """Closure compile a file and check for errors. |
| 165 | 165 |
| 166 Args: | 166 Args: |
| 167 source_file: A file to check. | 167 source_file: A file to check. |
| 168 depends: Other files that would be included with a <script> earlier in | 168 depends: Other files that would be included with a <script> earlier in |
| 169 the page. | 169 the page. |
| 170 externs: @extern files that inform the compiler about custom globals. | 170 externs: @extern files that inform the compiler about custom globals. |
| 171 | 171 |
| 172 Returns: | 172 Returns: |
| 173 (exitcode, output) The exit code of the Closure compiler (as a number) | 173 (exitcode, output) The exit code of the Closure compiler (as a number) |
|
rothwell
2014/12/18 22:30:47
is exitcode useful to the caller? I would expect a
Dan Beam
2015/02/25 03:36:49
Done.
| |
| 174 and its output (as a string). | 174 and its output (as a string). |
| 175 """ | 175 """ |
| 176 depends = depends or [] | 176 depends = depends or [] |
| 177 externs = externs or set() | 177 externs = externs or set() |
| 178 | 178 |
| 179 if not self._check_java_path(): | 179 if not self._check_java_path(): |
| 180 return 1, "" | 180 return 1, "" |
| 181 | 181 |
| 182 self._debug("FILE: %s" % source_file) | 182 self._debug("FILE: %s" % source_file) |
| 183 | 183 |
| 184 if source_file.endswith("_externs.js"): | 184 if source_file.endswith("_externs.js"): |
| 185 self._debug("Skipping externs: %s" % source_file) | 185 self._debug("Skipping externs: %s" % source_file) |
| 186 return | 186 return |
|
rothwell
2014/12/18 22:30:47
this return code doesn't match your stated API
Dan Beam
2015/02/25 03:36:48
Done.
| |
| 187 | 187 |
| 188 self._file_arg = source_file | 188 self._file_arg = source_file |
| 189 | 189 |
| 190 tmp_dir = tempfile.gettempdir() | 190 tmp_dir = tempfile.gettempdir() |
| 191 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) | 191 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) |
|
rothwell
2014/12/18 22:30:47
not sure this lambda is buying you much, I would j
Dan Beam
2015/02/25 03:36:49
I find:
includes = [rel_path(f) for f in depend
rothwell
2015/03/20 19:24:31
defer to your preference
| |
| 192 | 192 |
| 193 includes = [rel_path(f) for f in depends + [source_file]] | 193 includes = [rel_path(f) for f in depends + [source_file]] |
| 194 contents = ['<include src="%s">' % i for i in includes] | 194 contents = ['<include src="%s">' % i for i in includes] |
| 195 meta_file = self._create_temp_file("\n".join(contents)) | 195 meta_file = self._create_temp_file("\n".join(contents)) |
| 196 self._debug("Meta file: %s" % meta_file) | 196 self._debug("Meta file: %s" % meta_file) |
| 197 | 197 |
| 198 self._processor = processor.Processor(meta_file) | 198 self._processor = processor.Processor(meta_file) |
| 199 self._expanded_file = self._create_temp_file(self._processor.contents) | 199 self._expanded_file = self._create_temp_file(self._processor.contents) |
| 200 self._debug("Expanded file: %s" % self._expanded_file) | 200 self._debug("Expanded file: %s" % self._expanded_file) |
| 201 | 201 |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 221 | 221 |
| 222 output = self._format_errors(map(self._fix_up_error, errors)) | 222 output = self._format_errors(map(self._fix_up_error, errors)) |
| 223 if errors: | 223 if errors: |
| 224 prefix = "\n" if output else "" | 224 prefix = "\n" if output else "" |
| 225 self._error("Error in: %s%s%s" % (source_file, prefix, output)) | 225 self._error("Error in: %s%s%s" % (source_file, prefix, output)) |
| 226 elif output: | 226 elif output: |
| 227 self._debug("Output: %s" % output) | 227 self._debug("Output: %s" % output) |
| 228 | 228 |
| 229 self._clean_up() | 229 self._clean_up() |
| 230 | 230 |
| 231 return bool(errors), output | 231 return bool(errors), output |
|
rothwell
2014/12/18 22:30:47
your API says you will return an integer, this is
Dan Beam
2015/02/25 03:36:49
Done.
| |
| 232 | 232 |
| 233 | 233 |
| 234 if __name__ == "__main__": | 234 if __name__ == "__main__": |
| 235 parser = argparse.ArgumentParser( | 235 parser = argparse.ArgumentParser( |
| 236 description="Typecheck JavaScript using Closure compiler") | 236 description="Typecheck JavaScript using Closure compiler") |
| 237 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, | 237 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, |
| 238 help="Path to a source file to typecheck") | 238 help="Path to a source file to typecheck") |
| 239 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) | 239 parser.add_argument("-d", "--depends", nargs=argparse.ZERO_OR_MORE) |
| 240 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) | 240 parser.add_argument("-e", "--externs", nargs=argparse.ZERO_OR_MORE) |
| 241 parser.add_argument("-o", "--out_file", help="A place to output results") | 241 parser.add_argument("-o", "--out_file", help="A place to output results") |
| 242 parser.add_argument("-v", "--verbose", action="store_true", | 242 parser.add_argument("-v", "--verbose", action="store_true", |
| 243 help="Show more information as this script runs") | 243 help="Show more information as this script runs") |
| 244 opts = parser.parse_args() | 244 opts = parser.parse_args() |
| 245 | 245 |
| 246 checker = Checker(verbose=opts.verbose) | 246 checker = Checker(verbose=opts.verbose) |
| 247 for source in opts.sources: | 247 for source in opts.sources: |
| 248 depends, externs = build.inputs.resolve_recursive_dependencies( | 248 depends, externs = build.inputs.resolve_recursive_dependencies( |
| 249 source, | 249 source, |
| 250 opts.depends, | 250 opts.depends, |
| 251 opts.externs) | 251 opts.externs) |
| 252 exit, _ = checker.check(source, depends=depends, externs=externs) | 252 exit, _ = checker.check(source, depends=depends, externs=externs) |
|
rothwell
2014/12/18 22:30:48
why return an error string if it's just going to b
Dan Beam
2015/02/25 03:36:49
it is used here:
https://code.google.com/p/chromiu
| |
| 253 if exit != 0: | 253 if exit != 0: |
| 254 sys.exit(exit) | 254 sys.exit(exit) |
| 255 | 255 |
| 256 if opts.out_file: | 256 if opts.out_file: |
| 257 out_dir = os.path.dirname(opts.out_file) | 257 out_dir = os.path.dirname(opts.out_file) |
| 258 if not os.path.exists(out_dir): | 258 if not os.path.exists(out_dir): |
| 259 os.makedirs(out_dir) | 259 os.makedirs(out_dir) |
| 260 # TODO(dbeam): write compiled file to |opts.out_file|. | 260 # TODO(dbeam): write compiled file to |opts.out_file|. |
| 261 open(opts.out_file, "w").write("") | 261 open(opts.out_file, "w").write("") |
|
rothwell
2014/12/18 22:30:47
it seems confusing to write an empty file - should
Dan Beam
2015/02/25 03:36:49
this empty file is used by the build system for in
| |
| OLD | NEW |