Files
sdk/pkg/dartdev/lib/dartdev.dart
T
Paul Berry c041afad9f Decouple the migration tool from package:dartdev so it can be migrated.
In order to migrate the migration tool to null safety, it can't have a
dependency on `package:dartdev`, because `package:dartdev` isn't
migrated yet.  (This is a problem because once the migration tool's
tests have been migrated to null safety, they will require the
transitive closure of all the packages they depend on to also be
migrated).

Fortunately, it's quite easy to change the migration tool so that it
doesn't depend on `package:dartdev`; we only need to move the class
`MigrateCommand` from `package:nnbd_migration` to `package:dartdev`.

To make this work, we have to move the constant `migrationGuideLink`
from `MigrateCommand` to `MigrationCli`, and make the method
`MigrationCli._defineOptions` public.

Change-Id: Ie1ce8db0e8245e98bbb72facddd723300816340e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204540
Reviewed-by: Devon Carew <devoncarew@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2021-06-23 22:57:21 +00:00

275 lines
9.1 KiB
Dart

// 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.
// Do not call exit() directly. Use VmInteropHandler.exit() instead.
import 'dart:io' as io hide exit;
import 'dart:isolate';
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:dartdev/src/commands/migrate.dart';
import 'package:meta/meta.dart';
import 'package:pedantic/pedantic.dart';
import 'package:pub/pub.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/language_server.dart';
import 'src/commands/run.dart';
import 'src/commands/test.dart';
import 'src/core.dart';
import 'src/events.dart';
import 'src/experiments.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.
Future<void> runDartdev(List<String> args, SendPort port) async {
VmInteropHandler.initialize(port);
if (args.contains('run')) {
// 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') ||
element.contains('--devtools')),
)
.toList();
}
// Finally, call the runner to execute the command; see DartdevRunner.
final runner = DartdevRunner(args);
var exitCode = 1;
try {
exitCode = await runner.run(args);
} on UsageException catch (e) {
// TODO(sigurdm): It is unclear when a UsageException gets to here, and
// when it is in DartdevRunner.runCommand.
io.stderr.writeln('$e');
exitCode = 64;
} finally {
VmInteropHandler.exit(exitCode);
}
}
class DartdevRunner extends CommandRunner<int> {
static const String dartdevDescription =
'A command-line utility for Dart development';
@override
final ArgParser argParser = ArgParser(
usageLineLength: dartdevUsageLineLength,
allowTrailingOptions: false,
);
final bool verbose;
Analytics _analytics;
DartdevRunner(List<String> args)
: verbose = args.contains('-v') || args.contains('--verbose'),
super('dart', '$dartdevDescription.') {
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 analytics.');
argParser.addFlag('disable-analytics',
negatable: false, help: 'Disable analytics.');
argParser.addFlag('diagnostics',
negatable: false, help: 'Show tool diagnostic output.', hide: !verbose);
argParser.addFlag(
'analytics',
negatable: true,
help: 'Disable analytics for this `dart *` run',
hide: true,
);
addCommand(AnalyzeCommand(verbose: verbose));
addCommand(CreateCommand(verbose: verbose));
addCommand(CompileCommand(verbose: verbose));
addCommand(FixCommand(verbose: verbose));
addCommand(FormatCommand(verbose: verbose));
addCommand(LanguageServerCommand(verbose: verbose));
addCommand(MigrateCommand(verbose: verbose));
addCommand(pubCommand());
addCommand(RunCommand(verbose: verbose));
addCommand(TestCommand());
}
@visibleForTesting
Analytics get analytics => _analytics;
@override
String get invocation =>
'dart ${verbose ? '[vm-options] ' : ''}<command|dart-file> [arguments]';
@override
String get usageFooter =>
'See https://dart.dev/tools/dart-tool for detailed documentation.';
@override
Future<int> runCommand(ArgResults topLevelResults) async {
final stopwatch = Stopwatch()..start();
// The Analytics instance used to report information back to Google Analytics;
// see lib/src/analytics.dart.
_analytics = createAnalyticsInstance(
topLevelResults.wasParsed('analytics')
? !topLevelResults['analytics']
: false,
);
// 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 as DartdevAnalytics).disclosureShownOnTerminal &&
io.stdout.hasTerminal &&
!isBot()) {
print(analyticsNoticeOnFirstRunMessage);
(analytics as DartdevAnalytics).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 (topLevelResults['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 has been disabled.
print(analyticsDisabledNoticeMessage);
return 0;
} else if (topLevelResults['enable-analytics']) {
analytics.enabled = true;
// Alert the user again that data will be collected.
print(analyticsNoticeOnFirstRunMessage);
return 0;
}
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.
return 254;
}
}
final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
log = topLevelResults['diagnostics']
? Logger.verbose(ansi: ansi)
: Logger.standard(ansi: ansi);
var command = topLevelResults.command;
final commandNames = [];
while (command != null) {
commandNames.add(command.name);
if (command.command == null) break;
command = command.command;
}
final path = commandNames.join('/');
// Send the screen view to analytics
unawaited(
analytics.sendScreenView(path),
);
// The exit code for the dartdev process; null indicates that it has not 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;
try {
exitCode = await super.runCommand(topLevelResults);
if (path != null && analytics.enabled) {
// Send the event to analytics
unawaited(
sendUsageEvent(
analytics,
path,
exitCode: exitCode,
commandFlags:
// This finds the options that where explicitly given to the command
// (and not for an eventual subcommand) without including the actual
// value.
//
// Note that this will also conflate short-options and long-options.
command?.options?.where(command.wasParsed)?.toList(),
specifiedExperiments: topLevelResults.enabledExperiments,
),
);
}
} on UsageException catch (e) {
io.stderr.writeln('$e');
exitCode = 64;
} catch (e, st) {
// 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();
if (analytics.enabled) {
unawaited(
analytics.sendTiming(
path ?? '',
stopwatch.elapsedMilliseconds,
category: 'commands',
),
);
}
// Set the exitCode, if it wasn't set in the catch block above.
exitCode ??= 0;
// Send analytics before exiting
if (analytics.enabled) {
// And now send the exceptions and events to Google Analytics:
if (exception != null) {
unawaited(
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();
}
return exitCode;
}
}