| // 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.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); |
| } |
| |
| 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; |
| } |
| } |