98e29db45c
We want to avoid users passing `--enable-experiment=native-assets` on stable and beta, as we'd like to move fast and break things on the experiment. This aligns the experiment with how the experiment is working in Flutter: main and dev branch only. Before this CL, dartdev did not check experiment flags. Unknown experiments would fail in the VM. After this CL, dartdev checks the experiment flags and errors out early. Change-Id: I875ea3272f4b67342da19ea2e4be329a4b380573 Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406660 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
279 lines
10 KiB
Dart
279 lines
10 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/development_service.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
|
|
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.') {
|
|
// The list of commands should be kept in sync with
|
|
// `DartDevIsolate::ShouldParseCommand` in `runtime/bin/dartdev_isolate.cc`.
|
|
addCommand(AnalyzeCommand(verbose: verbose));
|
|
addCommand(CompilationServerCommand(verbose: verbose));
|
|
final nativeAssetsExperimentEnabled =
|
|
nativeAssetsEnabled(vmEnabledExperiments);
|
|
if (nativeAssetsExperimentEnabled) {
|
|
final recordUseExperimentEnabled = recordUseEnabled(vmEnabledExperiments);
|
|
addCommand(BuildCommand(
|
|
verbose: verbose,
|
|
recordUseEnabled: recordUseExperimentEnabled,
|
|
));
|
|
}
|
|
addCommand(CompileCommand(
|
|
verbose: verbose,
|
|
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
|
|
));
|
|
addCommand(CreateCommand(verbose: verbose));
|
|
addCommand(DebugAdapterCommand(verbose: verbose));
|
|
addCommand(DevelopmentServiceCommand(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.flag('analytics') ||
|
|
topLevelResults.flag('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.flag('enable-analytics');
|
|
final disableAnalytics = topLevelResults.flag('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.flag('diagnostics')) {
|
|
log = Logger.verbose(ansi: ansi);
|
|
}
|
|
|
|
late final List<String> experimentErrors =
|
|
validateExperiments(vmEnabledExperiments);
|
|
if (experimentErrors.isNotEmpty) {
|
|
experimentErrors.forEach(io.stderr.writeln);
|
|
return 254;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|