blob: 300ab8ba729280d891342f27f9ae6cc0fd61ad86 [file] [log] [blame]
// 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:io';
import 'package:analyzer/src/command_line/arguments.dart'
show defineAnalysisArguments, ignoreUnrecognizedFlagsFlag;
import 'package:analyzer/src/summary/package_bundle_reader.dart'
show ConflictingSummaryException;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:args/command_runner.dart' show UsageException;
import 'package:path/path.dart' as path;
import 'context.dart' show AnalyzerOptions;
import 'module_compiler.dart' show CompilerOptions, ModuleCompiler;
const _binaryName = 'dartdevc';
bool _verbose = false;
/// Runs a single compile for dartdevc.
///
/// This handles argument parsing, usage, error handling.
/// See bin/dartdevc.dart for the actual entry point, which includes Bazel
/// worker support.
int compile(List<String> args, {void printFn(Object obj)}) {
printFn ??= print;
ArgResults argResults;
AnalyzerOptions analyzerOptions;
try {
var parser = ddcArgParser();
if (args.contains('--$ignoreUnrecognizedFlagsFlag')) {
args = filterUnknownArguments(args, parser);
}
argResults = parser.parse(args);
analyzerOptions = AnalyzerOptions.fromArguments(argResults);
} on FormatException catch (error) {
printFn('$error\n\n$_usageMessage');
return 64;
}
_verbose = argResults['verbose'] as bool;
if (argResults['help'] as bool || args.isEmpty) {
printFn(_usageMessage);
return 0;
}
if (argResults['version'] as bool) {
printFn('$_binaryName version ${_getVersion()}');
return 0;
}
try {
_compile(argResults, analyzerOptions, printFn);
return 0;
} on UsageException catch (error) {
// Incorrect usage, input file not found, etc.
printFn('${error.message}\n\n$_usageMessage');
return 64;
} on ConflictingSummaryException catch (error) {
// Same input file appears in multiple provided summaries.
printFn(error);
return 65;
} on CompileErrorException catch (error) {
// Code has error(s) and failed to compile.
printFn(error);
return 1;
} catch (error, stackTrace) {
// Anything else is likely a compiler bug.
//
// --unsafe-force-compile is a bit of a grey area, but it's nice not to
// crash while compiling
// (of course, output code may crash, if it had errors).
//
printFn('''
We're sorry, you've found a bug in our compiler.
You can report this bug at:
https://github.com/dart-lang/sdk/issues/labels/area-dev-compiler
Please include the information below in your report, along with
any other information that may help us track it down. Thanks!
$_binaryName arguments: ${args.join(' ')}
dart --version: ${Platform.version}
```
$error
$stackTrace
```''');
return 70;
}
}
ArgParser ddcArgParser({bool hide = true}) {
var argParser = ArgParser(allowTrailingOptions: true)
..addFlag('help',
abbr: 'h',
help: 'Display this message. Add -v to show hidden options.',
negatable: false)
..addFlag('verbose',
abbr: 'v', negatable: false, help: 'Verbose help output.', hide: hide)
..addFlag('version',
negatable: false, help: 'Print the $_binaryName version.', hide: hide)
..addFlag(ignoreUnrecognizedFlagsFlag,
help: 'Ignore unrecognized command line flags.',
defaultsTo: false,
hide: hide)
..addMultiOption('out', abbr: 'o', help: 'Output file (required).');
CompilerOptions.addArguments(argParser, hide: hide);
defineAnalysisArguments(argParser, hide: hide, ddc: true);
AnalyzerOptions.addArguments(argParser, hide: hide);
return argParser;
}
bool _changed(List<int> list1, List<int> list2) {
var length = list1.length;
if (length != list2.length) return true;
for (var i = 0; i < length; ++i) {
if (list1[i] != list2[i]) return true;
}
return false;
}
void _compile(ArgResults argResults, AnalyzerOptions analyzerOptions,
void printFn(Object obj)) {
var compilerOpts = CompilerOptions.fromArguments(argResults);
var compiler = ModuleCompiler(analyzerOptions,
summaryPaths: compilerOpts.summaryModules.keys);
var outPaths = argResults['out'] as List<String>;
var moduleFormats = compilerOpts.moduleFormats;
if (outPaths.isEmpty) {
throw UsageException(
'Please specify the output file location. For example:\n'
' -o PATH/TO/OUTPUT_FILE.js',
'');
} else if (outPaths.length != moduleFormats.length) {
throw UsageException(
'Number of output files (${outPaths.length}) must match '
'number of module formats (${moduleFormats.length}).',
'');
}
var module = compiler.compile(argResults.rest, compilerOpts);
module.errors.forEach(printFn);
if (!module.isValid) {
throw compilerOpts.unsafeForceCompile
? ForceCompileErrorException()
: CompileErrorException();
}
// Write JS file, as well as source map and summary (if requested).
for (var i = 0; i < outPaths.length; i++) {
module.writeCodeSync(moduleFormats[i], outPaths[i]);
}
if (module.summaryBytes != null) {
var summaryPaths = compilerOpts.summaryOutPath != null
? [compilerOpts.summaryOutPath]
: outPaths.map((p) =>
'${path.withoutExtension(p)}.${compilerOpts.summaryExtension}');
// place next to every compiled module
for (var summaryPath in summaryPaths) {
// Only overwrite if summary changed. This plays better with timestamp
// based build systems.
var file = File(summaryPath);
if (!file.existsSync() ||
_changed(file.readAsBytesSync(), module.summaryBytes)) {
if (!file.parent.existsSync()) file.parent.createSync(recursive: true);
file.writeAsBytesSync(module.summaryBytes);
}
}
}
}
String get _usageMessage =>
'The Dart Development Compiler compiles Dart sources into a JavaScript '
'module.\n\n'
'Usage: $_binaryName [options...] <sources...>\n\n'
'${ddcArgParser(hide: !_verbose).usage}';
String _getVersion() {
try {
// This is relative to bin/snapshot, so ../..
String versionPath = Platform.script.resolve('../../version').toFilePath();
File versionFile = File(versionPath);
return versionFile.readAsStringSync().trim();
} catch (_) {
// This happens when the script is not running in the context of an SDK.
return "<unknown>";
}
}
/// Thrown when the input source code has errors.
class CompileErrorException implements Exception {
toString() => '\nPlease fix all errors before compiling (warnings are okay).';
}
/// Thrown when force compilation failed (probably due to static errors).
class ForceCompileErrorException extends CompileErrorException {
toString() =>
'\nForce-compilation not successful. Please check static errors.';
}
// TODO(jmesserly): fix this function in analyzer
List<String> filterUnknownArguments(List<String> args, ArgParser parser) {
Set<String> knownOptions = Set<String>();
Set<String> knownAbbreviations = Set<String>();
parser.options.forEach((String name, option) {
knownOptions.add(name);
String abbreviation = option.abbr;
if (abbreviation != null) {
knownAbbreviations.add(abbreviation);
}
});
List<String> filtered = <String>[];
for (int i = 0; i < args.length; i++) {
String argument = args[i];
if (argument.startsWith('--') && argument.length > 2) {
int equalsOffset = argument.lastIndexOf('=');
int end = equalsOffset < 0 ? argument.length : equalsOffset;
if (knownOptions.contains(argument.substring(2, end))) {
filtered.add(argument);
}
} else if (argument.startsWith('-') && argument.length > 1) {
// TODO(jmesserly): fix this line in analyzer
// It was discarding abbreviations such as -Da=b
// Abbreviations must be 1-character (this is enforced by ArgParser),
// so we don't need to use `optionName`
if (knownAbbreviations.contains(argument[1])) {
filtered.add(argument);
}
} else {
filtered.add(argument);
}
}
return filtered;
}