executable.dart 10.9 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:intl/intl_standalone.dart' as intl;
9
import 'package:meta/meta.dart';
10
import 'package:process/process.dart';
11

12
import 'src/artifacts.dart';
13
import 'src/base/common.dart';
14
import 'src/base/config.dart';
15
import 'src/base/context.dart';
16
import 'src/base/file_system.dart';
17
import 'src/base/io.dart';
18
import 'src/base/logger.dart';
19
import 'src/base/platform.dart';
20
import 'src/base/process.dart';
21
import 'src/base/terminal.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
import 'src/usage.dart';
56
import 'src/version.dart';
57

58 59
/// Main entry point for commands.
///
60
/// This function is intended to be used from the `flutter` command line tool.
61
Future<Null> main(List<String> args) async {
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
  await run(args, <FlutterCommand>[
    new AnalyzeCommand(verboseHelp: verboseHelp),
    new BuildCommand(verboseHelp: verboseHelp),
    new ChannelCommand(),
71
    new ConfigCommand(verboseHelp: verboseHelp),
72 73 74 75 76 77
    new CreateCommand(),
    new DaemonCommand(hidden: !verboseHelp),
    new DevicesCommand(),
    new DoctorCommand(),
    new DriveCommand(),
    new FormatCommand(),
78
    new FuchsiaReloadCommand(),
79 80 81 82 83 84 85
    new InstallCommand(),
    new LogsCommand(),
    new PackagesCommand(),
    new PrecacheCommand(),
    new RunCommand(verboseHelp: verboseHelp),
    new ScreenshotCommand(),
    new StopCommand(),
86
    new TestCommand(verboseHelp: verboseHelp),
87 88 89 90 91 92 93 94 95 96 97 98 99 100
    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(AnsiTerminal, () => new AnsiTerminal());
125
    context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
126
    context.putIfAbsent(Config, () => new Config());
127 128

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return crashFile;
}

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

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

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

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

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

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

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

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

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

  await completer.future;
326
  return code;
327
}