OLD | NEW |
| (Empty) |
1 #!/usr/bin/env dart | |
2 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
3 // for details. All rights reserved. Use of this source code is governed by a | |
4 // BSD-style license that can be found in the LICENSE file. | |
5 | |
6 import 'dart:async'; | |
7 import 'dart:io'; | |
8 | |
9 import 'batch_util.dart'; | |
10 import 'util.dart'; | |
11 | |
12 import 'package:args/args.dart'; | |
13 import 'package:analyzer/src/kernel/loader.dart'; | |
14 import 'package:kernel/application_root.dart'; | |
15 import 'package:kernel/verifier.dart'; | |
16 import 'package:kernel/kernel.dart'; | |
17 import 'package:kernel/log.dart'; | |
18 import 'package:kernel/target/targets.dart'; | |
19 import 'package:kernel/transformations/treeshaker.dart'; | |
20 import 'package:path/path.dart' as path; | |
21 | |
22 // Returns the path to the current sdk based on `Platform.resolvedExecutable`. | |
23 String currentSdk() { | |
24 // The dart executable should be inside dart-sdk/bin/dart. | |
25 return path.dirname(path.dirname(path.absolute(Platform.resolvedExecutable))); | |
26 } | |
27 | |
28 ArgParser parser = new ArgParser(allowTrailingOptions: true) | |
29 ..addOption('format', | |
30 abbr: 'f', | |
31 allowed: ['text', 'bin'], | |
32 help: 'Output format.\n' | |
33 '(defaults to "text" unless output file ends with ".dill")') | |
34 ..addOption('out', | |
35 abbr: 'o', | |
36 help: 'Output file.\n' | |
37 '(defaults to "out.dill" if format is "bin", otherwise stdout)') | |
38 ..addOption('sdk', defaultsTo: currentSdk(), help: 'Path to the Dart SDK.') | |
39 ..addOption('packages', | |
40 abbr: 'p', help: 'Path to the .packages file or packages folder.') | |
41 ..addOption('package-root', help: 'Deprecated alias for --packages') | |
42 ..addOption('app-root', | |
43 help: 'Store library paths relative to the given directory.\n' | |
44 'If none is given, absolute paths are used.\n' | |
45 'Application libraries not inside the application root are stored ' | |
46 'using absolute paths') | |
47 ..addOption('target', | |
48 abbr: 't', | |
49 help: 'Tailor the IR to the given target.', | |
50 allowed: targetNames, | |
51 defaultsTo: 'vm') | |
52 ..addFlag('strong', | |
53 help: 'Load .dart files in strong mode.\n' | |
54 'Does not affect loading of binary files. Strong mode support is very\
n' | |
55 'unstable and not well integrated yet.') | |
56 ..addFlag('link', abbr: 'l', help: 'Link the whole program into one file.') | |
57 ..addFlag('no-output', negatable: false, help: 'Do not output any files.') | |
58 ..addOption('url-mapping', | |
59 allowMultiple: true, | |
60 help: 'A custom url mapping of the form `<scheme>:<name>::<uri>`.') | |
61 ..addOption('embedder-entry-points-manifest', | |
62 allowMultiple: true, | |
63 help: 'A path to a file describing entrypoints ' | |
64 '(lines of the form `<library>,<class>,<member>`).') | |
65 ..addFlag('verbose', | |
66 abbr: 'v', | |
67 negatable: false, | |
68 help: 'Print internal warnings and diagnostics to stderr.') | |
69 ..addFlag('print-metrics', | |
70 negatable: false, help: 'Print performance metrics.') | |
71 ..addFlag('verify-ir', help: 'Perform slow internal correctness checks.') | |
72 ..addFlag('tolerant', | |
73 help: 'Generate kernel even if there are compile-time errors.', | |
74 defaultsTo: false) | |
75 ..addOption('D', | |
76 abbr: 'D', | |
77 allowMultiple: true, | |
78 help: 'Define an environment variable.', | |
79 hide: true) | |
80 ..addFlag('show-external', | |
81 help: 'When printing a library as text, also print its dependencies\n' | |
82 'on external libraries.') | |
83 ..addFlag('show-offsets', | |
84 help: 'When printing a library as text, also print node offsets') | |
85 ..addFlag('include-sdk', | |
86 help: 'Include the SDK in the output. Implied by --link.') | |
87 ..addFlag('tree-shake', | |
88 defaultsTo: false, help: 'Enable tree-shaking if the target supports it'); | |
89 | |
90 String getUsage() => """ | |
91 Usage: dartk [options] FILE | |
92 | |
93 Convert .dart or .dill files to kernel's IR and print out its textual | |
94 or binary form. | |
95 | |
96 Examples: | |
97 dartk foo.dart # print text IR for foo.dart | |
98 dartk foo.dart -ofoo.dill # write binary IR for foo.dart to foo.dill | |
99 dartk foo.dill # print text IR for binary file foo.dill | |
100 | |
101 Options: | |
102 ${parser.usage} | |
103 | |
104 -D<name>=<value> Define an environment variable. | |
105 """; | |
106 | |
107 dynamic fail(String message) { | |
108 stderr.writeln(message); | |
109 exit(1); | |
110 return null; | |
111 } | |
112 | |
113 ArgResults options; | |
114 | |
115 String defaultFormat() { | |
116 if (options['out'] != null && options['out'].endsWith('.dill')) { | |
117 return 'bin'; | |
118 } | |
119 return 'text'; | |
120 } | |
121 | |
122 String defaultOutput() { | |
123 if (options['format'] == 'bin') { | |
124 return 'out.dill'; | |
125 } | |
126 return null; | |
127 } | |
128 | |
129 void checkIsDirectoryOrNull(String path, String option) { | |
130 if (path == null) return; | |
131 var stat = new File(path).statSync(); | |
132 switch (stat.type) { | |
133 case FileSystemEntityType.DIRECTORY: | |
134 case FileSystemEntityType.LINK: | |
135 return; | |
136 case FileSystemEntityType.NOT_FOUND: | |
137 throw fail('$option not found: $path'); | |
138 default: | |
139 fail('$option is not a directory: $path'); | |
140 } | |
141 } | |
142 | |
143 void checkIsFile(String path, {String option}) { | |
144 var stat = new File(path).statSync(); | |
145 switch (stat.type) { | |
146 case FileSystemEntityType.DIRECTORY: | |
147 throw fail('$option is a directory: $path'); | |
148 | |
149 case FileSystemEntityType.NOT_FOUND: | |
150 throw fail('$option not found: $path'); | |
151 } | |
152 } | |
153 | |
154 void checkIsFileOrDirectoryOrNull(String path, String option) { | |
155 if (path == null) return; | |
156 var stat = new File(path).statSync(); | |
157 if (stat.type == FileSystemEntityType.NOT_FOUND) { | |
158 fail('$option not found: $path'); | |
159 } | |
160 } | |
161 | |
162 int getTotalSourceSize(List<String> files) { | |
163 int size = 0; | |
164 for (var filename in files) { | |
165 size += new File(filename).statSync().size; | |
166 } | |
167 return size; | |
168 } | |
169 | |
170 bool get shouldReportMetrics => options['print-metrics']; | |
171 | |
172 void dumpString(String value, [String filename]) { | |
173 if (filename == null) { | |
174 print(value); | |
175 } else { | |
176 new File(filename).writeAsStringSync(value); | |
177 } | |
178 } | |
179 | |
180 Map<Uri, Uri> parseCustomUriMappings(List<String> mappings) { | |
181 Map<Uri, Uri> customUriMappings = <Uri, Uri>{}; | |
182 | |
183 fatal(String mapping) { | |
184 fail('Invalid uri mapping "$mapping". Each mapping should have the ' | |
185 'form "<scheme>:<name>::<uri>".'); | |
186 } | |
187 | |
188 // Each mapping has the form <uri>::<uri>. | |
189 for (var mapping in mappings) { | |
190 List<String> parts = mapping.split('::'); | |
191 if (parts.length != 2) { | |
192 fatal(mapping); | |
193 } | |
194 Uri fromUri = Uri.parse(parts[0]); | |
195 if (fromUri.scheme == '' || fromUri.path.contains('/')) { | |
196 fatal(mapping); | |
197 } | |
198 Uri toUri = Uri.parse(parts[1]); | |
199 if (toUri.scheme == '') { | |
200 toUri = new Uri.file(path.absolute(parts[1])); | |
201 } | |
202 customUriMappings[fromUri] = toUri; | |
203 } | |
204 | |
205 return customUriMappings; | |
206 } | |
207 | |
208 /// Maintains state that should be shared between batched executions when | |
209 /// running in batch mode (for testing purposes). | |
210 /// | |
211 /// This reuses the analyzer's in-memory copy of the Dart SDK between runs. | |
212 class BatchModeState { | |
213 bool isBatchMode = false; | |
214 DartLoaderBatch batch = new DartLoaderBatch(); | |
215 } | |
216 | |
217 main(List<String> args) async { | |
218 if (args.isNotEmpty && args[0] == '--batch') { | |
219 if (args.length != 1) { | |
220 return fail('--batch cannot be used with other arguments'); | |
221 } | |
222 var batchModeState = new BatchModeState()..isBatchMode = true; | |
223 await runBatch((args) => batchMain(args, batchModeState)); | |
224 } else { | |
225 CompilerOutcome outcome = await batchMain(args, new BatchModeState()); | |
226 exit(outcome == CompilerOutcome.Ok ? 0 : 1); | |
227 } | |
228 } | |
229 | |
230 bool isSupportedArgument(String arg) { | |
231 if (arg.startsWith('--')) { | |
232 int equals = arg.indexOf('='); | |
233 var name = equals != -1 ? arg.substring(2, equals) : arg.substring(2); | |
234 return parser.options.containsKey(name); | |
235 } | |
236 if (arg.startsWith('-')) { | |
237 return parser.findByAbbreviation(arg.substring(1)) != null; | |
238 } | |
239 return true; | |
240 } | |
241 | |
242 Future<CompilerOutcome> batchMain( | |
243 List<String> args, BatchModeState batchModeState) async { | |
244 if (args.contains('--ignore-unrecognized-flags')) { | |
245 args = args.where(isSupportedArgument).toList(); | |
246 } | |
247 | |
248 if (args.isEmpty) { | |
249 return fail(getUsage()); | |
250 } | |
251 | |
252 try { | |
253 options = parser.parse(args); | |
254 } on FormatException catch (e) { | |
255 return fail(e.message); // Don't puke stack traces. | |
256 } | |
257 | |
258 checkIsDirectoryOrNull(options['sdk'], 'Dart SDK'); | |
259 | |
260 String packagePath = options['packages'] ?? options['package-root']; | |
261 checkIsFileOrDirectoryOrNull(packagePath, 'Package root or .packages'); | |
262 | |
263 String applicationRootOption = options['app-root']; | |
264 checkIsDirectoryOrNull(applicationRootOption, 'Application root'); | |
265 if (applicationRootOption != null) { | |
266 applicationRootOption = new File(applicationRootOption).absolute.path; | |
267 } | |
268 var applicationRoot = new ApplicationRoot(applicationRootOption); | |
269 | |
270 // Set up logging. | |
271 if (options['verbose']) { | |
272 log.onRecord.listen((LogRecord rec) { | |
273 stderr.writeln(rec.message); | |
274 }); | |
275 } | |
276 | |
277 bool includeSdk = options['include-sdk']; | |
278 | |
279 List<String> inputFiles = options.rest; | |
280 if (inputFiles.length < 1 && !includeSdk) { | |
281 return fail('At least one file should be given.'); | |
282 } | |
283 | |
284 bool hasBinaryInput = false; | |
285 bool hasDartInput = includeSdk; | |
286 for (String file in inputFiles) { | |
287 checkIsFile(file, option: 'Input file'); | |
288 if (file.endsWith('.dill')) { | |
289 hasBinaryInput = true; | |
290 } else if (file.endsWith('.dart')) { | |
291 hasDartInput = true; | |
292 } else { | |
293 fail('Unrecognized file extension: $file'); | |
294 } | |
295 } | |
296 | |
297 if (hasBinaryInput && hasDartInput) { | |
298 fail('Mixed binary and dart input is not currently supported'); | |
299 } | |
300 | |
301 String format = options['format'] ?? defaultFormat(); | |
302 String outputFile = options['out'] ?? defaultOutput(); | |
303 | |
304 List<String> urlMapping = options['url-mapping'] as List<String>; | |
305 var customUriMappings = parseCustomUriMappings(urlMapping); | |
306 | |
307 List<String> embedderEntryPointManifests = | |
308 options['embedder-entry-points-manifest'] as List<String>; | |
309 List<ProgramRoot> programRoots = | |
310 parseProgramRoots(embedderEntryPointManifests); | |
311 | |
312 var program = new Program(); | |
313 | |
314 var watch = new Stopwatch()..start(); | |
315 List errors = const []; | |
316 TargetFlags targetFlags = new TargetFlags( | |
317 strongMode: options['strong'], | |
318 treeShake: options['tree-shake'], | |
319 kernelRuntime: Platform.script.resolve('../runtime/'), | |
320 programRoots: programRoots); | |
321 Target target = getTarget(options['target'], targetFlags); | |
322 | |
323 var declaredVariables = <String, String>{}; | |
324 declaredVariables.addAll(target.extraDeclaredVariables); | |
325 for (String define in options['D']) { | |
326 int separator = define.indexOf('='); | |
327 if (separator == -1) { | |
328 fail('Invalid define: -D$define. Format is -D<name>=<value>'); | |
329 } | |
330 String name = define.substring(0, separator); | |
331 String value = define.substring(separator + 1); | |
332 declaredVariables[name] = value; | |
333 } | |
334 | |
335 DartLoader loader; | |
336 if (hasDartInput) { | |
337 String packageDiscoveryPath = | |
338 batchModeState.isBatchMode || inputFiles.isEmpty | |
339 ? null | |
340 : inputFiles.first; | |
341 loader = await batchModeState.batch.getLoader( | |
342 program, | |
343 new DartOptions( | |
344 strongMode: target.strongMode, | |
345 strongModeSdk: target.strongModeSdk, | |
346 sdk: options['sdk'], | |
347 packagePath: packagePath, | |
348 customUriMappings: customUriMappings, | |
349 declaredVariables: declaredVariables, | |
350 applicationRoot: applicationRoot), | |
351 packageDiscoveryPath: packageDiscoveryPath); | |
352 if (includeSdk) { | |
353 for (var uri in batchModeState.batch.dartSdk.uris) { | |
354 loader.loadLibrary(Uri.parse(uri)); | |
355 } | |
356 } | |
357 loader.loadSdkInterface(program, target); | |
358 } | |
359 | |
360 for (String file in inputFiles) { | |
361 Uri fileUri = Uri.base.resolve(file); | |
362 | |
363 if (file.endsWith('.dill')) { | |
364 loadProgramFromBinary(file, program); | |
365 } else { | |
366 if (options['link']) { | |
367 loader.loadProgram(fileUri, target: target); | |
368 } else { | |
369 var library = loader.loadLibrary(fileUri); | |
370 program.mainMethod ??= library.procedures | |
371 .firstWhere((p) => p.name.name == 'main', orElse: () => null); | |
372 } | |
373 errors = loader.errors; | |
374 if (errors.isNotEmpty) { | |
375 const int errorLimit = 100; | |
376 stderr.writeln(errors.take(errorLimit).join('\n')); | |
377 if (errors.length > errorLimit) { | |
378 stderr.writeln( | |
379 '[error] ${errors.length - errorLimit} errors not shown'); | |
380 } | |
381 } | |
382 } | |
383 } | |
384 | |
385 bool canContinueCompilation = errors.isEmpty || options['tolerant']; | |
386 | |
387 int loadTime = watch.elapsedMilliseconds; | |
388 if (shouldReportMetrics) { | |
389 print('loader.time = $loadTime ms'); | |
390 } | |
391 | |
392 void runVerifier() { | |
393 if (options['verify-ir']) { | |
394 verifyProgram(program); | |
395 } | |
396 } | |
397 | |
398 if (canContinueCompilation) { | |
399 runVerifier(); | |
400 } | |
401 | |
402 if (options['link'] && program.mainMethodName == null) { | |
403 fail('[error] The program has no main method.'); | |
404 } | |
405 | |
406 // Apply target-specific transformations. | |
407 if (target != null && canContinueCompilation) { | |
408 target.performModularTransformations(program); | |
409 runVerifier(); | |
410 if (options['link']) { | |
411 target.performGlobalTransformations(program); | |
412 runVerifier(); | |
413 } | |
414 } | |
415 | |
416 if (options['no-output']) { | |
417 return CompilerOutcome.Ok; | |
418 } | |
419 | |
420 watch.reset(); | |
421 | |
422 Future ioFuture; | |
423 if (canContinueCompilation) { | |
424 switch (format) { | |
425 case 'text': | |
426 writeProgramToText(program, | |
427 path: outputFile, | |
428 showExternal: options['show-external'], | |
429 showOffsets: options['show-offsets']); | |
430 break; | |
431 case 'bin': | |
432 ioFuture = writeProgramToBinary(program, outputFile); | |
433 break; | |
434 } | |
435 } | |
436 | |
437 int time = watch.elapsedMilliseconds; | |
438 if (shouldReportMetrics) { | |
439 print('writer.time = $time ms'); | |
440 } | |
441 | |
442 await ioFuture; | |
443 | |
444 if (shouldReportMetrics) { | |
445 int flushTime = watch.elapsedMilliseconds - time; | |
446 print('writer.flush_time = $flushTime ms'); | |
447 } | |
448 | |
449 if (options['tolerant']) { | |
450 return CompilerOutcome.Ok; | |
451 } | |
452 | |
453 return errors.length > 0 ? CompilerOutcome.Fail : CompilerOutcome.Ok; | |
454 } | |
OLD | NEW |