blob: e735f9789e5a18560c3dd1c4cca2a6f8b418486a [file] [log] [blame]
#!/usr/bin/env dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'batch_util.dart';
import 'package:args/args.dart';
import 'package:kernel/analyzer/loader.dart';
import 'package:kernel/verifier.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/log.dart';
import 'package:kernel/target/targets.dart';
import 'package:path/path.dart' as path;
// Returns the path to the current sdk based on `Platform.resolvedExecutable`.
String currentSdk() {
// The dart executable should be inside dart-sdk/bin/dart.
return path.dirname(path.dirname(path.absolute(Platform.resolvedExecutable)));
}
ArgParser parser = new ArgParser(allowTrailingOptions: true)
..addOption('format',
abbr: 'f',
allowed: ['text', 'bin'],
help: 'Output format.\n'
'(defaults to "text" unless output file ends with ".dill")')
..addOption('out',
abbr: 'o',
help: 'Output file.\n'
'(defaults to "out.dill" if format is "bin", otherwise stdout)')
..addOption('sdk', defaultsTo: currentSdk(), help: 'Path to the Dart SDK.')
..addOption('packages',
abbr: 'p', help: 'Path to the .packages file or packages folder.')
..addOption('package-root', help: 'Deprecated alias for --packages')
..addOption('target',
abbr: 't',
help: 'Tailor the IR to the given target.',
allowed: targetNames,
defaultsTo: 'vm')
..addFlag('strong',
help: 'Load .dart files in strong mode.\n'
'Does not affect loading of binary files. Strong mode support is very\n'
'unstable and not well integrated yet.')
..addFlag('link', abbr: 'l', help: 'Link the whole program into one file.')
..addFlag('no-output', negatable: false, help: 'Do not output any files.')
..addOption('url-mapping',
allowMultiple: true,
help: 'A custom url mapping of the form `<scheme>:<name>::<uri>`.')
..addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Print internal warnings and diagnostics to stderr.')
..addFlag('print-metrics',
negatable: false, help: 'Print performance metrics.')
..addOption('write-dependencies',
help: 'Write all the .dart that were loaded to the given file.')
..addFlag('verify-ir', help: 'Perform slow internal correctness checks.')
..addFlag('tolerant',
help: 'Generate kernel even if there are compile-time errors.',
defaultsTo: false)
..addOption('D',
abbr: 'D',
allowMultiple: true,
help: 'Define an environment variable.',
hide: true)
..addFlag('show-external',
help: 'When printing a library as text, also print its dependencies\n'
'on external libraries.');
String getUsage() => """
Usage: dartk [options] FILE
Convert .dart or .dill files to kernel's IR and print out its textual
or binary form.
Examples:
dartk foo.dart # print text IR for foo.dart
dartk foo.dart -ofoo.dill # write binary IR for foo.dart to foo.dill
dartk foo.dill # print text IR for binary file foo.dill
Options:
${parser.usage}
-D<name>=<value> Define an environment variable.
""";
dynamic fail(String message) {
stderr.writeln(message);
exit(1);
return null;
}
ArgResults options;
String defaultFormat() {
if (options['out'] != null && options['out'].endsWith('.dill')) {
return 'bin';
}
return 'text';
}
String defaultOutput() {
if (options['format'] == 'bin') {
return 'out.dill';
}
return null;
}
void checkIsDirectoryOrNull(String path, String option) {
if (path == null) return;
var stat = new File(path).statSync();
switch (stat.type) {
case FileSystemEntityType.DIRECTORY:
case FileSystemEntityType.LINK:
return;
case FileSystemEntityType.NOT_FOUND:
throw fail('$option not found: $path');
default:
fail('$option is not a directory: $path');
}
}
void checkIsFile(String path, {String option}) {
var stat = new File(path).statSync();
switch (stat.type) {
case FileSystemEntityType.DIRECTORY:
throw fail('$option is a directory: $path');
case FileSystemEntityType.NOT_FOUND:
throw fail('$option not found: $path');
}
}
void checkIsFileOrDirectoryOrNull(String path, String option) {
if (path == null) return;
var stat = new File(path).statSync();
if (stat.type == FileSystemEntityType.NOT_FOUND) {
fail('$option not found: $path');
}
}
int getTotalSourceSize(List<String> files) {
int size = 0;
for (var filename in files) {
size += new File(filename).statSync().size;
}
return size;
}
bool get shouldReportMetrics => options['print-metrics'];
void dumpString(String value, [String filename]) {
if (filename == null) {
print(value);
} else {
new File(filename).writeAsStringSync(value);
}
}
Map<Uri, Uri> parseCustomUriMappings(List<String> mappings) {
Map<Uri, Uri> customUriMappings = <Uri, Uri>{};
fatal(String mapping) {
fail('Invalid uri mapping "$mapping". Each mapping should have the '
'form "<scheme>:<name>::<uri>".');
}
// Each mapping has the form <uri>::<uri>.
for (var mapping in mappings) {
List<String> parts = mapping.split('::');
if (parts.length != 2) {
fatal(mapping);
}
Uri fromUri = Uri.parse(parts[0]);
if (fromUri.scheme == '' || fromUri.path.contains('/')) {
fatal(mapping);
}
Uri toUri = Uri.parse(parts[1]);
if (toUri.scheme == '') {
toUri = new Uri.file(path.absolute(parts[1]));
}
customUriMappings[fromUri] = toUri;
}
return customUriMappings;
}
/// Maintains state that should be shared between batched executions when
/// running in batch mode (for testing purposes).
///
/// This reuses the analyzer's in-memory copy of the Dart SDK between runs.
class BatchModeState {
bool isBatchMode = false;
DartLoaderBatch batch = new DartLoaderBatch();
}
main(List<String> args) async {
if (args.isNotEmpty && args[0] == '--batch') {
if (args.length != 1) {
return fail('--batch cannot be used with other arguments');
}
var batchModeState = new BatchModeState()..isBatchMode = true;
await runBatch((args) => batchMain(args, batchModeState));
} else {
CompilerOutcome outcome = await batchMain(args, new BatchModeState());
exit(outcome == CompilerOutcome.Ok ? 0 : 1);
}
}
bool isSupportedArgument(String arg) {
if (arg.startsWith('--')) {
int equals = arg.indexOf('=');
var name = equals != -1 ? arg.substring(2, equals) : arg.substring(2);
return parser.options.containsKey(name);
}
if (arg.startsWith('-')) {
return parser.findByAbbreviation(arg.substring(1)) != null;
}
return true;
}
Future<CompilerOutcome> batchMain(
List<String> args, BatchModeState batchModeState) async {
if (args.contains('--ignore-unrecognized-flags')) {
args = args.where(isSupportedArgument).toList();
}
if (args.isEmpty) {
return fail(getUsage());
}
try {
options = parser.parse(args);
} on FormatException catch (e) {
return fail(e.message); // Don't puke stack traces.
}
checkIsDirectoryOrNull(options['sdk'], 'Dart SDK');
String packagePath = options['packages'] ?? options['package-root'];
checkIsFileOrDirectoryOrNull(packagePath, 'Package root or .packages');
// Set up logging.
if (options['verbose']) {
log.onRecord.listen((LogRecord rec) {
stderr.writeln(rec.message);
});
}
if (options.rest.length != 1) {
return fail('Exactly one FILE should be given.');
}
var file = options.rest.single;
checkIsFile(file, option: 'Input file');
String format = options['format'] ?? defaultFormat();
String outputFile = options['out'] ?? defaultOutput();
List<String> urlMapping = options['url-mapping'] as List<String>;
var customUriMappings = parseCustomUriMappings(urlMapping);
var repository = new Repository();
Program program;
var watch = new Stopwatch()..start();
List<String> loadedFiles;
Function getLoadedFiles;
List errors = const [];
TargetFlags targetFlags = new TargetFlags(strongMode: options['strong']);
Target target = getTarget(options['target'], targetFlags);
var declaredVariables = <String, String>{};
declaredVariables.addAll(target.extraDeclaredVariables);
for (String define in options['D']) {
int separator = define.indexOf('=');
if (separator == -1) {
fail('Invalid define: -D$define. Format is -D<name>=<value>');
}
String name = define.substring(0, separator);
String value = define.substring(separator + 1);
declaredVariables[name] = value;
}
if (file.endsWith('.dill')) {
program = loadProgramFromBinary(file, repository);
getLoadedFiles = () => [file];
} else {
DartOptions dartOptions = new DartOptions(
strongMode: target.strongMode,
strongModeSdk: target.strongModeSdk,
sdk: options['sdk'],
packagePath: packagePath,
customUriMappings: customUriMappings,
declaredVariables: declaredVariables);
String packageDiscoveryPath = batchModeState.isBatchMode ? null : file;
DartLoader loader = await batchModeState.batch.getLoader(
repository, dartOptions,
packageDiscoveryPath: packageDiscoveryPath);
if (options['link']) {
program = loader.loadProgram(file, target: target);
} else {
var library = loader.loadLibrary(file);
assert(library == repository.getLibrary(file));
program = new Program(repository.libraries);
}
errors = loader.errors;
if (errors.isNotEmpty) {
const int errorLimit = 100;
stderr.writeln(errors.take(errorLimit).join('\n'));
if (errors.length > errorLimit) {
stderr
.writeln('[error] ${errors.length - errorLimit} errors not shown');
}
}
getLoadedFiles = () => loadedFiles ??= loader.getLoadedFileNames();
}
bool canContinueCompilation = errors.isEmpty || options['tolerant'];
int loadTime = watch.elapsedMilliseconds;
if (shouldReportMetrics) {
print('loader.time = $loadTime ms');
}
void runVerifier() {
if (options['verify-ir']) {
verifyProgram(program);
}
}
if (canContinueCompilation) {
runVerifier();
}
String outputDependencies = options['write-dependencies'];
if (outputDependencies != null) {
new File(outputDependencies).writeAsStringSync(getLoadedFiles().join('\n'));
}
// Apply target-specific transformations.
if (target != null && options['link'] && canContinueCompilation) {
target.transformProgram(program);
runVerifier();
}
if (options['no-output']) {
return CompilerOutcome.Ok;
}
watch.reset();
Future ioFuture;
if (canContinueCompilation) {
switch (format) {
case 'text':
writeProgramToText(program,
path: outputFile, showExternal: options['show-external']);
break;
case 'bin':
ioFuture = writeProgramToBinary(program, outputFile);
break;
}
}
int time = watch.elapsedMilliseconds;
if (shouldReportMetrics) {
print('writer.time = $time ms');
}
await ioFuture;
if (shouldReportMetrics) {
int flushTime = watch.elapsedMilliseconds - time;
print('writer.flush_time = $flushTime ms');
}
return errors.length > 0 ? CompilerOutcome.Fail : CompilerOutcome.Ok;
}