3495a21fc8
Arguments matching the following patterns were being removed from the argument list before being processed by DartDev's argument parser: --observe --enable-vm-service --devtools This was due to these arguments having non-standard formats that caused parsing issues in the past. However, these arguments are now completely parsed by the VM and not actually decoded in the CLI, so there's no need to blindly remove them. This was causing issues when passing arguments to user processes (e.g., `dart run foo.dart --devtools-port` would drop the `--devtools-port` argument due to this logic). Fixes https://github.com/dart-lang/sdk/issues/54815 Change-Id: I09358d7f5004483ad3b457ba74229c940d9a0d75 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350360 Reviewed-by: Kenzie Davisson <kenzieschmoll@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
264 lines
9.4 KiB
Dart
264 lines
9.4 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:async';
|
|
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:meta/meta.dart';
|
|
import 'package:pub/pub.dart';
|
|
import 'package:unified_analytics/unified_analytics.dart';
|
|
|
|
import 'src/commands/analyze.dart';
|
|
import 'src/commands/build.dart';
|
|
import 'src/commands/compilation_server.dart';
|
|
import 'src/commands/compile.dart';
|
|
import 'src/commands/create.dart';
|
|
import 'src/commands/debug_adapter.dart';
|
|
import 'src/commands/devtools.dart';
|
|
import 'src/commands/doc.dart';
|
|
import 'src/commands/fix.dart';
|
|
import 'src/commands/info.dart';
|
|
import 'src/commands/language_server.dart';
|
|
import 'src/commands/run.dart';
|
|
import 'src/commands/test.dart';
|
|
import 'src/commands/tooling_daemon.dart';
|
|
import 'src/core.dart';
|
|
import 'src/experiments.dart';
|
|
import 'src/unified_analytics.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 {
|
|
int? exitCode = 1;
|
|
try {
|
|
VmInteropHandler.initialize(port);
|
|
// Call the runner to execute the command; see DartdevRunner.
|
|
final runner = DartdevRunner(args, vmArgs: io.Platform.executableArguments);
|
|
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;
|
|
} catch (e, st) {
|
|
// Unexpected error encountered.
|
|
io.stderr.writeln('An unexpected error was encountered by the Dart CLI.');
|
|
io.stderr.writeln('Please file an issue at '
|
|
'https://github.com/dart-lang/sdk/issues/new with the following '
|
|
'details:\n');
|
|
io.stderr.writeln("Invocation: 'dart ${args.join(' ')}'");
|
|
io.stderr.writeln("Exception: '$e'");
|
|
io.stderr.writeln('Stack Trace:');
|
|
io.stderr.writeln(st.toString());
|
|
exitCode = 255;
|
|
} finally {
|
|
VmInteropHandler.exit(exitCode);
|
|
}
|
|
}
|
|
|
|
class DartdevRunner extends CommandRunner<int> {
|
|
static const String dartdevDescription =
|
|
'A command-line utility for Dart development';
|
|
|
|
@override
|
|
late final ArgParser argParser;
|
|
|
|
final bool verbose;
|
|
|
|
final List<String> vmEnabledExperiments;
|
|
|
|
Analytics? _unifiedAnalytics;
|
|
final bool _isAnalyticsTest;
|
|
|
|
DartdevRunner(
|
|
List<String> args, {
|
|
Analytics? analyticsOverride,
|
|
bool isAnalyticsTest = false,
|
|
List<String> vmArgs = const [],
|
|
}) : verbose = args.contains('-v') || args.contains('--verbose'),
|
|
argParser = globalDartdevOptionsParser(
|
|
verbose: args.contains('-v') || args.contains('--verbose')),
|
|
vmEnabledExperiments = parseVmEnabledExperiments(vmArgs),
|
|
_unifiedAnalytics = analyticsOverride,
|
|
_isAnalyticsTest = isAnalyticsTest,
|
|
super('dart', '$dartdevDescription.') {
|
|
addCommand(AnalyzeCommand(verbose: verbose));
|
|
addCommand(CompilationServerCommand(verbose: verbose));
|
|
final nativeAssetsExperimentEnabled =
|
|
nativeAssetsEnabled(vmEnabledExperiments);
|
|
if (nativeAssetsExperimentEnabled) {
|
|
addCommand(BuildCommand(verbose: verbose));
|
|
}
|
|
addCommand(CompileCommand(
|
|
verbose: verbose,
|
|
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
|
|
));
|
|
addCommand(CreateCommand(verbose: verbose));
|
|
addCommand(DebugAdapterCommand(verbose: verbose));
|
|
addCommand(DevToolsCommand(verbose: verbose));
|
|
addCommand(DocCommand(verbose: verbose));
|
|
addCommand(FixCommand(verbose: verbose));
|
|
addCommand(FormatCommand(verbose: verbose));
|
|
addCommand(InfoCommand(verbose: verbose));
|
|
addCommand(LanguageServerCommand(verbose: verbose));
|
|
addCommand(pubCommand(isVerbose: () => verbose));
|
|
addCommand(RunCommand(
|
|
verbose: verbose,
|
|
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
|
|
));
|
|
addCommand(TestCommand(
|
|
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
|
|
));
|
|
addCommand(ToolingDaemonCommand(verbose: verbose));
|
|
}
|
|
|
|
@visibleForTesting
|
|
Analytics get unifiedAnalytics => _unifiedAnalytics!;
|
|
|
|
@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();
|
|
|
|
// We don't want to run analytics when we're running in a CI environment
|
|
// unless we're explicitly testing analytics for dartdev.
|
|
final implicitlySuppressAnalytics = isBot() && !_isAnalyticsTest;
|
|
bool suppressAnalytics = !topLevelResults['analytics'] ||
|
|
topLevelResults['suppress-analytics'] ||
|
|
implicitlySuppressAnalytics;
|
|
|
|
if (topLevelResults.wasParsed('analytics')) {
|
|
io.stderr.writeln(
|
|
'`--[no-]analytics` is deprecated. Use `--suppress-analytics` '
|
|
'to disable analytics for one run instead.');
|
|
}
|
|
final enableAnalytics = topLevelResults['enable-analytics'];
|
|
final disableAnalytics = topLevelResults['disable-analytics'];
|
|
|
|
if (!implicitlySuppressAnalytics &&
|
|
suppressAnalytics &&
|
|
(enableAnalytics || disableAnalytics)) {
|
|
// This isn't an error if we're implicitly disabling analytics because
|
|
// we're running in a CI environment.
|
|
io.stderr.writeln('`--suppress-analytics` cannot be used with either'
|
|
' `--enable-analytics` or `--disable-analytics`.');
|
|
return 254;
|
|
}
|
|
// The Analytics instance used to report information back to Google Analytics;
|
|
// see lib/src/unified_analytics.dart.
|
|
_unifiedAnalytics ??= createUnifiedAnalytics(
|
|
disableAnalytics: suppressAnalytics,
|
|
);
|
|
|
|
// If we have not printed the analytics notification to stdout, the user is
|
|
// on a terminal, and the machine is not a bot, then print the disclosure.
|
|
bool analyticsMessagePrinted = false;
|
|
if (unifiedAnalytics.shouldShowMessage &&
|
|
io.stdout.hasTerminal &&
|
|
!isBot()) {
|
|
print(unifiedAnalytics.getConsentMessage);
|
|
unifiedAnalytics.clientShowedMessage();
|
|
analyticsMessagePrinted = true;
|
|
}
|
|
|
|
// When `--disable-analytics` or `--enable-analytics` are called we perform
|
|
// the respective intention and print any notices to standard out and exit.
|
|
if (disableAnalytics) {
|
|
// Disable sending data via the unified analytics package.
|
|
await unifiedAnalytics.setTelemetry(false);
|
|
await unifiedAnalytics.close();
|
|
|
|
// Alert the user that analytics has been disabled.
|
|
print(analyticsDisabledNoticeMessage);
|
|
return 0;
|
|
} else if (enableAnalytics) {
|
|
// Enable sending data via the unified analytics package.
|
|
await unifiedAnalytics.setTelemetry(true);
|
|
await unifiedAnalytics.close();
|
|
|
|
// Alert the user again that data will be collected.
|
|
if (!analyticsMessagePrinted) {
|
|
print(unifiedAnalytics.getConsentMessage);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (topLevelResults['diagnostics']) {
|
|
log = Logger.verbose(ansi: ansi);
|
|
}
|
|
|
|
var command = topLevelResults.command;
|
|
final commandNames = [];
|
|
while (command != null) {
|
|
commandNames.add(command.name);
|
|
if (command.command == null) break;
|
|
command = command.command;
|
|
}
|
|
|
|
// 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.
|
|
try {
|
|
exitCode = await super.runCommand(topLevelResults);
|
|
if (unifiedAnalytics.telemetryEnabled) {
|
|
// Send the event to analytics
|
|
final path = commandNames.join('/');
|
|
final experiments = topLevelResults.enabledExperiments
|
|
..sort((a, b) => a.compareTo(b));
|
|
unifiedAnalytics.send(
|
|
Event.dartCliCommandExecuted(
|
|
name: path,
|
|
enabledExperiments: experiments.join(','),
|
|
),
|
|
);
|
|
}
|
|
} on UsageException catch (e) {
|
|
io.stderr.writeln('$e');
|
|
exitCode = 64;
|
|
} catch (e, st) {
|
|
// Set the exception and stack trace only for non-UsageException cases:
|
|
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 ??= 0;
|
|
await unifiedAnalytics.close();
|
|
}
|
|
|
|
return exitCode;
|
|
}
|
|
}
|