OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dart2js.cmdline; | 5 library dart2js.cmdline; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io' | 8 import 'dart:io' |
9 show exit, File, FileMode, Platform, RandomAccessFile; | 9 show exit, File, FileMode, Platform, RandomAccessFile; |
10 import 'dart:math' as math; | 10 import 'dart:math' as math; |
(...skipping 27 matching lines...) Expand all Loading... | |
38 typedef void HandleOption(data); | 38 typedef void HandleOption(data); |
39 | 39 |
40 class OptionHandler { | 40 class OptionHandler { |
41 final String pattern; | 41 final String pattern; |
42 final HandleOption handle; | 42 final HandleOption handle; |
43 final bool multipleArguments; | 43 final bool multipleArguments; |
44 | 44 |
45 OptionHandler(this.pattern, this.handle, {this.multipleArguments: false}); | 45 OptionHandler(this.pattern, this.handle, {this.multipleArguments: false}); |
46 } | 46 } |
47 | 47 |
48 /** | 48 class _Dart2Js { |
ahe
2013/11/21 12:44:53
If you think it makes sense to create a class here
| |
49 * Extract the parameter of an option. | 49 final exitFunc; |
50 * | 50 final compileFunc; |
51 * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters | 51 |
52 * are ['fisk.js'] and ['hest.js'], respectively. | 52 _Dart2Js(this.exitFunc, this.compileFunc); |
53 */ | 53 |
54 String extractParameter(String argument) { | 54 /** |
55 // m[0] is the entire match (which will be equal to argument). m[1] | 55 * Extract the parameter of an option. |
56 // is something like "-o" or "--out=", and m[2] is the parameter. | 56 * |
57 Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument); | 57 * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters |
58 if (m == null) helpAndFail('Error: Unknown option "$argument".'); | 58 * are ['fisk.js'] and ['hest.js'], respectively. |
59 return m[2]; | 59 */ |
60 String extractParameter(String argument) { | |
61 // m[0] is the entire match (which will be equal to argument). m[1] | |
62 // is something like "-o" or "--out=", and m[2] is the parameter. | |
63 Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument); | |
64 if (m == null) helpAndFail('Error: Unknown option "$argument".'); | |
65 return m[2]; | |
66 } | |
67 | |
68 String extractPath(String argument) { | |
69 String path = nativeToUriPath(extractParameter(argument)); | |
70 return path.endsWith("/") ? path : "$path/"; | |
71 } | |
72 | |
73 void parseCommandLine(List<OptionHandler> handlers, List<String> argv) { | |
74 // TODO(ahe): Use ../../args/args.dart for parsing options instead. | |
75 var patterns = <String>[]; | |
76 for (OptionHandler handler in handlers) { | |
77 patterns.add(handler.pattern); | |
78 } | |
79 var pattern = new RegExp('^(${patterns.join(")\$|(")})\$'); | |
80 | |
81 Iterator<String> arguments = argv.iterator; | |
82 OUTER: while (arguments.moveNext()) { | |
83 String argument = arguments.current; | |
84 Match match = pattern.firstMatch(argument); | |
85 assert(match.groupCount == handlers.length); | |
86 for (int i = 0; i < handlers.length; i++) { | |
87 if (match[i + 1] != null) { | |
88 OptionHandler handler = handlers[i]; | |
89 if (handler.multipleArguments) { | |
90 handler.handle(arguments); | |
91 } else { | |
92 handler.handle(argument); | |
93 } | |
94 continue OUTER; | |
95 } | |
96 } | |
97 throw 'Internal error: "$argument" did not match'; | |
98 } | |
99 } | |
100 | |
101 FormattingDiagnosticHandler diagnosticHandler; | |
102 | |
103 Future compile(List<String> argv) { | |
104 bool isWindows = (Platform.operatingSystem == 'windows'); | |
105 stackTraceFilePrefix = '$currentDirectory'; | |
106 Uri libraryRoot = currentDirectory; | |
107 Uri out = currentDirectory.resolve('out.js'); | |
108 Uri sourceMapOut = currentDirectory.resolve('out.js.map'); | |
109 Uri packageRoot = null; | |
110 List<String> options = new List<String>(); | |
111 bool explicitOut = false; | |
112 bool wantHelp = false; | |
113 bool wantVersion = false; | |
114 String outputLanguage = 'JavaScript'; | |
115 bool stripArgumentSet = false; | |
116 bool analyzeOnly = false; | |
117 bool hasDisallowUnsafeEval = false; | |
118 // TODO(johnniwinther): Measure time for reading files. | |
119 SourceFileProvider inputProvider = new CompilerSourceFileProvider(); | |
120 diagnosticHandler = new FormattingDiagnosticHandler(inputProvider); | |
121 Map<String, dynamic> environment = new Map<String, dynamic>(); | |
122 | |
123 passThrough(String argument) => options.add(argument); | |
124 | |
125 if (BUILD_ID != null) { | |
126 passThrough("--build-id=$BUILD_ID"); | |
127 } | |
128 | |
129 setLibraryRoot(String argument) { | |
130 libraryRoot = currentDirectory.resolve(extractPath(argument)); | |
131 } | |
132 | |
133 setPackageRoot(String argument) { | |
134 packageRoot = currentDirectory.resolve(extractPath(argument)); | |
135 } | |
136 | |
137 setOutput(Iterator<String> arguments) { | |
138 String path; | |
139 if (arguments.current == '-o') { | |
140 if (!arguments.moveNext()) { | |
141 helpAndFail('Error: Missing file after -o option.'); | |
142 } | |
143 path = arguments.current; | |
144 } else { | |
145 path = extractParameter(arguments.current); | |
146 } | |
147 explicitOut = true; | |
148 out = currentDirectory.resolve(nativeToUriPath(path)); | |
149 sourceMapOut = Uri.parse('$out.map'); | |
150 } | |
151 | |
152 setOutputType(String argument) { | |
153 if (argument == '--output-type=dart') { | |
154 outputLanguage = OUTPUT_LANGUAGE_DART; | |
155 if (!explicitOut) { | |
156 out = currentDirectory.resolve('out.dart'); | |
157 sourceMapOut = currentDirectory.resolve('out.dart.map'); | |
158 } | |
159 } | |
160 passThrough(argument); | |
161 } | |
162 | |
163 String getDepsOutput(Map<String, SourceFile> sourceFiles) { | |
164 var filenames = new List.from(sourceFiles.keys); | |
165 filenames.sort(); | |
166 return filenames.join("\n"); | |
167 } | |
168 | |
169 setStrip(String argument) { | |
170 stripArgumentSet = true; | |
171 passThrough(argument); | |
172 } | |
173 | |
174 setAnalyzeOnly(String argument) { | |
175 analyzeOnly = true; | |
176 passThrough(argument); | |
177 } | |
178 | |
179 setVerbose(_) { | |
180 diagnosticHandler.verbose = true; | |
181 passThrough('--verbose'); | |
182 } | |
183 | |
184 addInEnvironment(String argument) { | |
185 int eqIndex = argument.indexOf('='); | |
186 String name = argument.substring(2, eqIndex); | |
187 String value = argument.substring(eqIndex + 1); | |
188 environment[name] = value; | |
189 } | |
190 | |
191 setCategories(String argument) { | |
192 List<String> categories = extractParameter(argument).split(','); | |
193 Set<String> allowedCategories = | |
194 LIBRARIES.values.map((x) => x.category).toSet(); | |
195 allowedCategories.remove('Shared'); | |
196 allowedCategories.remove('Internal'); | |
197 List<String> allowedCategoriesList = | |
198 new List<String>.from(allowedCategories); | |
199 allowedCategoriesList.sort(); | |
200 if (categories.contains('all')) { | |
201 categories = allowedCategoriesList; | |
202 } else { | |
203 String allowedCategoriesString = allowedCategoriesList.join(', '); | |
204 for (String category in categories) { | |
205 if (!allowedCategories.contains(category)) { | |
206 fail('Error: unsupported library category "$category", ' | |
207 'supported categories are: $allowedCategoriesString'); | |
208 } | |
209 } | |
210 } | |
211 passThrough('--categories=${categories.join(",")}'); | |
212 } | |
213 | |
214 checkGlobalName(String argument) { | |
215 String globalName = extractParameter(argument); | |
216 if (!new RegExp(r'^\$[a-z]*$').hasMatch(globalName)) { | |
217 fail('Error: "$globalName" must match "\\\$[a-z]*"'); | |
218 } | |
219 passThrough(argument); | |
220 } | |
221 | |
222 handleShortOptions(String argument) { | |
223 var shortOptions = argument.substring(1).split(""); | |
224 for (var shortOption in shortOptions) { | |
225 switch (shortOption) { | |
226 case 'v': | |
227 setVerbose(null); | |
228 break; | |
229 case 'h': | |
230 case '?': | |
231 wantHelp = true; | |
232 break; | |
233 case 'c': | |
234 passThrough('--enable-checked-mode'); | |
235 break; | |
236 default: | |
237 throw 'Internal error: "$shortOption" did not match'; | |
238 } | |
239 } | |
240 } | |
241 | |
242 Uri computePrecompiledUri() { | |
243 String extension = 'precompiled.js'; | |
244 String outPath = out.path; | |
245 if (outPath.endsWith('.js')) { | |
246 outPath = outPath.substring(0, outPath.length - 3); | |
247 return out.resolve('$outPath.$extension'); | |
248 } else { | |
249 return out.resolve(extension); | |
250 } | |
251 } | |
252 | |
253 List<String> arguments = <String>[]; | |
254 List<OptionHandler> handlers = <OptionHandler>[ | |
255 new OptionHandler('-[chv?]+', handleShortOptions), | |
256 new OptionHandler('--throw-on-error', | |
257 (_) => diagnosticHandler.throwOnError = true), | |
258 new OptionHandler('--suppress-warnings', | |
259 (_) => diagnosticHandler.showWarnings = false), | |
260 new OptionHandler('--suppress-hints', | |
261 (_) => diagnosticHandler.showHints = false), | |
262 new OptionHandler('--output-type=dart|--output-type=js', setOutputType), | |
263 new OptionHandler('--verbose', setVerbose), | |
264 new OptionHandler('--version', (_) => wantVersion = true), | |
265 new OptionHandler('--library-root=.+', setLibraryRoot), | |
266 new OptionHandler('--out=.+|-o.*', setOutput, multipleArguments: true), | |
267 new OptionHandler('--allow-mock-compilation', passThrough), | |
268 new OptionHandler('--minify', passThrough), | |
269 new OptionHandler('--force-strip=.*', setStrip), | |
270 new OptionHandler('--disable-diagnostic-colors', | |
271 (_) => diagnosticHandler.enableColors = false), | |
272 new OptionHandler('--enable-diagnostic-colors', | |
273 (_) => diagnosticHandler.enableColors = true), | |
274 new OptionHandler('--enable[_-]checked[_-]mode|--checked', | |
275 (_) => passThrough('--enable-checked-mode')), | |
276 new OptionHandler('--enable-concrete-type-inference', | |
277 (_) => passThrough('--enable-concrete-type-inference')), | |
278 new OptionHandler('--trust-type-annotations', | |
279 (_) => passThrough('--trust-type-annotations')), | |
280 new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true), | |
281 new OptionHandler('--package-root=.+|-p.+', setPackageRoot), | |
282 new OptionHandler('--analyze-all', passThrough), | |
283 new OptionHandler('--analyze-only', setAnalyzeOnly), | |
284 new OptionHandler('--analyze-signatures-only', passThrough), | |
285 new OptionHandler('--disable-native-live-type-analysis', passThrough), | |
286 new OptionHandler('--categories=.*', setCategories), | |
287 new OptionHandler('--global-js-name=.*', checkGlobalName), | |
288 new OptionHandler('--disable-type-inference', passThrough), | |
289 new OptionHandler('--terse', passThrough), | |
290 new OptionHandler('--disallow-unsafe-eval', | |
291 (_) => hasDisallowUnsafeEval = true), | |
292 new OptionHandler('-D.+=.*', addInEnvironment), | |
293 | |
294 // The following two options must come last. | |
295 new OptionHandler('-.*', (String argument) { | |
296 helpAndFail('Error: Unknown option "$argument".'); | |
297 }), | |
298 new OptionHandler('.*', (String argument) { | |
299 arguments.add(nativeToUriPath(argument)); | |
300 }) | |
301 ]; | |
302 | |
303 parseCommandLine(handlers, argv); | |
304 if (wantHelp || wantVersion) { | |
305 helpAndExit(wantHelp, wantVersion, diagnosticHandler.verbose); | |
306 } | |
307 | |
308 if (hasDisallowUnsafeEval) { | |
309 String precompiledName = | |
310 relativize(currentDirectory, computePrecompiledUri(), isWindows); | |
311 helpAndFail("Error: option '--disallow-unsafe-eval' has been removed." | |
312 " Instead, the compiler generates a file named" | |
313 " '$precompiledName'."); | |
314 } | |
315 | |
316 if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) { | |
317 helpAndFail('Error: --force-strip may only be used with ' | |
318 '--output-type=dart'); | |
319 } | |
320 if (arguments.isEmpty) { | |
321 helpAndFail('Error: No Dart file specified.'); | |
322 } | |
323 if (arguments.length > 1) { | |
324 var extra = arguments.sublist(1); | |
325 helpAndFail('Error: Extra arguments: ${extra.join(" ")}'); | |
326 } | |
327 | |
328 Uri uri = currentDirectory.resolve(arguments[0]); | |
329 if (packageRoot == null) { | |
330 packageRoot = uri.resolve('./packages/'); | |
331 } | |
332 | |
333 diagnosticHandler.info('package root is $packageRoot'); | |
334 | |
335 int totalCharactersWritten = 0; | |
336 | |
337 options.add('--source-map=$sourceMapOut'); | |
338 | |
339 compilationDone(String code) { | |
340 if (analyzeOnly) return; | |
341 if (code == null) { | |
342 fail('Error: Compilation failed.'); | |
343 } | |
344 writeString(Uri.parse('$out.deps'), | |
345 getDepsOutput(inputProvider.sourceFiles)); | |
346 diagnosticHandler.info( | |
347 'compiled ${inputProvider.dartCharactersRead} characters Dart ' | |
348 '-> $totalCharactersWritten characters $outputLanguage ' | |
349 'in ${relativize(currentDirectory, out, isWindows)}'); | |
350 if (!explicitOut) { | |
351 String input = uriPathToNative(arguments[0]); | |
352 String output = relativize(currentDirectory, out, isWindows); | |
353 print('Dart file ($input) compiled to $outputLanguage: $output'); | |
354 } | |
355 } | |
356 | |
357 EventSink<String> outputProvider(String name, String extension) { | |
358 Uri uri; | |
359 String sourceMapFileName; | |
360 bool isPrimaryOutput = false; | |
361 if (name == '') { | |
362 if (extension == 'js' || extension == 'dart') { | |
363 isPrimaryOutput = true; | |
364 uri = out; | |
365 sourceMapFileName = | |
366 sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1 ); | |
367 } else if (extension == 'precompiled.js') { | |
368 uri = computePrecompiledUri(); | |
369 diagnosticHandler.info( | |
370 "File ($uri) is compatible with header" | |
371 " \"Content-Security-Policy: script-src 'self'\""); | |
372 } else if (extension == 'js.map' || extension == 'dart.map') { | |
373 uri = sourceMapOut; | |
374 } else { | |
375 fail('Error: Unknown extension: $extension'); | |
376 } | |
377 } else { | |
378 uri = out.resolve('$name.$extension'); | |
379 } | |
380 | |
381 if (uri.scheme != 'file') { | |
382 fail('Error: Unhandled scheme ${uri.scheme} in $uri.'); | |
383 } | |
384 | |
385 RandomAccessFile output = | |
386 new File(uriPathToNative(uri.path)).openSync(mode: FileMode.WRITE); | |
387 int charactersWritten = 0; | |
388 | |
389 writeStringSync(String data) { | |
390 // Write the data in chunks of 8kb, otherwise we risk running OOM | |
391 int chunkSize = 8*1024; | |
392 | |
393 int offset = 0; | |
394 while (offset < data.length) { | |
395 output.writeStringSync( | |
396 data.substring(offset, math.min(offset + chunkSize, data.length))) ; | |
397 offset += chunkSize; | |
398 } | |
399 charactersWritten += data.length; | |
400 } | |
401 | |
402 onDone() { | |
403 if (sourceMapFileName != null) { | |
404 // Using # is the new proposed standard. @ caused problems in Internet | |
405 // Explorer due to "Conditional Compilation Statements" in JScript, | |
406 // see: | |
407 // http://msdn.microsoft.com/en-us/library/7kx09ct1(v=vs.80).aspx | |
408 // About source maps, see: | |
409 // https://docs.google.com/a/google.com/document/d/1U1RGAehQwRypUTovF1 KRlpiOFze0b-_2gc6fAH0KY0k/edit | |
410 // TODO(http://dartbug.com/11914): Remove @ line. | |
411 String sourceMapTag = ''' | |
412 | |
413 //# sourceMappingURL=$sourceMapFileName | |
414 //@ sourceMappingURL=$sourceMapFileName | |
415 '''; | |
416 writeStringSync(sourceMapTag); | |
417 } | |
418 output.closeSync(); | |
419 if (isPrimaryOutput) { | |
420 totalCharactersWritten += charactersWritten; | |
421 } | |
422 } | |
423 | |
424 return new EventSinkWrapper(writeStringSync, onDone); | |
425 } | |
426 | |
427 return compileFunc(uri, libraryRoot, packageRoot, | |
428 inputProvider, diagnosticHandler, | |
429 options, outputProvider, environment) | |
430 .then(compilationDone); | |
431 } | |
432 | |
433 void writeString(Uri uri, String text) { | |
434 if (uri.scheme != 'file') { | |
435 fail('Error: Unhandled scheme ${uri.scheme}.'); | |
436 } | |
437 var file = new File(uriPathToNative(uri.path)).openSync(mode: FileMode.WRITE ); | |
438 file.writeStringSync(text); | |
439 file.closeSync(); | |
440 } | |
441 | |
442 void fail(String message) { | |
443 if (diagnosticHandler != null) { | |
444 diagnosticHandler.diagnosticHandler( | |
445 null, -1, -1, message, api.Diagnostic.ERROR); | |
446 } else { | |
447 print(message); | |
448 } | |
449 exitFunc(1); | |
450 } | |
451 | |
452 Future compilerMain(List<String> arguments) { | |
453 var root = uriPathToNative("/$LIBRARY_ROOT"); | |
454 arguments = <String>['--library-root=${Platform.script.toFilePath()}$root'] | |
455 ..addAll(arguments); | |
456 return compile(arguments); | |
457 } | |
458 | |
459 void helpAndExit(bool wantHelp, bool wantVersion, bool verbose) { | |
460 if (wantVersion) { | |
461 var version = (BUILD_ID == null) | |
462 ? '<non-SDK build>' | |
463 : BUILD_ID; | |
464 print('Dart-to-JavaScript compiler (dart2js) version: $version'); | |
465 } | |
466 if (wantHelp) { | |
467 if (verbose) { | |
468 verboseHelp(); | |
469 } else { | |
470 help(); | |
471 } | |
472 } | |
473 exitFunc(0); | |
474 } | |
475 | |
476 void helpAndFail(String message) { | |
477 help(); | |
478 print(''); | |
479 fail(message); | |
480 } | |
60 } | 481 } |
61 | 482 |
62 String extractPath(String argument) { | |
63 String path = nativeToUriPath(extractParameter(argument)); | |
64 return path.endsWith("/") ? path : "$path/"; | |
65 } | |
66 | |
67 void parseCommandLine(List<OptionHandler> handlers, List<String> argv) { | |
68 // TODO(ahe): Use ../../args/args.dart for parsing options instead. | |
69 var patterns = <String>[]; | |
70 for (OptionHandler handler in handlers) { | |
71 patterns.add(handler.pattern); | |
72 } | |
73 var pattern = new RegExp('^(${patterns.join(")\$|(")})\$'); | |
74 | |
75 Iterator<String> arguments = argv.iterator; | |
76 OUTER: while (arguments.moveNext()) { | |
77 String argument = arguments.current; | |
78 Match match = pattern.firstMatch(argument); | |
79 assert(match.groupCount == handlers.length); | |
80 for (int i = 0; i < handlers.length; i++) { | |
81 if (match[i + 1] != null) { | |
82 OptionHandler handler = handlers[i]; | |
83 if (handler.multipleArguments) { | |
84 handler.handle(arguments); | |
85 } else { | |
86 handler.handle(argument); | |
87 } | |
88 continue OUTER; | |
89 } | |
90 } | |
91 throw 'Internal error: "$argument" did not match'; | |
92 } | |
93 } | |
94 | |
95 FormattingDiagnosticHandler diagnosticHandler; | |
96 | |
97 Future compile(List<String> argv) { | |
98 bool isWindows = (Platform.operatingSystem == 'windows'); | |
99 stackTraceFilePrefix = '$currentDirectory'; | |
100 Uri libraryRoot = currentDirectory; | |
101 Uri out = currentDirectory.resolve('out.js'); | |
102 Uri sourceMapOut = currentDirectory.resolve('out.js.map'); | |
103 Uri packageRoot = null; | |
104 List<String> options = new List<String>(); | |
105 bool explicitOut = false; | |
106 bool wantHelp = false; | |
107 bool wantVersion = false; | |
108 String outputLanguage = 'JavaScript'; | |
109 bool stripArgumentSet = false; | |
110 bool analyzeOnly = false; | |
111 bool hasDisallowUnsafeEval = false; | |
112 // TODO(johnniwinther): Measure time for reading files. | |
113 SourceFileProvider inputProvider = new CompilerSourceFileProvider(); | |
114 diagnosticHandler = new FormattingDiagnosticHandler(inputProvider); | |
115 Map<String, dynamic> environment = new Map<String, dynamic>(); | |
116 | |
117 passThrough(String argument) => options.add(argument); | |
118 | |
119 if (BUILD_ID != null) { | |
120 passThrough("--build-id=$BUILD_ID"); | |
121 } | |
122 | |
123 setLibraryRoot(String argument) { | |
124 libraryRoot = currentDirectory.resolve(extractPath(argument)); | |
125 } | |
126 | |
127 setPackageRoot(String argument) { | |
128 packageRoot = currentDirectory.resolve(extractPath(argument)); | |
129 } | |
130 | |
131 setOutput(Iterator<String> arguments) { | |
132 String path; | |
133 if (arguments.current == '-o') { | |
134 if (!arguments.moveNext()) { | |
135 helpAndFail('Error: Missing file after -o option.'); | |
136 } | |
137 path = arguments.current; | |
138 } else { | |
139 path = extractParameter(arguments.current); | |
140 } | |
141 explicitOut = true; | |
142 out = currentDirectory.resolve(nativeToUriPath(path)); | |
143 sourceMapOut = Uri.parse('$out.map'); | |
144 } | |
145 | |
146 setOutputType(String argument) { | |
147 if (argument == '--output-type=dart') { | |
148 outputLanguage = OUTPUT_LANGUAGE_DART; | |
149 if (!explicitOut) { | |
150 out = currentDirectory.resolve('out.dart'); | |
151 sourceMapOut = currentDirectory.resolve('out.dart.map'); | |
152 } | |
153 } | |
154 passThrough(argument); | |
155 } | |
156 | |
157 String getDepsOutput(Map<String, SourceFile> sourceFiles) { | |
158 var filenames = new List.from(sourceFiles.keys); | |
159 filenames.sort(); | |
160 return filenames.join("\n"); | |
161 } | |
162 | |
163 setStrip(String argument) { | |
164 stripArgumentSet = true; | |
165 passThrough(argument); | |
166 } | |
167 | |
168 setAnalyzeOnly(String argument) { | |
169 analyzeOnly = true; | |
170 passThrough(argument); | |
171 } | |
172 | |
173 setVerbose(_) { | |
174 diagnosticHandler.verbose = true; | |
175 passThrough('--verbose'); | |
176 } | |
177 | |
178 addInEnvironment(String argument) { | |
179 int eqIndex = argument.indexOf('='); | |
180 String name = argument.substring(2, eqIndex); | |
181 String value = argument.substring(eqIndex + 1); | |
182 environment[name] = value; | |
183 } | |
184 | |
185 setCategories(String argument) { | |
186 List<String> categories = extractParameter(argument).split(','); | |
187 Set<String> allowedCategories = | |
188 LIBRARIES.values.map((x) => x.category).toSet(); | |
189 allowedCategories.remove('Shared'); | |
190 allowedCategories.remove('Internal'); | |
191 List<String> allowedCategoriesList = | |
192 new List<String>.from(allowedCategories); | |
193 allowedCategoriesList.sort(); | |
194 if (categories.contains('all')) { | |
195 categories = allowedCategoriesList; | |
196 } else { | |
197 String allowedCategoriesString = allowedCategoriesList.join(', '); | |
198 for (String category in categories) { | |
199 if (!allowedCategories.contains(category)) { | |
200 fail('Error: unsupported library category "$category", ' | |
201 'supported categories are: $allowedCategoriesString'); | |
202 } | |
203 } | |
204 } | |
205 passThrough('--categories=${categories.join(",")}'); | |
206 } | |
207 | |
208 checkGlobalName(String argument) { | |
209 String globalName = extractParameter(argument); | |
210 if (!new RegExp(r'^\$[a-z]*$').hasMatch(globalName)) { | |
211 fail('Error: "$globalName" must match "\\\$[a-z]*"'); | |
212 } | |
213 passThrough(argument); | |
214 } | |
215 | |
216 handleShortOptions(String argument) { | |
217 var shortOptions = argument.substring(1).split(""); | |
218 for (var shortOption in shortOptions) { | |
219 switch (shortOption) { | |
220 case 'v': | |
221 setVerbose(null); | |
222 break; | |
223 case 'h': | |
224 case '?': | |
225 wantHelp = true; | |
226 break; | |
227 case 'c': | |
228 passThrough('--enable-checked-mode'); | |
229 break; | |
230 default: | |
231 throw 'Internal error: "$shortOption" did not match'; | |
232 } | |
233 } | |
234 } | |
235 | |
236 Uri computePrecompiledUri() { | |
237 String extension = 'precompiled.js'; | |
238 String outPath = out.path; | |
239 if (outPath.endsWith('.js')) { | |
240 outPath = outPath.substring(0, outPath.length - 3); | |
241 return out.resolve('$outPath.$extension'); | |
242 } else { | |
243 return out.resolve(extension); | |
244 } | |
245 } | |
246 | |
247 List<String> arguments = <String>[]; | |
248 List<OptionHandler> handlers = <OptionHandler>[ | |
249 new OptionHandler('-[chv?]+', handleShortOptions), | |
250 new OptionHandler('--throw-on-error', | |
251 (_) => diagnosticHandler.throwOnError = true), | |
252 new OptionHandler('--suppress-warnings', | |
253 (_) => diagnosticHandler.showWarnings = false), | |
254 new OptionHandler('--suppress-hints', | |
255 (_) => diagnosticHandler.showHints = false), | |
256 new OptionHandler('--output-type=dart|--output-type=js', setOutputType), | |
257 new OptionHandler('--verbose', setVerbose), | |
258 new OptionHandler('--version', (_) => wantVersion = true), | |
259 new OptionHandler('--library-root=.+', setLibraryRoot), | |
260 new OptionHandler('--out=.+|-o.*', setOutput, multipleArguments: true), | |
261 new OptionHandler('--allow-mock-compilation', passThrough), | |
262 new OptionHandler('--minify', passThrough), | |
263 new OptionHandler('--force-strip=.*', setStrip), | |
264 new OptionHandler('--disable-diagnostic-colors', | |
265 (_) => diagnosticHandler.enableColors = false), | |
266 new OptionHandler('--enable-diagnostic-colors', | |
267 (_) => diagnosticHandler.enableColors = true), | |
268 new OptionHandler('--enable[_-]checked[_-]mode|--checked', | |
269 (_) => passThrough('--enable-checked-mode')), | |
270 new OptionHandler('--enable-concrete-type-inference', | |
271 (_) => passThrough('--enable-concrete-type-inference')), | |
272 new OptionHandler('--trust-type-annotations', | |
273 (_) => passThrough('--trust-type-annotations')), | |
274 new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true), | |
275 new OptionHandler('--package-root=.+|-p.+', setPackageRoot), | |
276 new OptionHandler('--analyze-all', passThrough), | |
277 new OptionHandler('--analyze-only', setAnalyzeOnly), | |
278 new OptionHandler('--analyze-signatures-only', passThrough), | |
279 new OptionHandler('--disable-native-live-type-analysis', passThrough), | |
280 new OptionHandler('--categories=.*', setCategories), | |
281 new OptionHandler('--global-js-name=.*', checkGlobalName), | |
282 new OptionHandler('--disable-type-inference', passThrough), | |
283 new OptionHandler('--terse', passThrough), | |
284 new OptionHandler('--disallow-unsafe-eval', | |
285 (_) => hasDisallowUnsafeEval = true), | |
286 new OptionHandler('-D.+=.*', addInEnvironment), | |
287 | |
288 // The following two options must come last. | |
289 new OptionHandler('-.*', (String argument) { | |
290 helpAndFail('Error: Unknown option "$argument".'); | |
291 }), | |
292 new OptionHandler('.*', (String argument) { | |
293 arguments.add(nativeToUriPath(argument)); | |
294 }) | |
295 ]; | |
296 | |
297 parseCommandLine(handlers, argv); | |
298 if (wantHelp || wantVersion) { | |
299 helpAndExit(wantHelp, wantVersion, diagnosticHandler.verbose); | |
300 } | |
301 | |
302 if (hasDisallowUnsafeEval) { | |
303 String precompiledName = | |
304 relativize(currentDirectory, computePrecompiledUri(), isWindows); | |
305 helpAndFail("Error: option '--disallow-unsafe-eval' has been removed." | |
306 " Instead, the compiler generates a file named" | |
307 " '$precompiledName'."); | |
308 } | |
309 | |
310 if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) { | |
311 helpAndFail('Error: --force-strip may only be used with ' | |
312 '--output-type=dart'); | |
313 } | |
314 if (arguments.isEmpty) { | |
315 helpAndFail('Error: No Dart file specified.'); | |
316 } | |
317 if (arguments.length > 1) { | |
318 var extra = arguments.sublist(1); | |
319 helpAndFail('Error: Extra arguments: ${extra.join(" ")}'); | |
320 } | |
321 | |
322 Uri uri = currentDirectory.resolve(arguments[0]); | |
323 if (packageRoot == null) { | |
324 packageRoot = uri.resolve('./packages/'); | |
325 } | |
326 | |
327 diagnosticHandler.info('package root is $packageRoot'); | |
328 | |
329 int totalCharactersWritten = 0; | |
330 | |
331 options.add('--source-map=$sourceMapOut'); | |
332 | |
333 compilationDone(String code) { | |
334 if (analyzeOnly) return; | |
335 if (code == null) { | |
336 fail('Error: Compilation failed.'); | |
337 } | |
338 writeString(Uri.parse('$out.deps'), | |
339 getDepsOutput(inputProvider.sourceFiles)); | |
340 diagnosticHandler.info( | |
341 'compiled ${inputProvider.dartCharactersRead} characters Dart ' | |
342 '-> $totalCharactersWritten characters $outputLanguage ' | |
343 'in ${relativize(currentDirectory, out, isWindows)}'); | |
344 if (!explicitOut) { | |
345 String input = uriPathToNative(arguments[0]); | |
346 String output = relativize(currentDirectory, out, isWindows); | |
347 print('Dart file ($input) compiled to $outputLanguage: $output'); | |
348 } | |
349 } | |
350 | |
351 EventSink<String> outputProvider(String name, String extension) { | |
352 Uri uri; | |
353 String sourceMapFileName; | |
354 bool isPrimaryOutput = false; | |
355 if (name == '') { | |
356 if (extension == 'js' || extension == 'dart') { | |
357 isPrimaryOutput = true; | |
358 uri = out; | |
359 sourceMapFileName = | |
360 sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1); | |
361 } else if (extension == 'precompiled.js') { | |
362 uri = computePrecompiledUri(); | |
363 diagnosticHandler.info( | |
364 "File ($uri) is compatible with header" | |
365 " \"Content-Security-Policy: script-src 'self'\""); | |
366 } else if (extension == 'js.map' || extension == 'dart.map') { | |
367 uri = sourceMapOut; | |
368 } else { | |
369 fail('Error: Unknown extension: $extension'); | |
370 } | |
371 } else { | |
372 uri = out.resolve('$name.$extension'); | |
373 } | |
374 | |
375 if (uri.scheme != 'file') { | |
376 fail('Error: Unhandled scheme ${uri.scheme} in $uri.'); | |
377 } | |
378 | |
379 RandomAccessFile output = | |
380 new File(uriPathToNative(uri.path)).openSync(mode: FileMode.WRITE); | |
381 int charactersWritten = 0; | |
382 | |
383 writeStringSync(String data) { | |
384 // Write the data in chunks of 8kb, otherwise we risk running OOM | |
385 int chunkSize = 8*1024; | |
386 | |
387 int offset = 0; | |
388 while (offset < data.length) { | |
389 output.writeStringSync( | |
390 data.substring(offset, math.min(offset + chunkSize, data.length))); | |
391 offset += chunkSize; | |
392 } | |
393 charactersWritten += data.length; | |
394 } | |
395 | |
396 onDone() { | |
397 if (sourceMapFileName != null) { | |
398 // Using # is the new proposed standard. @ caused problems in Internet | |
399 // Explorer due to "Conditional Compilation Statements" in JScript, | |
400 // see: | |
401 // http://msdn.microsoft.com/en-us/library/7kx09ct1(v=vs.80).aspx | |
402 // About source maps, see: | |
403 // https://docs.google.com/a/google.com/document/d/1U1RGAehQwRypUTovF1KR lpiOFze0b-_2gc6fAH0KY0k/edit | |
404 // TODO(http://dartbug.com/11914): Remove @ line. | |
405 String sourceMapTag = ''' | |
406 | |
407 //# sourceMappingURL=$sourceMapFileName | |
408 //@ sourceMappingURL=$sourceMapFileName | |
409 '''; | |
410 writeStringSync(sourceMapTag); | |
411 } | |
412 output.closeSync(); | |
413 if (isPrimaryOutput) { | |
414 totalCharactersWritten += charactersWritten; | |
415 } | |
416 } | |
417 | |
418 return new EventSinkWrapper(writeStringSync, onDone); | |
419 } | |
420 | |
421 return api.compile(uri, libraryRoot, packageRoot, | |
422 inputProvider, diagnosticHandler, | |
423 options, outputProvider, environment) | |
424 .then(compilationDone); | |
425 } | |
426 | |
427 class EventSinkWrapper extends EventSink<String> { | 483 class EventSinkWrapper extends EventSink<String> { |
428 var onAdd, onClose; | 484 var onAdd, onClose; |
429 | 485 |
430 EventSinkWrapper(this.onAdd, this.onClose); | 486 EventSinkWrapper(this.onAdd, this.onClose); |
431 | 487 |
432 void add(String data) => onAdd(data); | 488 void add(String data) => onAdd(data); |
433 | 489 |
434 void addError(error, [StackTrace stackTrace]) => throw error; | 490 void addError(error, [StackTrace stackTrace]) => throw error; |
435 | 491 |
436 void close() => onClose(); | 492 void close() => onClose(); |
437 } | 493 } |
438 | 494 |
439 class AbortLeg { | 495 class AbortLeg { |
440 final message; | 496 final message; |
441 AbortLeg(this.message); | 497 AbortLeg(this.message); |
442 toString() => 'Aborted due to --throw-on-error: $message'; | 498 toString() => 'Aborted due to --throw-on-error: $message'; |
443 } | 499 } |
444 | 500 |
445 void writeString(Uri uri, String text) { | |
446 if (uri.scheme != 'file') { | |
447 fail('Error: Unhandled scheme ${uri.scheme}.'); | |
448 } | |
449 var file = new File(uriPathToNative(uri.path)).openSync(mode: FileMode.WRITE); | |
450 file.writeStringSync(text); | |
451 file.closeSync(); | |
452 } | |
453 | |
454 void fail(String message) { | |
455 if (diagnosticHandler != null) { | |
456 diagnosticHandler.diagnosticHandler( | |
457 null, -1, -1, message, api.Diagnostic.ERROR); | |
458 } else { | |
459 print(message); | |
460 } | |
461 exit(1); | |
462 } | |
463 | |
464 Future compilerMain(List<String> arguments) { | |
465 var root = uriPathToNative("/$LIBRARY_ROOT"); | |
466 arguments = <String>['--library-root=${Platform.script.toFilePath()}$root'] | |
467 ..addAll(arguments); | |
468 return compile(arguments); | |
469 } | |
470 | |
471 void help() { | 501 void help() { |
472 // This message should be no longer than 20 lines. The default | 502 // This message should be no longer than 20 lines. The default |
473 // terminal size normally 80x24. Two lines are used for the prompts | 503 // terminal size normally 80x24. Two lines are used for the prompts |
474 // before and after running the compiler. Another two lines may be | 504 // before and after running the compiler. Another two lines may be |
475 // used to print an error message. | 505 // used to print an error message. |
476 print(''' | 506 print(''' |
477 Usage: dart2js [options] dartfile | 507 Usage: dart2js [options] dartfile |
478 | 508 |
479 Compiles Dart to JavaScript. | 509 Compiles Dart to JavaScript. |
480 | 510 |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
571 all categories, use --categories=all. | 601 all categories, use --categories=all. |
572 | 602 |
573 --global-js-name=<name> | 603 --global-js-name=<name> |
574 By default, dart2js generates JavaScript output that uses a global | 604 By default, dart2js generates JavaScript output that uses a global |
575 variable named "$". The name of this global can be overridden | 605 variable named "$". The name of this global can be overridden |
576 with this option. The name must match the regular expression "\$[a-z]*". | 606 with this option. The name must match the regular expression "\$[a-z]*". |
577 | 607 |
578 '''.trim()); | 608 '''.trim()); |
579 } | 609 } |
580 | 610 |
581 void helpAndExit(bool wantHelp, bool wantVersion, bool verbose) { | 611 Future internalMain(Function exitFunc, Function compileFunc, |
582 if (wantVersion) { | 612 List<String> arguments) { |
583 var version = (BUILD_ID == null) | 613 var dart2js = new _Dart2Js(exitFunc, compileFunc); |
584 ? '<non-SDK build>' | 614 |
585 : BUILD_ID; | 615 onError(exception, trace) { |
586 print('Dart-to-JavaScript compiler (dart2js) version: $version'); | 616 if (exception is CompilerCrashedException) { |
ahe
2013/11/21 12:44:53
This looks like a break down of the API to me.
Co
| |
587 } | 617 // Error and stack trace already reported. |
588 if (wantHelp) { | 618 exitFunc(253); // 253 is recognized as a crash by our test scripts. |
589 if (verbose) { | |
590 verboseHelp(); | |
591 } else { | 619 } else { |
592 help(); | 620 try { |
621 print('Internal error: $exception'); | |
622 } catch (ignored) { | |
623 print('Internal error: error while printing exception'); | |
624 } | |
625 | |
626 try { | |
627 if (trace != null) { | |
628 print(trace); | |
629 } | |
630 } finally { | |
631 exitFunc(253); // 253 is recognized as a crash by our test scripts. | |
632 } | |
593 } | 633 } |
594 } | 634 } |
595 exit(0); | |
596 } | |
597 | 635 |
598 void helpAndFail(String message) { | 636 try { |
599 help(); | 637 return dart2js.compilerMain(arguments).catchError(onError); |
600 print(''); | 638 } catch (exception, trace) { |
601 fail(message); | 639 onError(exception, trace); |
640 return new Future.value(); | |
641 } | |
602 } | 642 } |
603 | 643 |
604 void main(List<String> arguments) { | 644 void main(List<String> arguments) { |
605 runZoned(() => compilerMain(arguments), onError: (exception, trace) { | 645 internalMain(exit, api.compile, arguments); |
606 try { | |
607 print('Internal error: $exception'); | |
608 } catch (ignored) { | |
609 print('Internal error: error while printing exception'); | |
610 } | |
611 | |
612 try { | |
613 if (trace != null) { | |
614 print(trace); | |
615 } | |
616 } finally { | |
617 exit(253); // 253 is recognized as a crash by our test scripts. | |
618 } | |
619 }); | |
620 } | 646 } |
OLD | NEW |