executable.dart 10.7 KB
Newer Older
1 2 3 4
// 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.

5 6 7
import 'dart:async';

import 'package:args/command_runner.dart';
8
import 'package:flutter_tools/src/version.dart';
9
import 'package:intl/intl_standalone.dart' as intl;
10
import 'package:meta/meta.dart';
11
import 'package:process/process.dart';
12

13
import 'src/artifacts.dart';
14
import 'src/base/common.dart';
15
import 'src/base/config.dart';
16
import 'src/base/context.dart';
17
import 'src/base/file_system.dart';
18
import 'src/base/io.dart';
19
import 'src/base/logger.dart';
20
import 'src/base/platform.dart';
21
import 'src/base/process.dart';
Devon Carew's avatar
Devon Carew committed
22
import 'src/base/utils.dart';
23
import 'src/cache.dart';
Hixie's avatar
Hixie committed
24
import 'src/commands/analyze.dart';
25
import 'src/commands/build.dart';
26
import 'src/commands/channel.dart';
27
import 'src/commands/config.dart';
28
import 'src/commands/create.dart';
Devon Carew's avatar
Devon Carew committed
29
import 'src/commands/daemon.dart';
30
import 'src/commands/devices.dart';
31
import 'src/commands/doctor.dart';
yjbanov's avatar
yjbanov committed
32
import 'src/commands/drive.dart';
33
import 'src/commands/format.dart';
34
import 'src/commands/fuchsia_reload.dart';
35 36
import 'src/commands/install.dart';
import 'src/commands/logs.dart';
37
import 'src/commands/packages.dart';
38
import 'src/commands/precache.dart';
39
import 'src/commands/run.dart';
Devon Carew's avatar
Devon Carew committed
40
import 'src/commands/screenshot.dart';
41
import 'src/commands/stop.dart';
42
import 'src/commands/test.dart';
43
import 'src/commands/trace.dart';
44
import 'src/commands/update_packages.dart';
45
import 'src/commands/upgrade.dart';
46
import 'src/crash_reporting.dart';
47
import 'src/devfs.dart';
48
import 'src/device.dart';
49
import 'src/doctor.dart';
Devon Carew's avatar
Devon Carew committed
50
import 'src/globals.dart';
51
import 'src/ios/simulators.dart';
52
import 'src/run_hot.dart';
53
import 'src/runner/flutter_command.dart';
54
import 'src/runner/flutter_command_runner.dart';
55 56
import 'src/usage.dart';

57 58
/// Main entry point for commands.
///
59
/// This function is intended to be used from the `flutter` command line tool.
60
Future<Null> main(List<String> args) async {
61 62
  final bool verbose = args.contains('-v') || args.contains('--verbose');
  final bool help = args.contains('-h') || args.contains('--help') ||
63
      (args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose);
64
  final bool verboseHelp = help && verbose;
Devon Carew's avatar
Devon Carew committed
65

66 67 68 69 70 71 72 73 74 75 76
  await run(args, <FlutterCommand>[
    new AnalyzeCommand(verboseHelp: verboseHelp),
    new BuildCommand(verboseHelp: verboseHelp),
    new ChannelCommand(),
    new ConfigCommand(),
    new CreateCommand(),
    new DaemonCommand(hidden: !verboseHelp),
    new DevicesCommand(),
    new DoctorCommand(),
    new DriveCommand(),
    new FormatCommand(),
77
    new FuchsiaReloadCommand(),
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    new InstallCommand(),
    new LogsCommand(),
    new PackagesCommand(),
    new PrecacheCommand(),
    new RunCommand(verboseHelp: verboseHelp),
    new ScreenshotCommand(),
    new StopCommand(),
    new TestCommand(),
    new TraceCommand(),
    new UpdatePackagesCommand(hidden: !verboseHelp),
    new UpgradeCommand(),
  ], verbose: verbose, verboseHelp: verboseHelp);
}

Future<int> run(List<String> args, List<FlutterCommand> subCommands, {
  bool verbose: false,
  bool verboseHelp: false,
  bool reportCrashes,
  String flutterVersion,
}) async {
  reportCrashes ??= !isRunningOnBot;

Devon Carew's avatar
Devon Carew committed
100 101 102 103 104 105
  if (verboseHelp) {
    // Remove the verbose option; for help, users don't need to see verbose logs.
    args = new List<String>.from(args);
    args.removeWhere((String option) => option == '-v' || option == '--verbose');
  }

106
  final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
107
  subCommands.forEach(runner.addCommand);
108

109
  // Construct a context.
110
  final AppContext _executableContext = new AppContext();
111 112

  // Make the context current.
113
  return await _executableContext.runInZone(() async {
114
    // Initialize the context with some defaults.
115 116 117 118
    // NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
    // `test/src/context.dart`. If you update this list of defaults, look
    // in those locations as well to see if you need a similar update there.

119
    // Seed these context entries first since others depend on them
120 121 122
    context.putIfAbsent(Platform, () => const LocalPlatform());
    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
123
    context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
124
    context.putIfAbsent(Config, () => new Config());
125 126

    // Order-independent context entries
127 128 129 130 131
    context.putIfAbsent(DeviceManager, () => new DeviceManager());
    context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
    context.putIfAbsent(Doctor, () => new Doctor());
    context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
    context.putIfAbsent(Cache, () => new Cache());
132
    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
133 134
    context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
    context.putIfAbsent(SimControl, () => new SimControl());
135

136 137 138
    // Initialize the system locale.
    await intl.findSystemLocale();

139
    try {
140
      await runner.run(args);
141
      await _exit(0);
142
    } catch (error, stackTrace) {
143
      String getVersion() => flutterVersion ?? FlutterVersion.getVersionString();
144 145 146
      return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
    }
    return 0;
147 148 149
  });
}

150 151 152 153 154 155 156 157 158 159
/// Writes the [string] to one of the standard output streams.
@visibleForTesting
typedef void WriteCallback([String string]);

/// Writes a line to STDERR.
///
/// Overwrite this in tests to avoid spurious test output.
@visibleForTesting
WriteCallback writelnStderr = stderr.writeln;

160 161
Future<int> _handleToolError(
    dynamic error,
162
    StackTrace stackTrace,
163 164 165
    bool verbose,
    List<String> args,
    bool reportCrashes,
166
    String getFlutterVersion(),
167 168
) async {
  if (error is UsageException) {
169 170 171
    writelnStderr(error.message);
    writelnStderr();
    writelnStderr(
172 173 174 175 176 177 178
        "Run 'flutter -h' (or 'flutter <command> -h') for available "
            "flutter commands and options."
    );
    // Argument error exit code.
    return _exit(64);
  } else if (error is ToolExit) {
    if (error.message != null)
179
      writelnStderr(error.message);
180
    if (verbose) {
181
      writelnStderr();
182
      writelnStderr(stackTrace.toString());
183
      writelnStderr();
184 185 186 187
    }
    return _exit(error.exitCode ?? 1);
  } else if (error is ProcessExit) {
    // We've caught an exit code.
188 189 190 191 192 193
    if (error.immediate) {
      exit(error.exitCode);
      return error.exitCode;
    } else {
      return _exit(error.exitCode);
    }
194 195
  } else {
    // We've crashed; emit a log report.
196
    writelnStderr();
197 198 199

    if (!reportCrashes) {
      // Print the stack trace on the bots - don't write a crash report.
200
      writelnStderr('$error');
201
      writelnStderr(stackTrace.toString());
202 203
      return _exit(1);
    } else {
204
      flutterUsage.sendException(error, stackTrace);
205

206
      if (error is String)
207
        writelnStderr('Oops; flutter has exited unexpectedly: "$error".');
208
      else
209
        writelnStderr('Oops; flutter has exited unexpectedly.');
210 211 212

      await CrashReportSender.instance.sendReport(
        error: error,
213
        stackTrace: stackTrace,
214
        getFlutterVersion: getFlutterVersion,
215 216
      );
      try {
217
        final File file = await _createLocalCrashReport(args, error, stackTrace);
218
        writelnStderr(
219 220
            'Crash report written to ${file.path};\n'
                'please let us know at https://github.com/flutter/flutter/issues.',
221
        );
222 223
        return _exit(1);
      } catch (error) {
224
        writelnStderr(
225 226 227
            'Unable to generate crash report due to secondary error: $error\n'
                'please let us know at https://github.com/flutter/flutter/issues.',
        );
228 229 230 231 232
        // 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.
        throw new ProcessExit(1, immediate: true);
233
      }
234 235
    }
  }
236
}
Devon Carew's avatar
Devon Carew committed
237

238 239 240 241 242 243
/// 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
244
FileSystem crashFileSystem = const LocalFileSystem();
245 246

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

250
  final StringBuffer buffer = new StringBuffer();
Devon Carew's avatar
Devon Carew committed
251

252
  buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
Devon Carew's avatar
Devon Carew committed
253

254 255
  buffer.writeln('## command\n');
  buffer.writeln('flutter ${args.join(' ')}\n');
Devon Carew's avatar
Devon Carew committed
256

257
  buffer.writeln('## exception\n');
258
  buffer.writeln('${error.runtimeType}: $error\n');
259
  buffer.writeln('```\n$stackTrace```\n');
Devon Carew's avatar
Devon Carew committed
260

261
  buffer.writeln('## flutter doctor\n');
262
  buffer.writeln('```\n${await _doctorText()}```');
Devon Carew's avatar
Devon Carew committed
263

264
  try {
265
    await crashFile.writeAsString(buffer.toString());
266 267
  } on FileSystemException catch (_) {
    // Fallback to the system temporary directory.
268
    crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
269
    try {
270
      await crashFile.writeAsString(buffer.toString());
271 272 273 274 275
    } on FileSystemException catch (e) {
      printError('Could not write crash report to disk: $e');
      printError(buffer.toString());
    }
  }
Devon Carew's avatar
Devon Carew committed
276 277 278 279

  return crashFile;
}

280
Future<String> _doctorText() async {
Devon Carew's avatar
Devon Carew committed
281
  try {
282 283
    final BufferLogger logger = new BufferLogger();
    final AppContext appContext = new AppContext();
Devon Carew's avatar
Devon Carew committed
284

285
    appContext.setVariable(Logger, logger);
Devon Carew's avatar
Devon Carew committed
286

287
    await appContext.runInZone(() => doctor.diagnose());
Devon Carew's avatar
Devon Carew committed
288 289

    return logger.statusText;
290
  } catch (error, trace) {
291
    return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
Devon Carew's avatar
Devon Carew committed
292 293
  }
}
294

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

299 300 301
  // 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) {
302
    final Stopwatch stopwatch = new Stopwatch()..start();
303 304 305 306
    await flutterUsage.ensureAnalyticsSent();
    printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
  }

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

310
  final Completer<Null> completer = new Completer<Null>();
311

312
  // Give the task / timer queue one cycle through before we hard exit.
313
  Timer.run(() {
314 315 316 317 318 319 320
    try {
      printTrace('exiting with code $code');
      exit(code);
      completer.complete();
    } catch (error, stackTrace) {
      completer.completeError(error, stackTrace);
    }
321
  });
322 323

  await completer.future;
324
  return code;
325
}