blob: c58fc72fa187c6bb004ec1ae156b42f8e01a04fa [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
5import 'dart:async';
6
7import 'package:args/command_runner.dart';
Devon Carew1c6078c2018-05-24 13:42:46 -07008import 'package:intl/intl.dart' as intl;
9import 'package:intl/intl_standalone.dart' as intl_standalone;
Todd Volkert454db9d2017-11-09 21:45:31 -080010import 'package:meta/meta.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080011
Todd Volkert454db9d2017-11-09 21:45:31 -080012import 'src/base/common.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080013import 'src/base/context.dart';
14import 'src/base/file_system.dart';
15import 'src/base/io.dart';
16import 'src/base/logger.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080017import 'src/base/process.dart';
Jonah Williamsf7b8d622020-01-08 12:35:12 -080018import 'src/base/terminal.dart';
Todd Volkert8d11f5c2018-03-28 10:58:28 -070019import 'src/context_runner.dart';
Todd Volkert454db9d2017-11-09 21:45:31 -080020import 'src/doctor.dart';
Zachary Anderson23a3d102020-01-13 10:12:06 -080021import 'src/globals.dart' as globals;
Jenn Magder7d8f8202019-11-26 14:06:31 -080022import 'src/reporting/github_template.dart';
Zachary Andersonef146f62019-07-29 07:24:02 -070023import 'src/reporting/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,
30 List<FlutterCommand> commands, {
Alexandre Ardhuin09276be2018-06-05 08:50:40 +020031 bool muteCommandLogging = false,
32 bool verbose = false,
33 bool verboseHelp = false,
Todd Volkert454db9d2017-11-09 21:45:31 -080034 bool reportCrashes,
35 String flutterVersion,
Jonah Williams81c7af32018-11-06 14:36:35 -080036 Map<Type, Generator> overrides,
Zachary Andersonb9ecebf2020-02-12 10:58:02 -080037}) async {
38 reportCrashes ??= !await globals.isRunningOnBot;
Todd Volkert454db9d2017-11-09 21:45:31 -080039
jcollins-g26102c92018-02-01 10:27:55 -080040 if (muteCommandLogging) {
41 // Remove the verbose option; for help and doctor, users don't need to see
42 // verbose logs.
Alexandre Ardhuind927c932018-09-12 08:29:29 +020043 args = List<String>.from(args);
Todd Volkert454db9d2017-11-09 21:45:31 -080044 args.removeWhere((String option) => option == '-v' || option == '--verbose');
45 }
46
Alexandre Ardhuind927c932018-09-12 08:29:29 +020047 final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
Todd Volkert454db9d2017-11-09 21:45:31 -080048 commands.forEach(runner.addCommand);
49
Todd Volkert8d11f5c2018-03-28 10:58:28 -070050 return runInContext<int>(() async {
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);
Zachary Andersonc7596da2019-07-31 13:51:19 -070059 Object firstError;
60 StackTrace firstStackTrace;
Alexander Aprelevd9179782019-06-10 09:52:22 -070061 return await runZoned<Future<int>>(() async {
62 try {
63 await runner.run(args);
64 return await _exit(0);
Zachary Anderson6c408a02020-03-06 10:22:12 -080065 // This catches all exceptions to send to crash logging, etc.
66 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
Zachary Andersonc7596da2019-07-31 13:51:19 -070067 firstError = error;
68 firstStackTrace = stackTrace;
Alexander Aprelevd9179782019-06-10 09:52:22 -070069 return await _handleToolError(
70 error, stackTrace, verbose, args, reportCrashes, getVersion);
71 }
72 }, onError: (Object error, StackTrace stackTrace) async {
Zachary Andersonc7596da2019-07-31 13:51:19 -070073 // If sending a crash report throws an error into the zone, we don't want
74 // to re-try sending the crash report with *that* error. Rather, we want
75 // to send the original error that triggered the crash report.
76 final Object e = firstError ?? error;
77 final StackTrace s = firstStackTrace ?? stackTrace;
78 await _handleToolError(e, s, verbose, args, reportCrashes, getVersion);
Alexander Aprelevd9179782019-06-10 09:52:22 -070079 });
Jonah Williams81c7af32018-11-06 14:36:35 -080080 }, overrides: overrides);
Todd Volkert454db9d2017-11-09 21:45:31 -080081}
82
Todd Volkert454db9d2017-11-09 21:45:31 -080083Future<int> _handleToolError(
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010084 dynamic error,
85 StackTrace stackTrace,
86 bool verbose,
87 List<String> args,
88 bool reportCrashes,
89 String getFlutterVersion(),
90) async {
Todd Volkert454db9d2017-11-09 21:45:31 -080091 if (error is UsageException) {
Zachary Anderson23a3d102020-01-13 10:12:06 -080092 globals.printError('${error.message}\n');
93 globals.printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.");
Todd Volkert454db9d2017-11-09 21:45:31 -080094 // Argument error exit code.
95 return _exit(64);
96 } else if (error is ToolExit) {
Zachary Andersone2340c62019-09-13 14:51:35 -070097 if (error.message != null) {
Zachary Anderson23a3d102020-01-13 10:12:06 -080098 globals.printError(error.message);
Zachary Andersone2340c62019-09-13 14:51:35 -070099 }
100 if (verbose) {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800101 globals.printError('\n$stackTrace\n');
Zachary Andersone2340c62019-09-13 14:51:35 -0700102 }
Todd Volkert454db9d2017-11-09 21:45:31 -0800103 return _exit(error.exitCode ?? 1);
104 } else if (error is ProcessExit) {
105 // We've caught an exit code.
106 if (error.immediate) {
107 exit(error.exitCode);
108 return error.exitCode;
109 } else {
110 return _exit(error.exitCode);
111 }
112 } else {
113 // We've crashed; emit a log report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800114 globals.stdio.stderrWrite('\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800115
116 if (!reportCrashes) {
117 // Print the stack trace on the bots - don't write a crash report.
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800118 globals.stdio.stderrWrite('$error\n');
119 globals.stdio.stderrWrite('$stackTrace\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800120 return _exit(1);
Zachary Anderson36e8b932019-08-21 13:07:52 -0700121 }
122
123 // Report to both [Usage] and [CrashReportSender].
Jenn Magderf6a55122020-03-06 12:14:29 -0800124 globals.flutterUsage.sendException(error);
Zachary Anderson36e8b932019-08-21 13:07:52 -0700125 await CrashReportSender.instance.sendReport(
126 error: error,
127 stackTrace: stackTrace,
128 getFlutterVersion: getFlutterVersion,
129 command: args.join(' '),
130 );
131
Jenn Magder7d8f8202019-11-26 14:06:31 -0800132 final String errorString = error.toString();
Zachary Anderson23a3d102020-01-13 10:12:06 -0800133 globals.printError('Oops; flutter has exited unexpectedly: "$errorString".');
Zachary Anderson36e8b932019-08-21 13:07:52 -0700134
135 try {
Jenn Magder7d8f8202019-11-26 14:06:31 -0800136 await _informUserOfCrash(args, error, stackTrace, errorString);
137
Zachary Anderson36e8b932019-08-21 13:07:52 -0700138 return _exit(1);
Zachary Anderson6c408a02020-03-06 10:22:12 -0800139 // This catch catches all exceptions to ensure the message below is printed.
140 } catch (error) { // ignore: avoid_catches_without_on_clauses
Zachary Anderson68ed5c22020-01-28 07:58:02 -0800141 globals.stdio.stderrWrite(
Zachary Anderson36e8b932019-08-21 13:07:52 -0700142 'Unable to generate crash report due to secondary error: $error\n'
Zachary Anderson93a5b7d2020-01-25 01:23:01 -0800143 'please let us know at https://github.com/flutter/flutter/issues.\n',
Zachary Anderson36e8b932019-08-21 13:07:52 -0700144 );
145 // Any exception throw here (including one thrown by `_exit()`) will
146 // get caught by our zone's `onError` handler. In order to avoid an
147 // infinite error loop, we throw an error that is recognized above
148 // and will trigger an immediate exit.
149 throw ProcessExit(1, immediate: true);
Todd Volkert454db9d2017-11-09 21:45:31 -0800150 }
151 }
152}
153
Jenn Magder7d8f8202019-11-26 14:06:31 -0800154Future<void> _informUserOfCrash(List<String> args, dynamic error, StackTrace stackTrace, String errorString) async {
155 final String doctorText = await _doctorText();
156 final File file = await _createLocalCrashReport(args, error, stackTrace, doctorText);
157
Zachary Anderson23a3d102020-01-13 10:12:06 -0800158 globals.printError('A crash report has been written to ${file.path}.');
159 globals.printStatus('This crash may already be reported. Check GitHub for similar crashes.', emphasis: true);
Jenn Magder7d8f8202019-11-26 14:06:31 -0800160
161 final GitHubTemplateCreator gitHubTemplateCreator = context.get<GitHubTemplateCreator>() ?? GitHubTemplateCreator();
162 final String similarIssuesURL = await gitHubTemplateCreator.toolCrashSimilarIssuesGitHubURL(errorString);
Zachary Anderson23a3d102020-01-13 10:12:06 -0800163 globals.printStatus('$similarIssuesURL\n', wrap: false);
164 globals.printStatus('To report your crash to the Flutter team, first read the guide to filing a bug.', emphasis: true);
165 globals.printStatus('https://flutter.dev/docs/resources/bug-reports\n', wrap: false);
166 globals.printStatus('Create a new GitHub issue by pasting this link into your browser and completing the issue template. Thank you!', emphasis: true);
Jenn Magder7d8f8202019-11-26 14:06:31 -0800167
168 final String command = _crashCommand(args);
169 final String gitHubTemplateURL = await gitHubTemplateCreator.toolCrashIssueTemplateGitHubURL(
170 command,
171 errorString,
172 _crashException(error),
173 stackTrace,
174 doctorText
175 );
Zachary Anderson23a3d102020-01-13 10:12:06 -0800176 globals.printStatus('$gitHubTemplateURL\n', wrap: false);
Jenn Magder7d8f8202019-11-26 14:06:31 -0800177}
178
179String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}';
180
181String _crashException(dynamic error) => '${error.runtimeType}: $error';
182
Todd Volkert454db9d2017-11-09 21:45:31 -0800183/// File system used by the crash reporting logic.
184///
185/// We do not want to use the file system stored in the context because it may
186/// be recording. Additionally, in the case of a crash we do not trust the
187/// integrity of the [AppContext].
188@visibleForTesting
189FileSystem crashFileSystem = const LocalFileSystem();
190
191/// Saves the crash report to a local file.
Jenn Magder7d8f8202019-11-26 14:06:31 -0800192Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace, String doctorText) async {
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800193 File crashFile = globals.fsUtils.getUniqueFile(
194 crashFileSystem.currentDirectory,
195 'flutter',
196 'log',
197 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800198
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200199 final StringBuffer buffer = StringBuffer();
Todd Volkert454db9d2017-11-09 21:45:31 -0800200
201 buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
202
203 buffer.writeln('## command\n');
Jenn Magder7d8f8202019-11-26 14:06:31 -0800204 buffer.writeln('${_crashCommand(args)}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800205
206 buffer.writeln('## exception\n');
Jenn Magder7d8f8202019-11-26 14:06:31 -0800207 buffer.writeln('${_crashException(error)}\n');
Todd Volkert454db9d2017-11-09 21:45:31 -0800208 buffer.writeln('```\n$stackTrace```\n');
209
210 buffer.writeln('## flutter doctor\n');
Jenn Magder7d8f8202019-11-26 14:06:31 -0800211 buffer.writeln('```\n$doctorText```');
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(
218 crashFileSystem.systemTempDirectory,
219 '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
233Future<String> _doctorText() async {
234 try {
Jonah Williamsf7b8d622020-01-08 12:35:12 -0800235 final BufferLogger logger = BufferLogger(
Zachary Anderson23a3d102020-01-13 10:12:06 -0800236 terminal: globals.terminal,
Jonah Williamsf7b8d622020-01-08 12:35:12 -0800237 outputPreferences: outputPreferences,
238 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800239
Todd Volkertf8058d72018-03-28 15:17:29 -0700240 await context.run<bool>(
Jenn Magder7d8f8202019-11-26 14:06:31 -0800241 body: () => doctor.diagnose(verbose: true, showColor: false),
Todd Volkert8d11f5c2018-03-28 10:58:28 -0700242 overrides: <Type, Generator>{
243 Logger: () => logger,
244 },
245 );
Todd Volkert454db9d2017-11-09 21:45:31 -0800246
247 return logger.statusText;
Zachary Anderson6c408a02020-03-06 10:22:12 -0800248 } on Exception catch (error, trace) {
Todd Volkert454db9d2017-11-09 21:45:31 -0800249 return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
250 }
251}
252
253Future<int> _exit(int code) async {
Zachary Anderson372fe292019-11-05 10:43:52 -0800254 // Prints the welcome message if needed.
Jenn Magderf6a55122020-03-06 12:14:29 -0800255 globals.flutterUsage.printWelcome();
Todd Volkert454db9d2017-11-09 21:45:31 -0800256
257 // Send any last analytics calls that are in progress without overly delaying
258 // the tool's exit (we wait a maximum of 250ms).
Jenn Magderf6a55122020-03-06 12:14:29 -0800259 if (globals.flutterUsage.enabled) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200260 final Stopwatch stopwatch = Stopwatch()..start();
Jenn Magderf6a55122020-03-06 12:14:29 -0800261 await globals.flutterUsage.ensureAnalyticsSent();
Zachary Anderson23a3d102020-01-13 10:12:06 -0800262 globals.printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
Todd Volkert454db9d2017-11-09 21:45:31 -0800263 }
264
265 // Run shutdown hooks before flushing logs
Zachary Anderson07161e82020-01-08 15:20:32 -0800266 await shutdownHooks.runShutdownHooks();
Todd Volkert454db9d2017-11-09 21:45:31 -0800267
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200268 final Completer<void> completer = Completer<void>();
Todd Volkert454db9d2017-11-09 21:45:31 -0800269
270 // Give the task / timer queue one cycle through before we hard exit.
271 Timer.run(() {
272 try {
Zachary Anderson23a3d102020-01-13 10:12:06 -0800273 globals.printTrace('exiting with code $code');
Todd Volkert454db9d2017-11-09 21:45:31 -0800274 exit(code);
275 completer.complete();
Zachary Anderson6c408a02020-03-06 10:22:12 -0800276 // This catches all exceptions becauce the error is propagated on the
277 // completer.
278 } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses
Todd Volkert454db9d2017-11-09 21:45:31 -0800279 completer.completeError(error, stackTrace);
280 }
281 });
282
283 await completer.future;
284 return code;
285}