OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
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 |
13 import tempfile | 13 import tempfile |
14 | 14 |
15 import build.inputs | 15 import build.inputs |
16 import processor | 16 import processor |
17 import error_filter | 17 import error_filter |
18 | 18 |
19 | 19 |
20 class Checker(object): | 20 class Checker(object): |
21 """Runs the Closure compiler on a given source file and returns the | 21 """Runs the Closure compiler on a given source file to typecheck it.""" |
22 success/errors.""" | |
23 | 22 |
24 _COMMON_CLOSURE_ARGS = [ | 23 _COMMON_CLOSURE_ARGS = [ |
25 "--accept_const_keyword", | 24 "--accept_const_keyword", |
26 "--jscomp_error=accessControls", | 25 "--jscomp_error=accessControls", |
27 "--jscomp_error=ambiguousFunctionDecl", | 26 "--jscomp_error=ambiguousFunctionDecl", |
28 "--jscomp_error=checkStructDictInheritance", | 27 "--jscomp_error=checkStructDictInheritance", |
29 "--jscomp_error=checkTypes", | 28 "--jscomp_error=checkTypes", |
30 "--jscomp_error=checkVars", | 29 "--jscomp_error=checkVars", |
31 "--jscomp_error=constantProperty", | 30 "--jscomp_error=constantProperty", |
32 "--jscomp_error=deprecated", | 31 "--jscomp_error=deprecated", |
33 "--jscomp_error=externsValidation", | 32 "--jscomp_error=externsValidation", |
34 "--jscomp_error=globalThis", | 33 "--jscomp_error=globalThis", |
35 "--jscomp_error=invalidCasts", | 34 "--jscomp_error=invalidCasts", |
36 "--jscomp_error=missingProperties", | 35 "--jscomp_error=missingProperties", |
37 "--jscomp_error=missingReturn", | 36 "--jscomp_error=missingReturn", |
38 "--jscomp_error=nonStandardJsDocs", | 37 "--jscomp_error=nonStandardJsDocs", |
39 "--jscomp_error=suspiciousCode", | 38 "--jscomp_error=suspiciousCode", |
40 "--jscomp_error=undefinedNames", | 39 "--jscomp_error=undefinedNames", |
41 "--jscomp_error=undefinedVars", | 40 "--jscomp_error=undefinedVars", |
42 "--jscomp_error=unknownDefines", | 41 "--jscomp_error=unknownDefines", |
43 "--jscomp_error=uselessCode", | 42 "--jscomp_error=uselessCode", |
44 "--jscomp_error=visibility", | 43 "--jscomp_error=visibility", |
45 "--language_in=ECMASCRIPT5_STRICT", | 44 "--language_in=ECMASCRIPT5_STRICT", |
46 "--summary_detail_level=3", | 45 "--summary_detail_level=3", |
47 "--compilation_level=SIMPLE_OPTIMIZATIONS", | 46 "--compilation_level=SIMPLE_OPTIMIZATIONS", |
48 "--source_map_format=V3", | 47 "--source_map_format=V3", |
49 ] | 48 ] |
50 | 49 |
51 # These are the extra flags used when compiling in 'strict' mode. | 50 # These are the extra flags used when compiling in strict mode. |
52 # Flags that are normally disabled are turned on for strict mode. | 51 # Flags that are normally disabled are turned on for strict mode. |
53 _STRICT_CLOSURE_ARGS = [ | 52 _STRICT_CLOSURE_ARGS = [ |
54 "--jscomp_error=reportUnknownTypes", | 53 "--jscomp_error=reportUnknownTypes", |
55 "--jscomp_error=duplicate", | 54 "--jscomp_error=duplicate", |
56 "--jscomp_error=misplacedTypeAnnotation", | 55 "--jscomp_error=misplacedTypeAnnotation", |
57 ] | 56 ] |
58 | 57 |
59 _DISABLED_CLOSURE_ARGS = [ | 58 _DISABLED_CLOSURE_ARGS = [ |
60 # TODO(dbeam): happens when the same file is <include>d multiple times. | 59 # TODO(dbeam): happens when the same file is <include>d multiple times. |
61 "--jscomp_off=duplicate", | 60 "--jscomp_off=duplicate", |
62 # TODO(fukino): happens when cr.defineProperty() has a type annotation. | 61 # TODO(fukino): happens when cr.defineProperty() has a type annotation. |
63 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562. | 62 # Avoiding parse-time warnings needs 2 pass compiling. crbug.com/421562. |
64 "--jscomp_off=misplacedTypeAnnotation", | 63 "--jscomp_off=misplacedTypeAnnotation", |
65 ] | 64 ] |
66 | 65 |
67 _JAR_COMMAND = [ | 66 _JAR_COMMAND = [ |
68 "java", | 67 "java", |
69 "-jar", | 68 "-jar", |
70 "-Xms1024m", | 69 "-Xms1024m", |
71 "-client", | 70 "-client", |
72 "-XX:+TieredCompilation" | 71 "-XX:+TieredCompilation" |
73 ] | 72 ] |
74 | 73 |
75 _found_java = False | |
76 | |
77 def __init__(self, verbose=False, strict=False): | 74 def __init__(self, verbose=False, strict=False): |
| 75 """ |
| 76 Args: |
| 77 verbose: Whether this class should output diagnostic messages. |
| 78 strict: Whether the Closure Compiler should be invoked more strictly. |
| 79 """ |
78 current_dir = os.path.join(os.path.dirname(__file__)) | 80 current_dir = os.path.join(os.path.dirname(__file__)) |
79 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") | 81 self._runner_jar = os.path.join(current_dir, "runner", "runner.jar") |
80 self._temp_files = [] | 82 self._temp_files = [] |
81 self._verbose = verbose | 83 self._verbose = verbose |
82 self._strict = strict | 84 self._strict = strict |
83 self._error_filter = error_filter.PromiseErrorFilter() | 85 self._error_filter = error_filter.PromiseErrorFilter() |
84 | 86 |
85 def _clean_up(self): | 87 def _clean_up(self): |
| 88 """Deletes any temp files this class knows about.""" |
86 if not self._temp_files: | 89 if not self._temp_files: |
87 return | 90 return |
88 | 91 |
89 self._debug("Deleting temporary files: %s" % ", ".join(self._temp_files)) | 92 self._log_debug("Deleting temp files: %s" % ", ".join(self._temp_files)) |
90 for f in self._temp_files: | 93 for f in self._temp_files: |
91 os.remove(f) | 94 os.remove(f) |
92 self._temp_files = [] | 95 self._temp_files = [] |
93 | 96 |
94 def _debug(self, msg, error=False): | 97 def _log_debug(self, msg, error=False): |
| 98 """Logs |msg| to stdout if --verbose/-v is passed when invoking this script. |
| 99 |
| 100 Args: |
| 101 msg: A debug message to log. |
| 102 """ |
95 if self._verbose: | 103 if self._verbose: |
96 print "(INFO) %s" % msg | 104 print "(INFO) %s" % msg |
97 | 105 |
98 def _error(self, msg): | 106 def _log_error(self, msg): |
| 107 """Logs |msg| to stderr regardless of --flags. |
| 108 |
| 109 Args: |
| 110 msg: An error message to log. |
| 111 """ |
99 print >> sys.stderr, "(ERROR) %s" % msg | 112 print >> sys.stderr, "(ERROR) %s" % msg |
100 self._clean_up() | |
101 | 113 |
102 def _common_args(self): | 114 def _common_args(self): |
103 """Returns an array of the common closure compiler args.""" | 115 """Returns an array of the common closure compiler args.""" |
104 if self._strict: | 116 if self._strict: |
105 return self._COMMON_CLOSURE_ARGS + self._STRICT_CLOSURE_ARGS | 117 return self._COMMON_CLOSURE_ARGS + self._STRICT_CLOSURE_ARGS |
106 return self._COMMON_CLOSURE_ARGS + self._DISABLED_CLOSURE_ARGS | 118 return self._COMMON_CLOSURE_ARGS + self._DISABLED_CLOSURE_ARGS |
107 | 119 |
108 def _run_command(self, cmd): | 120 def _run_jar(self, jar, args): |
109 """Runs a shell command. | 121 """Runs a .jar from the command line with arguments. |
110 | 122 |
111 Args: | 123 Args: |
112 cmd: A list of tokens to be joined into a shell command. | 124 jar: A file path to a .jar file |
| 125 args: A list of command line arguments to be passed when running the .jar. |
113 | 126 |
114 Return: | 127 Return: |
115 True if the exit code was 0, else False. | 128 (exit_code, stderr) The exit code of the command (e.g. 0 for success) and |
| 129 the stderr collected while running |jar| (as a string). |
116 """ | 130 """ |
117 cmd_str = " ".join(cmd) | 131 shell_command = " ".join(self._JAR_COMMAND + [jar] + args) |
118 self._debug("Running command: %s" % cmd_str) | 132 self._log_debug("Running jar: %s" % shell_command) |
119 | 133 |
120 devnull = open(os.devnull, "w") | 134 devnull = open(os.devnull, "w") |
121 return subprocess.Popen( | 135 kwargs = {"stdout": devnull, "stderr": subprocess.PIPE, "shell": True} |
122 cmd_str, stdout=devnull, stderr=subprocess.PIPE, shell=True) | 136 process = subprocess.Popen(shell_command, **kwargs) |
| 137 _, stderr = process.communicate() |
| 138 return process.returncode, stderr |
123 | 139 |
124 def _check_java_path(self): | 140 def _get_line_number(self, match): |
125 """Checks that `java` is on the system path.""" | 141 """When chrome is built, it preprocesses its JavaScript from: |
126 if not self._found_java: | |
127 proc = self._run_command(["which", "java"]) | |
128 proc.communicate() | |
129 if proc.returncode == 0: | |
130 self._found_java = True | |
131 else: | |
132 self._error("Cannot find java (`which java` => %s)" % proc.returncode) | |
133 | 142 |
134 return self._found_java | 143 <include src="blah.js"> |
| 144 alert(1); |
135 | 145 |
136 def _run_jar(self, jar, args=None): | 146 to: |
137 args = args or [] | |
138 self._check_java_path() | |
139 return self._run_command(self._JAR_COMMAND + [jar] + args) | |
140 | 147 |
141 def _fix_line_number(self, match): | 148 /* contents of blah.js inlined */ |
142 """Changes a line number from /tmp/file:300 to /orig/file:100. | 149 alert(1); |
| 150 |
| 151 Because Closure Compiler requires this inlining already be done (as |
| 152 <include> isn't valid JavaScript), this script creates temporary files to |
| 153 expand all the <include>s. |
| 154 |
| 155 When type errors are hit in temporary files, a developer doesn't know the |
| 156 original source location to fix. This method maps from /tmp/file:300 back to |
| 157 /original/source/file:100 so fixing errors is faster for developers. |
143 | 158 |
144 Args: | 159 Args: |
145 match: A re.MatchObject from matching against a line number regex. | 160 match: A re.MatchObject from matching against a line number regex. |
146 | 161 |
147 Returns: | 162 Returns: |
148 The fixed up /file and :line number. | 163 The fixed up /file and :line number. |
149 """ | 164 """ |
150 real_file = self._processor.get_file_from_line(match.group(1)) | 165 real_file = self._processor.get_file_from_line(match.group(1)) |
151 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) | 166 return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number) |
152 | 167 |
153 def _fix_up_error(self, error): | 168 def _filter_errors(self, errors): |
154 """Filter out irrelevant errors or fix line numbers. | 169 """Removes some extraneous errors. For example, we ignore: |
| 170 |
| 171 Variable x first declared in /tmp/expanded/file |
| 172 |
| 173 Because it's just a duplicated error (it'll only ever show up 2+ times). |
| 174 We also ignore Promose-based errors: |
| 175 |
| 176 found : function (VolumeInfo): (Promise<(DirectoryEntry|null)>|null) |
| 177 required: (function (Promise<VolumeInfo>): ?|null|undefined) |
| 178 |
| 179 as templates don't work with Promises in all cases yet. See |
| 180 https://github.com/google/closure-compiler/issues/715 for details. |
155 | 181 |
156 Args: | 182 Args: |
157 error: A Closure compiler error (2 line string with error and source). | 183 errors: A list of string errors extracted from Closure Compiler output. |
158 | 184 |
159 Return: | 185 Return: |
160 The fixed up error string (blank if it should be ignored). | 186 A slimmer, sleeker list of relevant errors (strings). |
161 """ | 187 """ |
162 if " first declared in " in error: | 188 first_declared_in = lambda e: " first declared in " not in e |
163 # Ignore "Variable x first declared in /same/file". | 189 return self._error_filter.filter(filter(first_declared_in, errors)) |
164 return "" | |
165 | 190 |
| 191 def _fix_up_error(self, error): |
| 192 """Reverse the effects that funky <include> preprocessing steps have on |
| 193 errors messages. |
| 194 |
| 195 Args: |
| 196 error: A Closure compiler error (2 line string with error and source). |
| 197 |
| 198 Return: |
| 199 The fixed up error string. |
| 200 """ |
166 expanded_file = self._expanded_file | 201 expanded_file = self._expanded_file |
167 fixed = re.sub("%s:(\d+)" % expanded_file, self._fix_line_number, error) | 202 fixed = re.sub("%s:(\d+)" % expanded_file, self._get_line_number, error) |
168 return fixed.replace(expanded_file, os.path.abspath(self._file_arg)) | 203 return fixed.replace(expanded_file, os.path.abspath(self._file_arg)) |
169 | 204 |
170 def _format_errors(self, errors): | 205 def _format_errors(self, errors): |
171 """Formats Closure compiler errors to easily spot compiler output.""" | 206 """Formats Closure compiler errors to easily spot compiler output. |
172 errors = filter(None, errors) | 207 |
| 208 Args: |
| 209 errors: A list of strings extracted from the Closure compiler's output. |
| 210 |
| 211 Returns: |
| 212 A formatted output string. |
| 213 """ |
173 contents = "\n## ".join("\n\n".join(errors).splitlines()) | 214 contents = "\n## ".join("\n\n".join(errors).splitlines()) |
174 return "## %s" % contents if contents else "" | 215 return "## %s" % contents if contents else "" |
175 | 216 |
176 def _create_temp_file(self, contents): | 217 def _create_temp_file(self, contents): |
| 218 """Creates an owned temporary file with |contents|. |
| 219 |
| 220 Args: |
| 221 content: A string of the file contens to write to a temporary file. |
| 222 |
| 223 Return: |
| 224 The filepath of the newly created, written, and closed temporary file. |
| 225 """ |
177 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: | 226 with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file: |
178 self._temp_files.append(tmp_file.name) | 227 self._temp_files.append(tmp_file.name) |
179 tmp_file.write(contents) | 228 tmp_file.write(contents) |
180 return tmp_file.name | 229 return tmp_file.name |
181 | 230 |
182 def _run_js_check(self, sources, out_file=None, externs=None): | 231 def _run_js_check(self, sources, out_file=None, externs=None): |
183 if not self._check_java_path(): | 232 """Check |sources| for type errors. |
184 return 1, "" | |
185 | 233 |
| 234 Args: |
| 235 sources: Files to check. |
| 236 externs: @extern files that inform the compiler about custom globals. |
| 237 |
| 238 Returns: |
| 239 (errors, stderr) A parsed list of errors (strings) found by the compiler |
| 240 and the raw stderr (as a string). |
| 241 """ |
186 args = ["--js=%s" % s for s in sources] | 242 args = ["--js=%s" % s for s in sources] |
187 | 243 |
188 if out_file: | 244 if out_file: |
189 args += ["--js_output_file=%s" % out_file] | 245 args += ["--js_output_file=%s" % out_file] |
190 args += ["--create_source_map=%s.map" % out_file] | 246 args += ["--create_source_map=%s.map" % out_file] |
191 | 247 |
192 if externs: | 248 if externs: |
193 args += ["--externs=%s" % e for e in externs] | 249 args += ["--externs=%s" % e for e in externs] |
| 250 |
194 args_file_content = " %s" % " ".join(self._common_args() + args) | 251 args_file_content = " %s" % " ".join(self._common_args() + args) |
195 self._debug("Args: %s" % args_file_content.strip()) | 252 self._log_debug("Args: %s" % args_file_content.strip()) |
196 | 253 |
197 args_file = self._create_temp_file(args_file_content) | 254 args_file = self._create_temp_file(args_file_content) |
198 self._debug("Args file: %s" % args_file) | 255 self._log_debug("Args file: %s" % args_file) |
199 | 256 |
200 runner_args = ["--compiler-args-file=%s" % args_file] | 257 runner_args = ["--compiler-args-file=%s" % args_file] |
201 runner_cmd = self._run_jar(self._runner_jar, args=runner_args) | 258 _, stderr = self._run_jar(self._runner_jar, runner_args) |
202 _, stderr = runner_cmd.communicate() | |
203 | 259 |
204 errors = stderr.strip().split("\n\n") | 260 errors = stderr.strip().split("\n\n") |
205 self._debug("Summary: %s" % errors.pop()) | 261 maybe_summary = errors.pop() |
206 | 262 |
207 self._clean_up() | 263 if re.search(".*error.*warning.*typed", maybe_summary): |
| 264 self._log_debug("Summary: %s" % maybe_summary) |
| 265 else: |
| 266 # Not a summary. Running the jar failed. Bail. |
| 267 self._log_error(stderr) |
| 268 self._clean_up() |
| 269 sys.exit(1) |
208 | 270 |
209 return errors, stderr | 271 return errors, stderr |
210 | 272 |
211 def check(self, source_file, out_file=None, depends=None, externs=None): | 273 def check(self, source_file, out_file=None, depends=None, externs=None): |
212 """Closure compile a file and check for errors. | 274 """Closure compiler |source_file| while checking for errors. |
213 | 275 |
214 Args: | 276 Args: |
215 source_file: A file to check. | 277 source_file: A file to check. |
216 out_file: A file where the compiled output is written to. | 278 out_file: A file where the compiled output is written to. |
217 depends: Other files that would be included with a <script> earlier in | 279 depends: Files that |source_file| requires to run (e.g. earlier <script>). |
218 the page. | 280 externs: @extern files that inform the compiler about custom globals. |
219 externs: @extern files that inform the compiler about custom globals. | |
220 | 281 |
221 Returns: | 282 Returns: |
222 (has_errors, output) A boolean indicating if there were errors and the | 283 (found_errors, stderr) A boolean indicating whether errors were found and |
223 Closure compiler output (as a string). | 284 the raw Closure compiler stderr (as a string). |
224 """ | 285 """ |
225 depends = depends or [] | 286 self._log_debug("FILE: %s" % source_file) |
226 externs = externs or set() | |
227 | |
228 if not self._check_java_path(): | |
229 return 1, "" | |
230 | |
231 self._debug("FILE: %s" % source_file) | |
232 | 287 |
233 if source_file.endswith("_externs.js"): | 288 if source_file.endswith("_externs.js"): |
234 self._debug("Skipping externs: %s" % source_file) | 289 self._log_debug("Skipping externs: %s" % source_file) |
235 return | 290 return |
236 | 291 |
237 self._file_arg = source_file | 292 self._file_arg = source_file |
238 | 293 |
239 tmp_dir = tempfile.gettempdir() | 294 cwd, tmp_dir = os.getcwd(), tempfile.gettempdir() |
240 rel_path = lambda f: os.path.join(os.path.relpath(os.getcwd(), tmp_dir), f) | 295 rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f) |
241 | 296 |
| 297 depends = depends or [] |
242 includes = [rel_path(f) for f in depends + [source_file]] | 298 includes = [rel_path(f) for f in depends + [source_file]] |
243 contents = ['<include src="%s">' % i for i in includes] | 299 contents = ['<include src="%s">' % i for i in includes] |
244 meta_file = self._create_temp_file("\n".join(contents)) | 300 meta_file = self._create_temp_file("\n".join(contents)) |
245 self._debug("Meta file: %s" % meta_file) | 301 self._log_debug("Meta file: %s" % meta_file) |
246 | 302 |
247 self._processor = processor.Processor(meta_file) | 303 self._processor = processor.Processor(meta_file) |
248 self._expanded_file = self._create_temp_file(self._processor.contents) | 304 self._expanded_file = self._create_temp_file(self._processor.contents) |
249 self._debug("Expanded file: %s" % self._expanded_file) | 305 self._log_debug("Expanded file: %s" % self._expanded_file) |
250 | 306 |
251 errors, stderr = self._run_js_check([self._expanded_file], | 307 errors, stderr = self._run_js_check([self._expanded_file], |
252 out_file=out_file, externs=externs) | 308 out_file=out_file, externs=externs) |
| 309 filtered_errors = self._filter_errors(errors) |
| 310 fixed_errors = map(self._fix_up_error, filtered_errors) |
| 311 output = self._format_errors(fixed_errors) |
253 | 312 |
254 # Filter out false-positive promise chain errors. | 313 if fixed_errors: |
255 # See https://github.com/google/closure-compiler/issues/715 for details. | 314 prefix = "\n" if output else "" |
256 errors = self._error_filter.filter(errors); | 315 self._log_error("Error in: %s%s%s" % (source_file, prefix, output)) |
| 316 elif output: |
| 317 self._log_debug("Output: %s" % output) |
257 | 318 |
258 output = self._format_errors(map(self._fix_up_error, errors)) | 319 self._clean_up() |
259 if errors: | 320 return bool(fixed_errors), stderr |
260 prefix = "\n" if output else "" | |
261 self._error("Error in: %s%s%s" % (source_file, prefix, output)) | |
262 elif output: | |
263 self._debug("Output: %s" % output) | |
264 | |
265 return bool(errors), output | |
266 | 321 |
267 def check_multiple(self, sources): | 322 def check_multiple(self, sources): |
268 """Closure compile a set of files and check for errors. | 323 """Closure compile a set of files and check for errors. |
269 | 324 |
270 Args: | 325 Args: |
271 sources: An array of files to check. | 326 sources: An array of files to check. |
272 | 327 |
273 Returns: | 328 Returns: |
274 (has_errors, output) A boolean indicating if there were errors and the | 329 (found_errors, stderr) A boolean indicating whether errors were found and |
275 Closure compiler output (as a string). | 330 the raw Closure Compiler stderr (as a string). |
276 """ | 331 """ |
| 332 errors, stderr = self._run_js_check(sources, []) |
| 333 self._clean_up() |
| 334 return bool(errors), stderr |
277 | 335 |
278 errors, stderr = self._run_js_check(sources) | |
279 return bool(errors), stderr | |
280 | 336 |
281 if __name__ == "__main__": | 337 if __name__ == "__main__": |
282 parser = argparse.ArgumentParser( | 338 parser = argparse.ArgumentParser( |
283 description="Typecheck JavaScript using Closure compiler") | 339 description="Typecheck JavaScript using Closure compiler") |
284 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, | 340 parser.add_argument("sources", nargs=argparse.ONE_OR_MORE, |
285 help="Path to a source file to typecheck") | 341 help="Path to a source file to typecheck") |
286 single_file_group = parser.add_mutually_exclusive_group() | 342 single_file_group = parser.add_mutually_exclusive_group() |
287 single_file_group.add_argument("--single-file", dest="single_file", | 343 single_file_group.add_argument("--single-file", dest="single_file", |
288 action="store_true", | 344 action="store_true", |
289 help="Process each source file individually") | 345 help="Process each source file individually") |
(...skipping 19 matching lines...) Expand all Loading... |
309 | 365 |
310 if opts.out_file: | 366 if opts.out_file: |
311 out_dir = os.path.dirname(opts.out_file) | 367 out_dir = os.path.dirname(opts.out_file) |
312 if not os.path.exists(out_dir): | 368 if not os.path.exists(out_dir): |
313 os.makedirs(out_dir) | 369 os.makedirs(out_dir) |
314 | 370 |
315 checker = Checker(verbose=opts.verbose, strict=opts.strict) | 371 checker = Checker(verbose=opts.verbose, strict=opts.strict) |
316 if opts.single_file: | 372 if opts.single_file: |
317 for source in opts.sources: | 373 for source in opts.sources: |
318 depends, externs = build.inputs.resolve_recursive_dependencies( | 374 depends, externs = build.inputs.resolve_recursive_dependencies( |
319 source, | 375 source, depends, externs) |
320 depends, | 376 found_errors, _ = checker.check(source, out_file=opts.out_file, |
321 externs) | 377 depends=depends, externs=externs) |
322 has_errors, _ = checker.check(source, out_file=opts.out_file, | 378 if found_errors: |
323 depends=depends, externs=externs) | |
324 if has_errors: | |
325 sys.exit(1) | 379 sys.exit(1) |
326 | |
327 else: | 380 else: |
328 has_errors, errors = checker.check_multiple(opts.sources) | 381 found_errors, stderr = checker.check_multiple(opts.sources) |
329 if has_errors: | 382 if found_errors: |
330 print errors | 383 print stderr |
331 sys.exit(1) | 384 sys.exit(1) |
332 | 385 |
333 if opts.success_stamp: | 386 if opts.success_stamp: |
334 with open(opts.success_stamp, 'w'): | 387 with open(opts.success_stamp, "w"): |
335 os.utime(opts.success_stamp, None) | 388 os.utime(opts.success_stamp, None) |
OLD | NEW |