blob: 72df662e49ae7517f8c5b303010b8889f8f8f8da [file] [log] [blame]
// Copyright (c) 2020, 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' as io;
import 'dart:isolate';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:dart_style/src/cli/format_command.dart';
import 'package:nnbd_migration/migration_cli.dart';
import 'package:usage/usage.dart';
import 'src/analytics.dart';
import 'src/commands/analyze.dart';
import 'src/commands/compile.dart';
import 'src/commands/create.dart';
import 'src/commands/fix.dart';
import 'src/commands/pub.dart';
import 'src/commands/run.dart';
import 'src/commands/test.dart' hide Runtime;
import 'src/core.dart';
import 'src/experiments.dart';
import 'src/sdk.dart';
import 'src/utils.dart';
import 'src/vm_interop_handler.dart';
/// This is typically called from bin/, but given the length of the method and
/// analytics logic, it has been moved here. Also note that this method calls
/// [io.exit(code)] directly.
Future<void> runDartdev(List<String> args, SendPort port) async {
VmInteropHandler.initialize(port);
final stopwatch = Stopwatch();
int result;
// The Analytics instance used to report information back to Google Analytics,
// see lib/src/analytics.dart.
Analytics analytics;
// The exit code for the dartdev process, null indicates that it has not yet
// been set yet. The value is set in the catch and finally blocks below.
int exitCode;
// Any caught non-UsageExceptions when running the sub command
Object exception;
StackTrace stackTrace;
analytics =
createAnalyticsInstance(args.contains('--disable-dartdev-analytics'));
// If we have not printed the analyticsNoticeOnFirstRunMessage to stdout,
// the user is on a terminal, and the machine is not a bot, then print the
// disclosure and set analytics.disclosureShownOnTerminal to true.
if (analytics is DartdevAnalytics &&
!analytics.disclosureShownOnTerminal &&
io.stdout.hasTerminal &&
!isBot()) {
print(analyticsNoticeOnFirstRunMessage);
analytics.disclosureShownOnTerminal = true;
}
// When `--disable-analytics` or `--enable-analytics` are called we perform
// the respective intention and print any notices to standard out and exit.
if (args.contains('--disable-analytics')) {
// This block also potentially catches the case of (disableAnalytics &&
// enableAnalytics), in which we favor the disabling of analytics.
analytics.enabled = false;
// Alert the user that analytics have been disabled:
print(analyticsDisabledNoticeMessage);
io.exit(0);
} else if (args.contains('--enable-analytics')) {
analytics.enabled = true;
// Alert the user again that anonymous data will be collected:
print(analyticsNoticeOnFirstRunMessage);
io.exit(0);
}
// --launch-dds is provided by the VM if the VM service is to be enabled. In
// that case, we need to launch DDS as well.
// TODO(bkonyi): add support for pub run (#42726)
if (args.contains('--launch-dds')) {
RunCommand.launchDds = true;
}
String commandName;
try {
stopwatch.start();
final runner = DartdevRunner(args);
// Run can't be called with the '--disable-dartdev-analytics' flag, remove
// it if it is contained in args.
if (args.contains('--disable-dartdev-analytics')) {
args = List.from(args)..remove('--disable-dartdev-analytics');
}
// Run also can't be called with '--launch-dds', remove it if it's
// contained in args.
if (args.contains('--launch-dds')) {
args = List.from(args)..remove('--launch-dds');
}
// These flags have a format that can't be handled by package:args, so
// while they are valid flags we'll assume the VM has verified them by this
// point.
args = args
.where(
(element) => !(element.contains('--observe') ||
element.contains('--enable-vm-service')),
)
.toList();
// Before calling to run, send the first ping to analytics to have the first
// ping, as well as the command itself, running in parallel.
if (analytics.enabled) {
commandName = getCommandStr(args, runner.commands.keys.toList());
// ignore: unawaited_futures
analytics.sendEvent(eventCategory, commandName);
}
// If ... help pub ... is in the args list, remove 'help', and add '--help'
// to the end of the list. This will make it possible to use the help
// command to access subcommands of pub such as `dart help pub publish`, see
// https://github.com/dart-lang/sdk/issues/42965
if (PubUtils.shouldModifyArgs(args, runner.commands.keys.toList())) {
args = PubUtils.modifyArgs(args);
}
// Finally, call the runner to execute the command, see DartdevRunner.
result = await runner.run(args);
} catch (e, st) {
if (e is UsageException) {
io.stderr.writeln('$e');
exitCode = 64;
} else {
// Set the exception and stack trace only for non-UsageException cases:
exception = e;
stackTrace = st;
io.stderr.writeln('$e');
io.stderr.writeln('$st');
exitCode = 1;
}
} finally {
stopwatch.stop();
// Set the exitCode, if it wasn't set in the catch block above.
exitCode ??= result ?? 0;
// Send analytics before exiting
if (analytics.enabled) {
analytics.setSessionValue(exitCodeParam, exitCode);
// ignore: unawaited_futures
analytics.sendTiming(commandName, stopwatch.elapsedMilliseconds,
category: 'commands');
// And now send the exceptions and events to Google Analytics:
if (exception != null) {
// ignore: unawaited_futures
analytics.sendException(
'${exception.runtimeType}\n${sanitizeStacktrace(stackTrace)}',
fatal: true);
}
await analytics.waitForLastPing(
timeout: const Duration(milliseconds: 200));
}
// Set the enabled flag in the analytics object to true. Note: this will not
// enable the analytics unless the disclosure was shown (terminal
// detected), and the machine is not detected to be a bot.
if (analytics.firstRun) {
analytics.enabled = true;
}
analytics.close();
VmInteropHandler.exit(exitCode);
}
}
class DartdevRunner<int> extends CommandRunner {
@override
final ArgParser argParser =
ArgParser(usageLineLength: dartdevUsageLineLength);
static const String dartdevDescription =
'A command-line utility for Dart development';
DartdevRunner(List<String> args) : super('dart', '$dartdevDescription.') {
final bool verbose = args.contains('-v') || args.contains('--verbose');
argParser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Show additional command output.');
argParser.addFlag('version',
negatable: false, help: 'Print the Dart SDK version.');
argParser.addFlag('enable-analytics',
negatable: false, help: 'Enable anonymous analytics.');
argParser.addFlag('disable-analytics',
negatable: false, help: 'Disable anonymous analytics.');
addExperimentalFlags(argParser, verbose);
argParser.addFlag('diagnostics',
negatable: false, help: 'Show tool diagnostic output.', hide: !verbose);
// A hidden flag to disable analytics on this run, this constructor can be
// called with this flag, but should be removed before run() is called as
// the flag has not been added to all sub-commands.
argParser.addFlag('disable-dartdev-analytics',
negatable: false,
help: 'Disable anonymous analytics for this `dart *` run',
hide: true);
// Another hidden flag used by the VM to indicate that DDS should be
// launched. Should be removed for all commands other than `run`.
argParser.addFlag('launch-dds',
negatable: false, hide: true, help: 'Launch DDS.');
addCommand(AnalyzeCommand());
addCommand(CreateCommand(verbose: verbose));
addCommand(CompileCommand());
addCommand(FixCommand());
addCommand(FormatCommand());
addCommand(MigrateCommand(
verbose: verbose,
hidden: Runtime.runtime.stableChannel,
));
addCommand(PubCommand());
addCommand(RunCommand(verbose: verbose));
addCommand(TestCommand());
}
@override
String get invocation =>
'dart [<vm-flags>] <command|dart-file> [<arguments>]';
void addExperimentalFlags(ArgParser argParser, bool verbose) {
List<ExperimentalFeature> features = experimentalFeatures;
Map<String, String> allowedHelp = {};
for (ExperimentalFeature feature in features) {
String suffix =
feature.isEnabledByDefault ? ' (no-op - enabled by default)' : '';
allowedHelp[feature.enableString] = '${feature.documentation}$suffix';
}
argParser.addMultiOption(
experimentFlagName,
valueHelp: 'experiment',
allowedHelp: verbose ? allowedHelp : null,
help: 'Enable one or more experimental features '
'(see dart.dev/go/experiments).',
);
}
@override
Future<int> runCommand(ArgResults topLevelResults) async {
assert(!topLevelResults.arguments.contains('--disable-dartdev-analytics'));
if (topLevelResults.command == null &&
topLevelResults.arguments.isNotEmpty) {
final firstArg = topLevelResults.arguments.first;
// If we make it this far, it means the VM couldn't find the file on disk.
if (firstArg.endsWith('.dart')) {
io.stderr.writeln(
"Error when reading '$firstArg': No such file or directory.");
// This is the exit code used by the frontend.
VmInteropHandler.exit(254);
}
}
isDiagnostics = topLevelResults['diagnostics'];
final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
log = isDiagnostics
? Logger.verbose(ansi: ansi)
: Logger.standard(ansi: ansi);
if (topLevelResults.wasParsed(experimentFlagName)) {
List<String> experimentIds = topLevelResults[experimentFlagName];
for (ExperimentalFeature feature in experimentalFeatures) {
// We allow default true flags, but complain when they are passed in.
if (feature.isEnabledByDefault &&
experimentIds.contains(feature.enableString)) {
print("'${feature.enableString}' is now enabled by default; this "
'flag is no longer required.');
}
}
}
return await super.runCommand(topLevelResults);
}
}