blob: 4d3da3ada8b119a7d014a0601624923017bc3f8c [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 Williams74bd7b62021-01-27 15:17:53 -08005// @dart = 2.8
6
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';
Zachary Anderson23a3d102020-01-13 10:12:06 -080022import '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,
34 bool reportCrashes,
35 String flutterVersion,
36 Map<Type, Generator> overrides,
37 }) async {
jcollins-g26102c92018-02-01 10:27:55 -080038 if (muteCommandLogging) {
39 // Remove the verbose option; for help and doctor, users don't need to see
40 // verbose logs.
James D. Lin566c1d12020-04-21 22:09:50 -070041 args = List<String>.of(args);
Todd Volkert454db9d2017-11-09 21:45:31 -080042 args.removeWhere((String option) => option == '-v' || option == '--verbose');
43 }
Todd Volkert454db9d2017-11-09 21:45:31 -080044
Todd Volkert8d11f5c2018-03-28 10:58:28 -070045 return runInContext<int>(() async {
Jonah Williams21333432020-03-10 11:35:52 -070046 reportCrashes ??= !await globals.isRunningOnBot;
Jonah Williamsf5de6aa2020-06-04 16:35:36 -070047 final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
Jonah Williamsbd3eee72020-09-03 11:55:55 -070048 commands().forEach(runner.addCommand);
Jonah Williams21333432020-03-10 11:35:52 -070049
Todd Volkert454db9d2017-11-09 21:45:31 -080050 // Initialize the system locale.
Devon Carew1c6078c2018-05-24 13:42:46 -070051 final String systemLocale = await intl_standalone.findSystemLocale();
52 intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
53 systemLocale, intl.NumberFormat.localeExists,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +010054 onFailure: (String _) => 'en_US',
Devon Carew1c6078c2018-05-24 13:42:46 -070055 );
Todd Volkert454db9d2017-11-09 21:45:31 -080056
Jenn Magder7f715622020-01-27 17:38:01 -080057 String getVersion() => flutterVersion ?? globals.flutterVersion.getVersionString(redactUnknownBranches: true);
Zachary Andersonc7596da2019-07-31 13:51:19 -070058 Object firstError;
59 StackTrace firstStackTrace;
Michael Goderbauercb867bb2021-03-05 18:38:15 -080060 return runZoned<Future<int>>(() async {
Alexander Aprelevd9179782019-06-10 09:52:22 -070061 try {
62 await runner.run(args);
James D. Linc21b3232020-06-15 09:35:04 -070063
64 // Triggering [runZoned]'s error callback does not necessarily mean that
65 // we stopped executing the body. See https://github.com/dart-lang/sdk/issues/42150.
66 if (firstError == null) {
67 return await _exit(0);
68 }
69
70 // We already hit some error, so don't return success. The error path
71 // (which should be in progress) is responsible for calling _exit().
72 return 1;
Zachary Anderson6c408a02020-03-06 10:22:12 -080073 // This catches all exceptions to send to crash logging, etc.
74 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
Zachary Andersonc7596da2019-07-31 13:51:19 -070075 firstError = error;
76 firstStackTrace = stackTrace;
Michael Goderbauercb867bb2021-03-05 18:38:15 -080077 return _handleToolError(
Alexander Aprelevd9179782019-06-10 09:52:22 -070078 error, stackTrace, verbose, args, reportCrashes, getVersion);
79 }
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;
86 await _handleToolError(firstError, firstStackTrace, verbose, args, reportCrashes, getVersion);
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(
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010092 dynamic error,
93 StackTrace stackTrace,
94 bool verbose,
95 List<String> args,
96 bool reportCrashes,
Michael Goderbauer7b251f52021-03-04 08:59:17 -080097 String Function() getFlutterVersion,
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010098) async {
Todd Volkert454db9d2017-11-09 21:45:31 -080099 if (error is UsageException) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800100 globals.printError('${error.message}\n');
101 globals.printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.");
Todd Volkert454db9d2017-11-09 21:45:31 -0800102 // Argument error exit code.
103 return _exit(64);
104 } else if (error is ToolExit) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700105 if (error.message != null) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800106 globals.printError(error.message);
Zachary Andersone2340c62019-09-13 14:51:35 -0700107 }
108 if (verbose) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800109 globals.printError('\n$stackTrace\n');
Zachary Andersone2340c62019-09-13 14:51:35 -0700110 }
Todd Volkert454db9d2017-11-09 21:45:31 -0800111 return _exit(error.exitCode ?? 1);
112 } else if (error is ProcessExit) {
113 // We've caught an exit code.
114 if (error.immediate) {
115 exit(error.exitCode);
116 return error.exitCode;
117 } else {
118 return _exit(error.exitCode);
119 }
120 } else {
121 // We've crashed; emit a log report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800122 globals.stdio.stderrWrite('\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800123
124 if (!reportCrashes) {
125 // Print the stack trace on the bots - don't write a crash report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800126 globals.stdio.stderrWrite('$error\n');
127 globals.stdio.stderrWrite('$stackTrace\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800128 return _exit(1);
Zachary Anderson36e8b932019-08-21 13:07:52 -0700129 }
130
131 // Report to both [Usage] and [CrashReportSender].
Jenn Magderf6a55122020-03-06 12:14:29 -0800132 globals.flutterUsage.sendException(error);
Jonah Williamsb1e99182021-01-13 14:14:07 -0800133 await asyncGuard(() async {
134 final CrashReportSender crashReportSender = CrashReportSender(
Jonah Williamsb1e99182021-01-13 14:14:07 -0800135 usage: globals.flutterUsage,
136 platform: globals.platform,
137 logger: globals.logger,
138 operatingSystemUtils: globals.os,
139 );
140 await crashReportSender.sendReport(
141 error: error,
142 stackTrace: stackTrace,
143 getFlutterVersion: getFlutterVersion,
144 command: args.join(' '),
145 );
146 }, onError: (dynamic error) {
147 globals.printError('Error sending crash report: $error');
148 });
Zachary Anderson36e8b932019-08-21 13:07:52 -0700149
James D. Linb7fd24a2020-04-28 10:34:03 -0700150 globals.printError('Oops; flutter has exited unexpectedly: "$error".');
Zachary Anderson36e8b932019-08-21 13:07:52 -0700151
152 try {
James D. Linb7fd24a2020-04-28 10:34:03 -0700153 final CrashDetails details = CrashDetails(
154 command: _crashCommand(args),
155 error: error,
156 stackTrace: stackTrace,
157 doctorText: await _doctorText(),
158 );
159 final File file = await _createLocalCrashReport(details);
160 await globals.crashReporter.informUser(details, file);
Jenn Magder7d8f8202019-11-26 14:06:31 -0800161
Zachary Anderson36e8b932019-08-21 13:07:52 -0700162 return _exit(1);
Zachary Anderson6c408a02020-03-06 10:22:12 -0800163 // This catch catches all exceptions to ensure the message below is printed.
164 } catch (error) { // ignore: avoid_catches_without_on_clauses
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800165 globals.stdio.stderrWrite(
Zachary Anderson36e8b932019-08-21 13:07:52 -0700166 'Unable to generate crash report due to secondary error: $error\n'
James D. Linb7fd24a2020-04-28 10:34:03 -0700167 '${globals.userMessages.flutterToolBugInstructions}\n');
168 // Any exception thrown here (including one thrown by `_exit()`) will
Zachary Anderson36e8b932019-08-21 13:07:52 -0700169 // get caught by our zone's `onError` handler. In order to avoid an
170 // infinite error loop, we throw an error that is recognized above
171 // and will trigger an immediate exit.
172 throw ProcessExit(1, immediate: true);
Todd Volkert454db9d2017-11-09 21:45:31 -0800173 }
174 }
175}
176
Jenn Magder7d8f8202019-11-26 14:06:31 -0800177String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}';
178
179String _crashException(dynamic error) => '${error.runtimeType}: $error';
180
Todd Volkert454db9d2017-11-09 21:45:31 -0800181/// Saves the crash report to a local file.
James D. Linb7fd24a2020-04-28 10:34:03 -0700182Future<File> _createLocalCrashReport(CrashDetails details) async {
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800183 File crashFile = globals.fsUtils.getUniqueFile(
Jenn Magder8109dcc2020-04-20 15:23:09 -0700184 globals.fs.currentDirectory,
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800185 'flutter',
186 'log',
187 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800188
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200189 final StringBuffer buffer = StringBuffer();
Todd Volkert454db9d2017-11-09 21:45:31 -0800190
James D. Linb7fd24a2020-04-28 10:34:03 -0700191 buffer.writeln('Flutter crash report.');
192 buffer.writeln('${globals.userMessages.flutterToolBugInstructions}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800193
194 buffer.writeln('## command\n');
James D. Linb7fd24a2020-04-28 10:34:03 -0700195 buffer.writeln('${details.command}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800196
197 buffer.writeln('## exception\n');
James D. Linb7fd24a2020-04-28 10:34:03 -0700198 buffer.writeln('${_crashException(details.error)}\n');
199 buffer.writeln('```\n${details.stackTrace}```\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800200
201 buffer.writeln('## flutter doctor\n');
James D. Linb7fd24a2020-04-28 10:34:03 -0700202 buffer.writeln('```\n${details.doctorText}```');
Todd Volkert454db9d2017-11-09 21:45:31 -0800203
204 try {
Zachary Anderson398ac1f2019-08-20 13:15:08 -0700205 crashFile.writeAsStringSync(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800206 } on FileSystemException catch (_) {
207 // Fallback to the system temporary directory.
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800208 crashFile = globals.fsUtils.getUniqueFile(
Jenn Magder8109dcc2020-04-20 15:23:09 -0700209 globals.fs.systemTempDirectory,
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800210 'flutter',
211 'log',
212 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800213 try {
Zachary Anderson398ac1f2019-08-20 13:15:08 -0700214 crashFile.writeAsStringSync(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800215 } on FileSystemException catch (e) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800216 globals.printError('Could not write crash report to disk: $e');
217 globals.printError(buffer.toString());
Todd Volkert454db9d2017-11-09 21:45:31 -0800218 }
219 }
220
221 return crashFile;
222}
223
224Future<String> _doctorText() async {
225 try {
Jonah Williamsf7b8d622020-01-08 12:35:12 -0800226 final BufferLogger logger = BufferLogger(
Zachary Anderson23a3d102020-01-13 10:12:06 -0800227 terminal: globals.terminal,
Jenn Magdere0ab6fc2020-03-18 15:54:26 -0700228 outputPreferences: globals.outputPreferences,
Jonah Williamsf7b8d622020-01-08 12:35:12 -0800229 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800230
Jenn Magder457972b2020-04-15 19:44:42 -0700231 final Doctor doctor = Doctor(logger: logger);
232 await doctor.diagnose(verbose: true, showColor: false);
Todd Volkert454db9d2017-11-09 21:45:31 -0800233
234 return logger.statusText;
Zachary Anderson6c408a02020-03-06 10:22:12 -0800235 } on Exception catch (error, trace) {
Todd Volkert454db9d2017-11-09 21:45:31 -0800236 return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
237 }
238}
239
240Future<int> _exit(int code) async {
Zachary Anderson372fe292019-11-05 10:43:52 -0800241 // Prints the welcome message if needed.
Jenn Magderf6a55122020-03-06 12:14:29 -0800242 globals.flutterUsage.printWelcome();
Todd Volkert454db9d2017-11-09 21:45:31 -0800243
244 // Send any last analytics calls that are in progress without overly delaying
245 // the tool's exit (we wait a maximum of 250ms).
Jenn Magderf6a55122020-03-06 12:14:29 -0800246 if (globals.flutterUsage.enabled) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200247 final Stopwatch stopwatch = Stopwatch()..start();
Jenn Magderf6a55122020-03-06 12:14:29 -0800248 await globals.flutterUsage.ensureAnalyticsSent();
Zachary Anderson23a3d102020-01-13 10:12:06 -0800249 globals.printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
Todd Volkert454db9d2017-11-09 21:45:31 -0800250 }
251
252 // Run shutdown hooks before flushing logs
Jonah Williamscf903d72021-03-22 10:35:40 -0700253 await globals.shutdownHooks.runShutdownHooks();
Todd Volkert454db9d2017-11-09 21:45:31 -0800254
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200255 final Completer<void> completer = Completer<void>();
Todd Volkert454db9d2017-11-09 21:45:31 -0800256
257 // Give the task / timer queue one cycle through before we hard exit.
258 Timer.run(() {
259 try {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800260 globals.printTrace('exiting with code $code');
Todd Volkert454db9d2017-11-09 21:45:31 -0800261 exit(code);
262 completer.complete();
Jonah Williams08576cb2020-10-12 09:31:02 -0700263 // This catches all exceptions because the error is propagated on the
Zachary Anderson6c408a02020-03-06 10:22:12 -0800264 // completer.
265 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
Todd Volkert454db9d2017-11-09 21:45:31 -0800266 completer.completeError(error, stackTrace);
267 }
268 });
269
270 await completer.future;
271 return code;
272}