executable.dart 10.8 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
  SigintProcessSignal.instance.init();
62 63
  final bool verbose = args.contains('-v') || args.contains('--verbose');
  final bool help = args.contains('-h') || args.contains('--help') ||
64
      (args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose);
65
  final bool verboseHelp = help && verbose;
Devon Carew's avatar
Devon Carew committed
66

67 68 69 70 71 72 73 74 75 76 77
  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(),
78
    new FuchsiaReloadCommand(),
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    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
101 102 103 104 105 106
  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');
  }

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

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

  // Make the context current.
114
  return await _executableContext.runInZone(() async {
115
    // Initialize the context with some defaults.
116 117 118 119
    // 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.

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

    // Order-independent context entries
128 129 130 131 132
    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());
133
    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
134 135
    context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
    context.putIfAbsent(SimControl, () => new SimControl());
136

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

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

151 152 153 154 155 156 157 158 159 160
/// 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;

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

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

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

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

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

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

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

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

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

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

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

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

  return crashFile;
}

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

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

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

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

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

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

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

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

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

  await completer.future;
325
  return code;
326
}