runner.dart 7.44 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:args/command_runner.dart';
8 9
import 'package:intl/intl.dart' as intl;
import 'package:intl/intl_standalone.dart' as intl_standalone;
10 11 12 13 14 15 16 17 18
import 'package:meta/meta.dart';

import 'src/base/common.dart';
import 'src/base/context.dart';
import 'src/base/file_system.dart';
import 'src/base/io.dart';
import 'src/base/logger.dart';
import 'src/base/process.dart';
import 'src/base/utils.dart';
19
import 'src/context_runner.dart';
20 21 22 23 24 25 26 27 28 29 30 31
import 'src/crash_reporting.dart';
import 'src/doctor.dart';
import 'src/globals.dart';
import 'src/runner/flutter_command.dart';
import 'src/runner/flutter_command_runner.dart';
import 'src/usage.dart';
import 'src/version.dart';

/// Runs the Flutter tool with support for the specified list of [commands].
Future<int> run(
  List<String> args,
  List<FlutterCommand> commands, {
32 33 34
  bool muteCommandLogging = false,
  bool verbose = false,
  bool verboseHelp = false,
35 36
  bool reportCrashes,
  String flutterVersion,
37
  Map<Type, Generator> overrides,
38
}) {
39 40
  reportCrashes ??= !isRunningOnBot;

41 42 43
  if (muteCommandLogging) {
    // Remove the verbose option; for help and doctor, users don't need to see
    // verbose logs.
44
    args = List<String>.from(args);
45 46 47
    args.removeWhere((String option) => option == '-v' || option == '--verbose');
  }

48
  final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
49 50
  commands.forEach(runner.addCommand);

51
  return runInContext<int>(() async {
52
    // Initialize the system locale.
53 54 55
    final String systemLocale = await intl_standalone.findSystemLocale();
    intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
      systemLocale, intl.NumberFormat.localeExists,
56
      onFailure: (String _) => 'en_US',
57
    );
58

59 60 61 62 63 64 65 66 67 68 69 70 71
    String getVersion() => flutterVersion ?? FlutterVersion.instance.getVersionString(redactUnknownBranches: true);
    return await runZoned<Future<int>>(() async {
      try {
        await runner.run(args);
        return await _exit(0);
      } catch (error, stackTrace) {
        return await _handleToolError(
            error, stackTrace, verbose, args, reportCrashes, getVersion);
      }
    }, onError: (Object error, StackTrace stackTrace) async {
      await _handleToolError(
          error, stackTrace, verbose, args, reportCrashes, getVersion);
    });
72
  }, overrides: overrides);
73 74 75
}

Future<int> _handleToolError(
76 77 78 79 80 81 82
  dynamic error,
  StackTrace stackTrace,
  bool verbose,
  List<String> args,
  bool reportCrashes,
  String getFlutterVersion(),
) async {
83
  if (error is UsageException) {
84 85
    printError('${error.message}\n');
    printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.");
86 87 88 89
    // Argument error exit code.
    return _exit(64);
  } else if (error is ToolExit) {
    if (error.message != null)
90 91 92
      printError(error.message);
    if (verbose)
      printError('\n$stackTrace\n');
93 94 95 96 97 98 99 100 101 102 103
    return _exit(error.exitCode ?? 1);
  } else if (error is ProcessExit) {
    // We've caught an exit code.
    if (error.immediate) {
      exit(error.exitCode);
      return error.exitCode;
    } else {
      return _exit(error.exitCode);
    }
  } else {
    // We've crashed; emit a log report.
104
    stderr.writeln();
105 106 107

    if (!reportCrashes) {
      // Print the stack trace on the bots - don't write a crash report.
108 109
      stderr.writeln('$error');
      stderr.writeln(stackTrace.toString());
110 111 112 113 114
      return _exit(1);
    } else {
      flutterUsage.sendException(error, stackTrace);

      if (error is String)
115
        stderr.writeln('Oops; flutter has exited unexpectedly: "$error".');
116
      else
117
        stderr.writeln('Oops; flutter has exited unexpectedly.');
118 119 120 121 122 123 124 125

      await CrashReportSender.instance.sendReport(
        error: error,
        stackTrace: stackTrace,
        getFlutterVersion: getFlutterVersion,
      );
      try {
        final File file = await _createLocalCrashReport(args, error, stackTrace);
126
        stderr.writeln(
127 128 129 130 131
          'Crash report written to ${file.path};\n'
              'please let us know at https://github.com/flutter/flutter/issues.',
        );
        return _exit(1);
      } catch (error) {
132
        stderr.writeln(
133 134 135 136 137 138 139
          'Unable to generate crash report due to secondary error: $error\n'
              'please let us know at https://github.com/flutter/flutter/issues.',
        );
        // Any exception throw here (including one thrown by `_exit()`) will
        // get caught by our zone's `onError` handler. In order to avoid an
        // infinite error loop, we throw an error that is recognized above
        // and will trigger an immediate exit.
140
        throw ProcessExit(1, immediate: true);
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
      }
    }
  }
}

/// File system used by the crash reporting logic.
///
/// We do not want to use the file system stored in the context because it may
/// be recording. Additionally, in the case of a crash we do not trust the
/// integrity of the [AppContext].
@visibleForTesting
FileSystem crashFileSystem = const LocalFileSystem();

/// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace) async {
  File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');

158
  final StringBuffer buffer = StringBuffer();
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

  buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');

  buffer.writeln('## command\n');
  buffer.writeln('flutter ${args.join(' ')}\n');

  buffer.writeln('## exception\n');
  buffer.writeln('${error.runtimeType}: $error\n');
  buffer.writeln('```\n$stackTrace```\n');

  buffer.writeln('## flutter doctor\n');
  buffer.writeln('```\n${await _doctorText()}```');

  try {
    await crashFile.writeAsString(buffer.toString());
  } on FileSystemException catch (_) {
    // Fallback to the system temporary directory.
    crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
    try {
      await crashFile.writeAsString(buffer.toString());
    } on FileSystemException catch (e) {
      printError('Could not write crash report to disk: $e');
      printError(buffer.toString());
    }
  }

  return crashFile;
}

Future<String> _doctorText() async {
  try {
190
    final BufferLogger logger = BufferLogger();
191

192
    await context.run<bool>(
193 194 195 196 197
      body: () => doctor.diagnose(verbose: true),
      overrides: <Type, Generator>{
        Logger: () => logger,
      },
    );
198 199 200 201 202 203 204 205 206 207 208 209 210 211

    return logger.statusText;
  } catch (error, trace) {
    return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
  }
}

Future<int> _exit(int code) async {
  if (flutterUsage.isFirstRun)
    flutterUsage.printWelcome();

  // Send any last analytics calls that are in progress without overly delaying
  // the tool's exit (we wait a maximum of 250ms).
  if (flutterUsage.enabled) {
212
    final Stopwatch stopwatch = Stopwatch()..start();
213 214 215 216 217 218 219
    await flutterUsage.ensureAnalyticsSent();
    printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
  }

  // Run shutdown hooks before flushing logs
  await runShutdownHooks();

220
  final Completer<void> completer = Completer<void>();
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

  // Give the task / timer queue one cycle through before we hard exit.
  Timer.run(() {
    try {
      printTrace('exiting with code $code');
      exit(code);
      completer.complete();
    } catch (error, stackTrace) {
      completer.completeError(error, stackTrace);
    }
  });

  await completer.future;
  return code;
}