blob: ca2413106ecfc8547044790cf59885cd7c0a550b [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.
library fasta.tool.command_line;
import 'dart:async' show Future;
import 'dart:io' show exit;
import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
import 'package:build_integration/file_system/single_root.dart'
show SingleRootFileSystem;
import 'package:front_end/src/api_prototype/compiler_options.dart'
show CompilerOptions, parseExperimentalFlags;
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/experimental_flags.dart'
show ExperimentalFlag;
import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem;
import 'package:front_end/src/api_prototype/standard_file_system.dart'
show StandardFileSystem;
import 'package:front_end/src/base/nnbd_mode.dart';
import 'package:front_end/src/base/processed_options.dart'
show ProcessedOptions;
import 'package:front_end/src/compute_platform_binaries_location.dart'
show computePlatformBinariesLocation;
import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
import 'package:front_end/src/base/command_line_options.dart';
import 'package:front_end/src/fasta/fasta_codes.dart'
show
Message,
templateFastaCLIArgumentRequired,
messageFastaUsageLong,
messageFastaUsageShort,
templateUnspecified;
import 'package:front_end/src/fasta/problems.dart' show DebugAbort;
import 'package:front_end/src/fasta/resolve_input_uri.dart'
show resolveInputUri;
import 'package:front_end/src/scheme_based_file_system.dart'
show SchemeBasedFileSystem;
import 'package:kernel/target/targets.dart'
show Target, getTarget, TargetFlags, targets;
class CommandLineProblem {
final Message message;
CommandLineProblem(this.message);
CommandLineProblem.deprecated(String message)
: this(templateUnspecified.withArguments(message));
}
class ParsedArguments {
final Map<String, dynamic> options = <String, dynamic>{};
final List<String> arguments = <String>[];
final Map<String, String> defines = <String, String>{};
toString() => "ParsedArguments($options, $arguments)";
/// Parses a list of command-line [arguments] into options and arguments.
///
/// An /option/ is something that, normally, starts with `-` or `--` (one or
/// two dashes). However, as a special case `/?` and `/h` are also recognized
/// as options for increased compatibility with Windows. An option can have a
/// value.
///
/// An /argument/ is something that isn't an option, for example, a file name.
///
/// The specification is a map of options to one of the following values:
/// * the type literal `Uri`, representing an option value of type [Uri],
/// * the type literal `int`, representing an option value of type [int],
/// * the bool literal `false`, representing a boolean option that is turned
/// off by default,
/// * the bool literal `true, representing a boolean option that is turned on
/// by default,
/// * or the string literal `","`, representing a comma-separated list of
/// values.
///
/// If [arguments] contains `"--"`, anything before is parsed as options, and
/// arguments; anything following is treated as arguments (even if starting
/// with, for example, a `-`).
///
/// If an option isn't found in [specification], an error is thrown.
///
/// Boolean options do not require an option value, but an optional value can
/// be provided using the forms `--option=value` where `value` can be `true`
/// or `yes` to turn on the option, or `false` or `no` to turn it off. If no
/// option value is specified, a boolean option is turned on.
///
/// All other options require an option value, either on the form `--option
/// value` or `--option=value`.
static ParsedArguments parse(
List<String> arguments, Map<String, ValueSpecification> specification) {
specification ??= const <String, ValueSpecification>{};
ParsedArguments result = new ParsedArguments();
int index = arguments.indexOf("--");
Iterable<String> nonOptions = const <String>[];
Iterator<String> iterator = arguments.iterator;
if (index != -1) {
nonOptions = arguments.skip(index + 1);
iterator = arguments.take(index).iterator;
}
while (iterator.moveNext()) {
String argument = iterator.current;
if (argument.startsWith("-") || argument == "/?" || argument == "/h") {
String value;
if (argument.startsWith("-D")) {
value = argument.substring("-D".length);
argument = "-D";
} else {
index = argument.indexOf("=");
if (index != -1) {
value = argument.substring(index + 1);
argument = argument.substring(0, index);
}
}
ValueSpecification valueSpecification = specification[argument];
if (valueSpecification == null) {
throw new CommandLineProblem.deprecated(
"Unknown option '$argument'.");
}
String canonicalArgument = argument;
if (valueSpecification.alias != null) {
canonicalArgument = valueSpecification.alias;
valueSpecification = specification[valueSpecification.alias];
}
if (valueSpecification == null) {
throw new CommandLineProblem.deprecated(
"Unknown option alias '$canonicalArgument'.");
}
final bool requiresValue = valueSpecification.requiresValue;
if (requiresValue && value == null) {
if (!iterator.moveNext()) {
throw new CommandLineProblem(
templateFastaCLIArgumentRequired.withArguments(argument));
}
value = iterator.current;
}
valueSpecification.processValue(
result, canonicalArgument, argument, value);
} else {
result.arguments.add(argument);
}
}
specification.forEach((String key, ValueSpecification value) {
if (value.defaultValue != null) {
result.options[key] ??= value.defaultValue;
}
});
result.arguments.addAll(nonOptions);
return result;
}
}
// Before adding new options here, you must:
// * Document the option.
// * Get an explicit approval from the front-end team.
const Map<String, ValueSpecification> optionSpecification =
const <String, ValueSpecification>{
Flags.bytecode: const BoolValue(false),
Flags.compileSdk: const UriValue(),
Flags.dumpIr: const BoolValue(false),
Flags.enableExperiment: const StringListValue(),
Flags.excludeSource: const BoolValue(false),
Flags.omitPlatform: const BoolValue(false),
Flags.fatal: const StringListValue(),
Flags.fatalSkip: const StringValue(),
Flags.forceLateLowering: const BoolValue(false),
Flags.forceStaticFieldLowering: const BoolValue(false),
Flags.forceNoExplicitGetterCalls: const BoolValue(false),
Flags.help: const BoolValue(false),
Flags.librariesJson: const UriValue(),
Flags.noDefines: const BoolValue(false),
Flags.output: const UriValue(),
Flags.packages: const UriValue(),
Flags.platform: const UriValue(),
Flags.sdk: const UriValue(),
Flags.singleRootBase: const UriValue(),
Flags.singleRootScheme: const StringValue(),
Flags.nnbdWeakMode: const BoolValue(false),
Flags.nnbdStrongMode: const BoolValue(false),
Flags.nnbdAgnosticMode: const BoolValue(false),
Flags.target: const StringValue(),
Flags.verbose: const BoolValue(false),
Flags.verify: const BoolValue(false),
Flags.warnOnReachabilityCheck: const BoolValue(false),
Flags.linkDependencies: const UriListValue(),
Flags.noDeps: const BoolValue(false),
"-D": const DefineValue(),
"-h": const AliasValue(Flags.help),
"--out": const AliasValue(Flags.output),
"-o": const AliasValue(Flags.output),
"-t": const AliasValue(Flags.target),
"-v": const AliasValue(Flags.verbose),
"/?": const AliasValue(Flags.help),
"/h": const AliasValue(Flags.help),
};
void throwCommandLineProblem(String message) {
throw new CommandLineProblem.deprecated(message);
}
ProcessedOptions analyzeCommandLine(String programName,
ParsedArguments parsedArguments, bool areRestArgumentsInputs) {
final Map<String, dynamic> options = parsedArguments.options;
final List<String> arguments = parsedArguments.arguments;
final bool help = options[Flags.help];
final bool verbose = options[Flags.verbose];
if (help) {
print(computeUsage(programName, verbose).message);
exit(0);
}
if (options.containsKey(Flags.compileSdk) &&
options.containsKey(Flags.platform)) {
return throw new CommandLineProblem.deprecated(
"Can't specify both '${Flags.compileSdk}' and '${Flags.platform}'.");
}
final String targetName = options[Flags.target] ?? "vm";
Map<ExperimentalFlag, bool> experimentalFlags = parseExperimentalFlags(
parseExperimentalArguments(options[Flags.enableExperiment]),
onError: throwCommandLineProblem,
onWarning: print);
final TargetFlags flags = new TargetFlags(
forceLateLoweringForTesting: options[Flags.forceLateLowering],
forceStaticFieldLoweringForTesting:
options[Flags.forceStaticFieldLowering],
forceNoExplicitGetterCallsForTesting:
options[Flags.forceNoExplicitGetterCalls],
enableNullSafety:
experimentalFlags.containsKey(ExperimentalFlag.nonNullable) &&
experimentalFlags[ExperimentalFlag.nonNullable]);
final Target target = getTarget(targetName, flags);
if (target == null) {
return throw new CommandLineProblem.deprecated(
"Target '${targetName}' not recognized. "
"Valid targets are:\n ${targets.keys.join("\n ")}");
}
final bool noDefines = options[Flags.noDefines];
final bool noDeps = options[Flags.noDeps];
final bool verify = options[Flags.verify];
final bool dumpIr = options[Flags.dumpIr];
final bool excludeSource = options[Flags.excludeSource];
final bool omitPlatform = options[Flags.omitPlatform];
final Uri packages = options[Flags.packages];
final Set<String> fatal =
new Set<String>.from(options[Flags.fatal] ?? <String>[]);
final bool errorsAreFatal = fatal.contains("errors");
final bool warningsAreFatal = fatal.contains("warnings");
final int fatalSkip = int.tryParse(options[Flags.fatalSkip] ?? "0") ?? -1;
final bool bytecode = options[Flags.bytecode];
final bool compileSdk = options.containsKey(Flags.compileSdk);
final String singleRootScheme = options[Flags.singleRootScheme];
final Uri singleRootBase = options[Flags.singleRootBase];
final bool nnbdStrongMode = options[Flags.nnbdStrongMode];
final bool nnbdWeakMode = options[Flags.nnbdWeakMode];
final bool nnbdAgnosticMode = options[Flags.nnbdAgnosticMode];
final NnbdMode nnbdMode = nnbdAgnosticMode
? NnbdMode.Agnostic
: (nnbdStrongMode ? NnbdMode.Strong : NnbdMode.Weak);
final bool warnOnReachabilityCheck = options[Flags.warnOnReachabilityCheck];
final List<Uri> linkDependencies = options[Flags.linkDependencies] ?? [];
if (nnbdStrongMode && nnbdWeakMode) {
return throw new CommandLineProblem.deprecated(
"Can't specify both '${Flags.nnbdStrongMode}' and "
"'${Flags.nnbdWeakMode}'.");
}
if (nnbdStrongMode && nnbdAgnosticMode) {
return throw new CommandLineProblem.deprecated(
"Can't specify both '${Flags.nnbdStrongMode}' and "
"'${Flags.nnbdAgnosticMode}'.");
}
if (nnbdWeakMode && nnbdAgnosticMode) {
return throw new CommandLineProblem.deprecated(
"Can't specify both '${Flags.nnbdWeakMode}' and "
"'${Flags.nnbdAgnosticMode}'.");
}
FileSystem fileSystem = StandardFileSystem.instance;
if (singleRootScheme != null) {
fileSystem = new SchemeBasedFileSystem({
'file': fileSystem,
'data': fileSystem,
// TODO(askesc): remove also when fixing StandardFileSystem (empty schemes
// should have been handled elsewhere).
'': fileSystem,
singleRootScheme: new SingleRootFileSystem(
singleRootScheme, singleRootBase, fileSystem),
});
}
CompilerOptions compilerOptions = new CompilerOptions()
..compileSdk = compileSdk
..fileSystem = fileSystem
..packagesFileUri = packages
..target = target
..throwOnErrorsForDebugging = errorsAreFatal
..throwOnWarningsForDebugging = warningsAreFatal
..skipForDebugging = fatalSkip
..embedSourceText = !excludeSource
..debugDump = dumpIr
..omitPlatform = omitPlatform
..verbose = verbose
..verify = verify
..experimentalFlags = experimentalFlags
..environmentDefines = noDefines ? null : parsedArguments.defines
..nnbdMode = nnbdMode
..additionalDills = linkDependencies
..emitDeps = !noDeps
..warnOnReachabilityCheck = warnOnReachabilityCheck;
if (programName == "compile_platform") {
if (arguments.length != 5) {
return throw new CommandLineProblem.deprecated(
"Expected five arguments.");
}
if (compileSdk) {
return throw new CommandLineProblem.deprecated(
"Cannot specify '${Flags.compileSdk}' option to compile_platform.");
}
if (options.containsKey(Flags.output)) {
return throw new CommandLineProblem.deprecated(
"Cannot specify '${Flags.output}' option to compile_platform.");
}
return new ProcessedOptions(
options: compilerOptions
..sdkSummary = options[Flags.platform]
..librariesSpecificationUri = resolveInputUri(arguments[1])
..setExitCodeOnProblem = true
..bytecode = bytecode,
inputs: <Uri>[Uri.parse(arguments[0])],
output: resolveInputUri(arguments[3]));
} else if (arguments.isEmpty) {
return throw new CommandLineProblem.deprecated("No Dart file specified.");
}
final Uri defaultOutput = resolveInputUri("${arguments.first}.dill");
final Uri output = options[Flags.output] ?? defaultOutput;
final Uri sdk = options[Flags.sdk] ?? options[Flags.compileSdk];
final Uri librariesJson = options[Flags.librariesJson];
String computePlatformDillName() {
switch (target.name) {
case 'dartdevc':
return 'dartdevc.dill';
case 'dart2js':
return 'dart2js_platform.dill';
case 'dart2js_server':
return 'dart2js_platform.dill';
case 'vm':
// TODO(johnniwinther): Stop generating 'vm_platform.dill' and rename
// 'vm_platform_strong.dill' to 'vm_platform.dill'.
return "vm_platform_strong.dill";
case 'none':
return "vm_platform_strong.dill";
default:
throwCommandLineProblem("Target '${target.name}' requires an explicit "
"'${Flags.platform}' option.");
}
return null;
}
final Uri platform = compileSdk
? null
: (options[Flags.platform] ??
computePlatformBinariesLocation(forceBuildDir: true)
.resolve(computePlatformDillName()));
compilerOptions
..sdkRoot = sdk
..sdkSummary = platform
..librariesSpecificationUri = librariesJson;
List<Uri> inputs = <Uri>[];
if (areRestArgumentsInputs) {
for (String argument in arguments) {
inputs.add(resolveInputUri(argument));
}
}
return new ProcessedOptions(
options: compilerOptions, inputs: inputs, output: output);
}
Future<T> withGlobalOptions<T>(
String programName,
List<String> arguments,
bool areRestArgumentsInputs,
Future<T> f(CompilerContext context, List<String> restArguments)) {
ParsedArguments parsedArguments;
ProcessedOptions options;
CommandLineProblem problem;
try {
parsedArguments = ParsedArguments.parse(arguments, optionSpecification);
options = analyzeCommandLine(
programName, parsedArguments, areRestArgumentsInputs);
} on CommandLineProblem catch (e) {
options = new ProcessedOptions();
problem = e;
}
return CompilerContext.runWithOptions<T>(options, (c) {
if (problem != null) {
print(computeUsage(programName, options.verbose).message);
print(c.formatWithoutLocation(problem.message, Severity.error));
exit(1);
}
return f(c, parsedArguments.arguments);
}, errorOnMissingInput: problem == null);
}
Message computeUsage(String programName, bool verbose) {
String basicUsage = "Usage: $programName [options] dartfile\n";
String summary;
String options =
(verbose ? messageFastaUsageLong.message : messageFastaUsageShort.message)
.trim();
switch (programName) {
case "outline":
summary =
"Creates an outline of a Dart program in the Dill/Kernel IR format.";
break;
case "compile":
summary = "Compiles a Dart program to the Dill/Kernel IR format.";
break;
case "run":
summary = "Runs a Dart program.";
break;
case "compile_platform":
summary = "Compiles Dart SDK platform to the Dill/Kernel IR format.";
basicUsage = "Usage: $programName [options]"
" dart-library-uri libraries.json vm_outline_strong.dill"
" platform.dill outline.dill\n";
}
StringBuffer sb = new StringBuffer(basicUsage);
if (summary != null) {
sb.writeln();
sb.writeln(summary);
sb.writeln();
}
sb.write(options);
// TODO(ahe): Don't use [templateUnspecified].
return templateUnspecified.withArguments("$sb");
}
Future<T> runProtectedFromAbort<T>(Future<T> Function() action,
[T failingValue]) async {
if (CompilerContext.isActive) {
throw "runProtectedFromAbort should be called from 'main',"
" that is, outside a compiler context.";
}
try {
return await action();
} on DebugAbort catch (e) {
print(e.message.message);
// DebugAbort should never happen in production code, so we want test.py to
// treat this as a crash which is signalled by exiting with 255.
exit(255);
}
}
abstract class ValueSpecification {
const ValueSpecification();
String get alias => null;
dynamic get defaultValue => null;
bool get requiresValue => true;
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value);
}
class AliasValue extends ValueSpecification {
final String alias;
const AliasValue(this.alias);
bool get requiresValue =>
throw new UnsupportedError("AliasValue.requiresValue");
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
throw new UnsupportedError("AliasValue.processValue");
}
}
class UriValue extends ValueSpecification {
const UriValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
if (result.options.containsKey(canonicalArgument)) {
throw new CommandLineProblem.deprecated(
"Multiple values for '$argument': "
"'${result.options[canonicalArgument]}' and '$value'.");
}
// TODO(ahe): resolve Uris lazily, so that schemes provided by
// other flags can be used for parsed command-line arguments too.
result.options[canonicalArgument] = resolveInputUri(value);
}
}
class StringValue extends ValueSpecification {
const StringValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
if (result.options.containsKey(canonicalArgument)) {
throw new CommandLineProblem.deprecated(
"Multiple values for '$argument': "
"'${result.options[canonicalArgument]}' and '$value'.");
}
result.options[canonicalArgument] = value;
}
}
class BoolValue extends ValueSpecification {
final bool defaultValue;
const BoolValue(this.defaultValue);
bool get requiresValue => false;
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
if (result.options.containsKey(canonicalArgument)) {
throw new CommandLineProblem.deprecated(
"Multiple values for '$argument': "
"'${result.options[canonicalArgument]}' and '$value'.");
}
bool parsedValue;
if (value == null || value == "true" || value == "yes") {
parsedValue = true;
} else if (value == "false" || value == "no") {
parsedValue = false;
} else {
throw new CommandLineProblem.deprecated(
"Value for '$argument' is '$value', "
"but expected one of: 'true', 'false', 'yes', or 'no'.");
}
result.options[canonicalArgument] = parsedValue;
}
}
class IntValue extends ValueSpecification {
const IntValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
if (result.options.containsKey(canonicalArgument)) {
throw new CommandLineProblem.deprecated(
"Multiple values for '$argument': "
"'${result.options[canonicalArgument]}' and '$value'.");
}
int parsedValue = int.tryParse(value);
if (parsedValue == null) {
throw new CommandLineProblem.deprecated(
"Value for '$argument', '$value', isn't an int.");
}
result.options[canonicalArgument] = parsedValue;
}
}
class DefineValue extends ValueSpecification {
const DefineValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
int index = value.indexOf('=');
String name;
String expression;
if (index != -1) {
name = value.substring(0, index);
expression = value.substring(index + 1);
} else {
name = value;
expression = value;
}
result.defines[name] = expression;
}
}
class StringListValue extends ValueSpecification {
const StringListValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
result.options
.putIfAbsent(canonicalArgument, () => <String>[])
.addAll(value.split(","));
}
}
class UriListValue extends ValueSpecification {
const UriListValue();
void processValue(ParsedArguments result, String canonicalArgument,
String argument, String value) {
result.options
.putIfAbsent(canonicalArgument, () => <Uri>[])
.addAll(value.split(",").map(resolveInputUri));
}
}