blob: 9da36edd0f61d5df03dc291d129c743ed14e3d3a [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Todd Volkert454db9d2017-11-09 21:45:31 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jonah Williams92034482022-06-15 13:02:07 -07005
Jonah Williams74bd7b62021-01-27 15:17:53 -08006
Todd Volkert454db9d2017-11-09 21:45:31 -08007import 'dart:async';
8
9import 'package:args/command_runner.dart';
Devon Carew1c6078c2018-05-24 13:42:46 -070010import 'package:intl/intl.dart' as intl;
11import 'package:intl/intl_standalone.dart' as intl_standalone;
Todd Volkert454db9d2017-11-09 21:45:31 -080012
Jonah Williamsb1e99182021-01-13 14:14:07 -080013import 'src/base/async_guard.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080014import 'src/base/common.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080015import 'src/base/context.dart';
16import 'src/base/file_system.dart';
17import 'src/base/io.dart';
18import 'src/base/logger.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080019import 'src/base/process.dart';
Todd Volkert8d11f5c2018-03-28 10:58:28 -070020import 'src/context_runner.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080021import 'src/doctor.dart';
Jenn Magder868e4172021-11-03 12:53:04 -070022import 'src/globals.dart' as globals;
Jenn Magdere29595d2021-04-09 15:24:15 -070023import 'src/reporting/crash_reporting.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080024import 'src/runner/flutter_command.dart';
25import 'src/runner/flutter_command_runner.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080026
27/// Runs the Flutter tool with support for the specified list of [commands].
28Future<int> run(
29 List<String> args,
Jonah Williamsbd3eee72020-09-03 11:55:55 -070030 List<FlutterCommand> Function() commands, {
Jenn Magder8109dcc2020-04-20 15:23:09 -070031 bool muteCommandLogging = false,
32 bool verbose = false,
33 bool verboseHelp = false,
Jonah Williams92034482022-06-15 13:02:07 -070034 bool? reportCrashes,
35 String? flutterVersion,
36 Map<Type, Generator>? overrides,
Christopher Fujino08d5d512022-09-02 10:38:56 -070037 required ShutdownHooks shutdownHooks,
Jenn Magder8109dcc2020-04-20 15:23:09 -070038 }) async {
jcollins-g26102c92018-02-01 10:27:55 -080039 if (muteCommandLogging) {
40 // Remove the verbose option; for help and doctor, users don't need to see
41 // verbose logs.
James D. Lin566c1d12020-04-21 22:09:50 -070042 args = List<String>.of(args);
Greg Spencer52ae1022021-11-10 16:13:04 -080043 args.removeWhere((String option) => option == '-vv' || option == '-v' || option == '--verbose');
Todd Volkert454db9d2017-11-09 21:45:31 -080044 }
Todd Volkert454db9d2017-11-09 21:45:31 -080045
Todd Volkert8d11f5c2018-03-28 10:58:28 -070046 return runInContext<int>(() async {
Jonah Williams21333432020-03-10 11:35:52 -070047 reportCrashes ??= !await globals.isRunningOnBot;
Jonah Williamsf5de6aa2020-06-04 16:35:36 -070048 final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
Jonah Williamsbd3eee72020-09-03 11:55:55 -070049 commands().forEach(runner.addCommand);
Jonah Williams21333432020-03-10 11:35:52 -070050
Todd Volkert454db9d2017-11-09 21:45:31 -080051 // Initialize the system locale.
Devon Carew1c6078c2018-05-24 13:42:46 -070052 final String systemLocale = await intl_standalone.findSystemLocale();
53 intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
54 systemLocale, intl.NumberFormat.localeExists,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +010055 onFailure: (String _) => 'en_US',
Devon Carew1c6078c2018-05-24 13:42:46 -070056 );
Todd Volkert454db9d2017-11-09 21:45:31 -080057
Jenn Magder7f715622020-01-27 17:38:01 -080058 String getVersion() => flutterVersion ?? globals.flutterVersion.getVersionString(redactUnknownBranches: true);
Jonah Williams92034482022-06-15 13:02:07 -070059 Object? firstError;
60 StackTrace? firstStackTrace;
Michael Goderbauercb867bb2021-03-05 18:38:15 -080061 return runZoned<Future<int>>(() async {
Alexander Aprelevd9179782019-06-10 09:52:22 -070062 try {
63 await runner.run(args);
James D. Linc21b3232020-06-15 09:35:04 -070064
65 // Triggering [runZoned]'s error callback does not necessarily mean that
Pierre-Louis0c2f7bc2022-09-02 06:00:58 +020066 // we stopped executing the body. See https://github.com/dart-lang/sdk/issues/42150.
James D. Linc21b3232020-06-15 09:35:04 -070067 if (firstError == null) {
Christopher Fujino08d5d512022-09-02 10:38:56 -070068 return await _exit(0, shutdownHooks: shutdownHooks);
James D. Linc21b3232020-06-15 09:35:04 -070069 }
70
Pierre-Louis0c2f7bc2022-09-02 06:00:58 +020071 // We already hit some error, so don't return success. The error path
James D. Linc21b3232020-06-15 09:35:04 -070072 // (which should be in progress) is responsible for calling _exit().
73 return 1;
Ian Hickson299d4842021-10-14 22:03:03 -070074 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
75 // This catches all exceptions to send to crash logging, etc.
Zachary Andersonc7596da2019-07-31 13:51:19 -070076 firstError = error;
77 firstStackTrace = stackTrace;
Christopher Fujino08d5d512022-09-02 10:38:56 -070078 return _handleToolError(error, stackTrace, verbose, args, reportCrashes!, getVersion, shutdownHooks);
Alexander Aprelevd9179782019-06-10 09:52:22 -070079 }
Lasse R.H. Nielsenaf5194d2020-03-24 16:34:17 +010080 }, onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use
Zachary Andersonc7596da2019-07-31 13:51:19 -070081 // If sending a crash report throws an error into the zone, we don't want
82 // to re-try sending the crash report with *that* error. Rather, we want
83 // to send the original error that triggered the crash report.
James D. Linc21b3232020-06-15 09:35:04 -070084 firstError ??= error;
85 firstStackTrace ??= stackTrace;
Christopher Fujino08d5d512022-09-02 10:38:56 -070086 await _handleToolError(firstError!, firstStackTrace, verbose, args, reportCrashes!, getVersion, shutdownHooks);
Alexander Aprelevd9179782019-06-10 09:52:22 -070087 });
Jonah Williams81c7af32018-11-06 14:36:35 -080088 }, overrides: overrides);
Todd Volkert454db9d2017-11-09 21:45:31 -080089}
90
Todd Volkert454db9d2017-11-09 21:45:31 -080091Future<int> _handleToolError(
Jonah Williams92034482022-06-15 13:02:07 -070092 Object error,
93 StackTrace? stackTrace,
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010094 bool verbose,
95 List<String> args,
96 bool reportCrashes,
Michael Goderbauer7b251f52021-03-04 08:59:17 -080097 String Function() getFlutterVersion,
Christopher Fujino08d5d512022-09-02 10:38:56 -070098 ShutdownHooks shutdownHooks,
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010099) async {
Todd Volkert454db9d2017-11-09 21:45:31 -0800100 if (error is UsageException) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800101 globals.printError('${error.message}\n');
102 globals.printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.");
Todd Volkert454db9d2017-11-09 21:45:31 -0800103 // Argument error exit code.
Christopher Fujino08d5d512022-09-02 10:38:56 -0700104 return _exit(64, shutdownHooks: shutdownHooks);
Todd Volkert454db9d2017-11-09 21:45:31 -0800105 } else if (error is ToolExit) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700106 if (error.message != null) {
Jonah Williams92034482022-06-15 13:02:07 -0700107 globals.printError(error.message!);
Zachary Andersone2340c62019-09-13 14:51:35 -0700108 }
109 if (verbose) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800110 globals.printError('\n$stackTrace\n');
Zachary Andersone2340c62019-09-13 14:51:35 -0700111 }
Christopher Fujino08d5d512022-09-02 10:38:56 -0700112 return _exit(error.exitCode ?? 1, shutdownHooks: shutdownHooks);
Todd Volkert454db9d2017-11-09 21:45:31 -0800113 } else if (error is ProcessExit) {
114 // We've caught an exit code.
115 if (error.immediate) {
116 exit(error.exitCode);
117 return error.exitCode;
118 } else {
Christopher Fujino08d5d512022-09-02 10:38:56 -0700119 return _exit(error.exitCode, shutdownHooks: shutdownHooks);
Todd Volkert454db9d2017-11-09 21:45:31 -0800120 }
121 } else {
122 // We've crashed; emit a log report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800123 globals.stdio.stderrWrite('\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800124
125 if (!reportCrashes) {
126 // Print the stack trace on the bots - don't write a crash report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800127 globals.stdio.stderrWrite('$error\n');
128 globals.stdio.stderrWrite('$stackTrace\n');
Christopher Fujino08d5d512022-09-02 10:38:56 -0700129 return _exit(1, shutdownHooks: shutdownHooks);
Zachary Anderson36e8b932019-08-21 13:07:52 -0700130 }
131
132 // Report to both [Usage] and [CrashReportSender].
Jenn Magderf6a55122020-03-06 12:14:29 -0800133 globals.flutterUsage.sendException(error);
Jonah Williamsb1e99182021-01-13 14:14:07 -0800134 await asyncGuard(() async {
135 final CrashReportSender crashReportSender = CrashReportSender(
Jonah Williamsb1e99182021-01-13 14:14:07 -0800136 usage: globals.flutterUsage,
137 platform: globals.platform,
138 logger: globals.logger,
139 operatingSystemUtils: globals.os,
140 );
141 await crashReportSender.sendReport(
142 error: error,
Jonah Williams92034482022-06-15 13:02:07 -0700143 stackTrace: stackTrace!,
Jonah Williamsb1e99182021-01-13 14:14:07 -0800144 getFlutterVersion: getFlutterVersion,
145 command: args.join(' '),
146 );
147 }, onError: (dynamic error) {
148 globals.printError('Error sending crash report: $error');
149 });
Zachary Anderson36e8b932019-08-21 13:07:52 -0700150
James D. Linb7fd24a2020-04-28 10:34:03 -0700151 globals.printError('Oops; flutter has exited unexpectedly: "$error".');
Zachary Anderson36e8b932019-08-21 13:07:52 -0700152
153 try {
Jenn Magder1c0eade2022-01-10 13:20:21 -0800154 final BufferLogger logger = BufferLogger(
155 terminal: globals.terminal,
156 outputPreferences: globals.outputPreferences,
157 );
158
159 final DoctorText doctorText = DoctorText(logger);
160
James D. Linb7fd24a2020-04-28 10:34:03 -0700161 final CrashDetails details = CrashDetails(
162 command: _crashCommand(args),
163 error: error,
Jonah Williams92034482022-06-15 13:02:07 -0700164 stackTrace: stackTrace!,
Jenn Magder1c0eade2022-01-10 13:20:21 -0800165 doctorText: doctorText,
James D. Linb7fd24a2020-04-28 10:34:03 -0700166 );
167 final File file = await _createLocalCrashReport(details);
Jonah Williams92034482022-06-15 13:02:07 -0700168 await globals.crashReporter!.informUser(details, file);
Jenn Magder7d8f8202019-11-26 14:06:31 -0800169
Christopher Fujino08d5d512022-09-02 10:38:56 -0700170 return _exit(1, shutdownHooks: shutdownHooks);
Zachary Anderson6c408a02020-03-06 10:22:12 -0800171 // This catch catches all exceptions to ensure the message below is printed.
Jonah Williams92034482022-06-15 13:02:07 -0700172 } catch (error, st) { // ignore: avoid_catches_without_on_clauses
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800173 globals.stdio.stderrWrite(
Jonah Williams92034482022-06-15 13:02:07 -0700174 'Unable to generate crash report due to secondary error: $error\n$st\n'
Ian Hickson299d4842021-10-14 22:03:03 -0700175 '${globals.userMessages.flutterToolBugInstructions}\n',
176 );
James D. Linb7fd24a2020-04-28 10:34:03 -0700177 // Any exception thrown here (including one thrown by `_exit()`) will
Zachary Anderson36e8b932019-08-21 13:07:52 -0700178 // get caught by our zone's `onError` handler. In order to avoid an
179 // infinite error loop, we throw an error that is recognized above
180 // and will trigger an immediate exit.
181 throw ProcessExit(1, immediate: true);
Todd Volkert454db9d2017-11-09 21:45:31 -0800182 }
183 }
184}
185
Jenn Magder7d8f8202019-11-26 14:06:31 -0800186String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}';
187
188String _crashException(dynamic error) => '${error.runtimeType}: $error';
189
Todd Volkert454db9d2017-11-09 21:45:31 -0800190/// Saves the crash report to a local file.
James D. Linb7fd24a2020-04-28 10:34:03 -0700191Future<File> _createLocalCrashReport(CrashDetails details) async {
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800192 File crashFile = globals.fsUtils.getUniqueFile(
Jenn Magder8109dcc2020-04-20 15:23:09 -0700193 globals.fs.currentDirectory,
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800194 'flutter',
195 'log',
196 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800197
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200198 final StringBuffer buffer = StringBuffer();
Todd Volkert454db9d2017-11-09 21:45:31 -0800199
James D. Linb7fd24a2020-04-28 10:34:03 -0700200 buffer.writeln('Flutter crash report.');
201 buffer.writeln('${globals.userMessages.flutterToolBugInstructions}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800202
203 buffer.writeln('## command\n');
James D. Linb7fd24a2020-04-28 10:34:03 -0700204 buffer.writeln('${details.command}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800205
206 buffer.writeln('## exception\n');
James D. Linb7fd24a2020-04-28 10:34:03 -0700207 buffer.writeln('${_crashException(details.error)}\n');
208 buffer.writeln('```\n${details.stackTrace}```\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800209
210 buffer.writeln('## flutter doctor\n');
Jenn Magder1c0eade2022-01-10 13:20:21 -0800211 buffer.writeln('```\n${await details.doctorText.text}```');
Todd Volkert454db9d2017-11-09 21:45:31 -0800212
213 try {
Zachary Anderson398ac1f2019-08-20 13:15:08 -0700214 crashFile.writeAsStringSync(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800215 } on FileSystemException catch (_) {
216 // Fallback to the system temporary directory.
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800217 crashFile = globals.fsUtils.getUniqueFile(
Jenn Magder8109dcc2020-04-20 15:23:09 -0700218 globals.fs.systemTempDirectory,
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800219 'flutter',
220 'log',
221 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800222 try {
Zachary Anderson398ac1f2019-08-20 13:15:08 -0700223 crashFile.writeAsStringSync(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800224 } on FileSystemException catch (e) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800225 globals.printError('Could not write crash report to disk: $e');
226 globals.printError(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800227 }
228 }
229
230 return crashFile;
231}
232
Christopher Fujino08d5d512022-09-02 10:38:56 -0700233Future<int> _exit(int code, {required ShutdownHooks shutdownHooks}) async {
Zachary Anderson372fe292019-11-05 10:43:52 -0800234 // Prints the welcome message if needed.
Jenn Magderf6a55122020-03-06 12:14:29 -0800235 globals.flutterUsage.printWelcome();
Todd Volkert454db9d2017-11-09 21:45:31 -0800236
237 // Send any last analytics calls that are in progress without overly delaying
238 // the tool's exit (we wait a maximum of 250ms).
Jenn Magderf6a55122020-03-06 12:14:29 -0800239 if (globals.flutterUsage.enabled) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200240 final Stopwatch stopwatch = Stopwatch()..start();
Jenn Magderf6a55122020-03-06 12:14:29 -0800241 await globals.flutterUsage.ensureAnalyticsSent();
Zachary Anderson23a3d102020-01-13 10:12:06 -0800242 globals.printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
Todd Volkert454db9d2017-11-09 21:45:31 -0800243 }
244
245 // Run shutdown hooks before flushing logs
Christopher Fujino08d5d512022-09-02 10:38:56 -0700246 await shutdownHooks.runShutdownHooks(globals.logger);
Todd Volkert454db9d2017-11-09 21:45:31 -0800247
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200248 final Completer<void> completer = Completer<void>();
Todd Volkert454db9d2017-11-09 21:45:31 -0800249
250 // Give the task / timer queue one cycle through before we hard exit.
251 Timer.run(() {
252 try {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800253 globals.printTrace('exiting with code $code');
Todd Volkert454db9d2017-11-09 21:45:31 -0800254 exit(code);
255 completer.complete();
Jonah Williams08576cb2020-10-12 09:31:02 -0700256 // This catches all exceptions because the error is propagated on the
Zachary Anderson6c408a02020-03-06 10:22:12 -0800257 // completer.
258 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
Todd Volkert454db9d2017-11-09 21:45:31 -0800259 completer.completeError(error, stackTrace);
260 }
261 });
262
263 await completer.future;
264 return code;
265}