| 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 |