run.dart 27.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8
import 'dart:async';

9 10 11
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart';

12
import '../android/android_device.dart';
13
import '../base/common.dart';
14
import '../base/file_system.dart';
15
import '../base/utils.dart';
16
import '../build_info.dart';
17
import '../device.dart';
18
import '../features.dart';
19
import '../globals.dart' as globals;
20
import '../project.dart';
21
import '../reporting/reporting.dart';
22
import '../resident_runner.dart';
23 24
import '../run_cold.dart';
import '../run_hot.dart';
25
import '../runner/flutter_command.dart';
26
import '../tracing.dart';
27
import '../vmservice.dart';
28
import '../web/web_runner.dart';
29
import 'daemon.dart';
30

31
/// Shared logic between `flutter run` and `flutter drive` commands.
32
abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
33 34
  RunCommandBase({ @required bool verboseHelp }) {
    addBuildModeFlags(verboseHelp: verboseHelp, defaultToRelease: false);
35
    usesDartDefineOption();
36
    usesFlavorOption();
37
    usesWebRendererOption();
38
    addNativeNullAssertions(hide: !verboseHelp);
39 40
    argParser
      ..addFlag('trace-startup',
41
        negatable: false,
42 43 44 45
        help: 'Trace application startup, then exit, saving the trace to a file. '
              'By default, this will be saved in the "build" directory. If the '
              'FLUTTER_TEST_OUTPUTS_DIR environment variable is set, the file '
              'will be written there instead.',
46
      )
47 48
      ..addFlag('verbose-system-logs',
        negatable: false,
49
        help: 'Include verbose logging from the Flutter engine.',
50
      )
51 52
      ..addFlag('cache-sksl',
        negatable: false,
53
        help: 'Cache the shader in the SkSL format instead of in binary or GLSL formats.',
54 55 56 57
      )
      ..addFlag('dump-skp-on-shader-compilation',
        negatable: false,
        help: 'Automatically dump the skp that triggers new shader compilations. '
58 59 60
              'This is useful for writing custom ShaderWarmUp to reduce jank. '
              'By default, this is not enabled as it introduces significant overhead. '
              'This is only available in profile or debug builds.',
61
      )
62 63 64
      ..addFlag('purge-persistent-cache',
        negatable: false,
        help: 'Removes all existing persistent caches. This allows reproducing '
65 66 67
              'shader compilation jank that normally only happens the first time '
              'an app is run, or for reliable testing of compilation jank fixes '
              '(e.g. shader warm-up).',
68
      )
69 70
      ..addOption('route',
        help: 'Which route to load when running the app.',
71 72
      )
      ..addOption('vmservice-out-file',
73 74 75 76
        help: 'A file to write the attached vmservice URL to after an '
              'application is started.',
        valueHelp: 'project/example/out.txt',
        hide: !verboseHelp,
77 78 79 80
      )
      ..addFlag('disable-service-auth-codes',
        negatable: false,
        hide: !verboseHelp,
81 82
        help: '(deprecated) Allow connections to the VM service without using authentication codes. '
              '(Not recommended! This can open your device to remote code execution attacks!)'
83 84
      )
      ..addOption('use-application-binary',
85 86 87
        help: 'Specify a pre-built application binary to use when running. For Android applications, '
              'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types '
              'do not yet support prebuilt application binaries.',
88
        valueHelp: 'path/to/app.apk',
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
      )
      ..addFlag('start-paused',
        defaultsTo: startPausedDefault,
        help: 'Start in a paused mode and wait for a debugger to connect.',
      )
      ..addOption('dart-flags',
        hide: !verboseHelp,
        help: 'Pass a list of comma separated flags to the Dart instance at '
              'application startup. Flags passed through this option must be '
              'present on the allowlist defined within the Flutter engine. If '
              'a disallowed flag is encountered, the process will be '
              'terminated immediately.\n\n'
              'This flag is not available on the stable channel and is only '
              'applied in debug and profile modes. This option should only '
              'be used for experiments and should not be used by typical users.'
      )
      ..addFlag('endless-trace-buffer',
        negatable: false,
107 108 109
        help: 'Enable tracing to an infinite buffer, instead of a ring buffer. '
              'This is useful when recording large traces. To use an endless buffer to '
              'record startup traces, combine this with "--trace-startup".',
110 111 112 113 114 115 116 117 118 119
      )
      ..addFlag('trace-systrace',
        negatable: false,
        help: 'Enable tracing to the system tracer. This is only useful on '
              'platforms where such a tracer is available (Android and Fuchsia).',
      )
      ..addFlag('trace-skia',
        negatable: false,
        help: 'Enable tracing of Skia code. This is useful when debugging '
              'the raster thread (formerly known as the GPU thread). '
120 121
              'By default, Flutter will not log Skia code, as it introduces significant '
              'overhead that may affect recorded performance metrics in a misleading way.',
122 123
      )
      ..addOption('trace-allowlist',
124
        hide: !verboseHelp,
125
        help: 'Filters out all trace events except those that are specified in '
126
              'this comma separated list of allowed prefixes.',
127
        valueHelp: 'foo,bar',
128 129 130 131 132 133 134
      )
      ..addMultiOption('dart-entrypoint-args',
        abbr: 'a',
        help: 'Pass a list of arguments to the Dart entrypoint at application '
              'startup. By default this is main(List<String> args). Specify '
              'this option multiple times each with one argument to pass '
              'multiple arguments to the Dart entrypoint. Currently this is '
135 136 137
              'only supported on desktop platforms.'
    );
    usesWebOptions(verboseHelp: verboseHelp);
138
    usesTargetOption();
139 140
    usesPortOptions(verboseHelp: verboseHelp);
    usesIpv6Flag(verboseHelp: verboseHelp);
141
    usesPubOption();
142
    usesTrackWidgetCreation(verboseHelp: verboseHelp);
143
    addNullSafetyModeOptions(hide: !verboseHelp);
144
    usesDeviceUserOption();
145
    usesDeviceTimeoutOption();
146
    addDdsOptions(verboseHelp: verboseHelp);
147
    addDevToolsOptions(verboseHelp: verboseHelp);
148
    addAndroidSpecificBuildOptions(hide: !verboseHelp);
149
  }
150

151 152 153
  bool get traceStartup => boolArg('trace-startup');
  bool get cacheSkSL => boolArg('cache-sksl');
  bool get dumpSkpOnShaderCompilation => boolArg('dump-skp-on-shader-compilation');
154
  bool get purgePersistentCache => boolArg('purge-persistent-cache');
155
  bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes');
156 157 158
  bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null;
  bool get trackWidgetCreation => boolArg('track-widget-creation');

159 160 161
  @override
  bool get reportNullSafety => true;

162 163 164
  /// Whether to start the application paused by default.
  bool get startPausedDefault;

165
  String get route => stringArg('route');
166 167 168 169

  String get traceAllowlist => stringArg('trace-allowlist');

  /// Create a debugging options instance for the current `run` or `drive` invocation.
170
  Future<DebuggingOptions> createDebuggingOptions(bool webMode) async {
171
    final BuildInfo buildInfo = await getBuildInfo();
172 173 174 175 176 177
    final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
      ? int.parse(stringArg('web-browser-debug-port'))
      : null;
    if (buildInfo.mode.isRelease) {
      return DebuggingOptions.disabled(
        buildInfo,
178
        dartEntrypointArgs: stringsArg('dart-entrypoint-args'),
179 180 181 182
        hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
        port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
        webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
        webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
183
        webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse',
184 185 186 187 188 189 190 191 192 193
        webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
        webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
        webBrowserDebugPort: browserDebugPort,
      );
    } else {
      return DebuggingOptions.enabled(
        buildInfo,
        startPaused: boolArg('start-paused'),
        disableServiceAuthCodes: boolArg('disable-service-auth-codes'),
        disableDds: boolArg('disable-dds'),
194
        dartEntrypointArgs: stringsArg('dart-entrypoint-args'),
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        dartFlags: stringArg('dart-flags') ?? '',
        useTestFonts: argParser.options.containsKey('use-test-fonts') && boolArg('use-test-fonts'),
        enableSoftwareRendering: argParser.options.containsKey('enable-software-rendering') && boolArg('enable-software-rendering'),
        skiaDeterministicRendering: argParser.options.containsKey('skia-deterministic-rendering') && boolArg('skia-deterministic-rendering'),
        traceSkia: boolArg('trace-skia'),
        traceAllowlist: traceAllowlist,
        traceSystrace: boolArg('trace-systrace'),
        endlessTraceBuffer: boolArg('endless-trace-buffer'),
        dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation,
        cacheSkSL: cacheSkSL,
        purgePersistentCache: purgePersistentCache,
        deviceVmServicePort: deviceVmservicePort,
        hostVmServicePort: hostVmservicePort,
        disablePortPublication: disablePortPublication,
        ddsPort: ddsPort,
210
        devToolsServerAddress: devToolsServerAddress,
211 212 213 214 215
        verboseSystemLogs: boolArg('verbose-system-logs'),
        hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
        port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
        webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse',
        webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse',
216
        webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse',
217 218 219 220 221 222 223 224 225
        webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'),
        webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
        webBrowserDebugPort: browserDebugPort,
        webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'),
        vmserviceOutFile: stringArg('vmservice-out-file'),
        fastStart: argParser.options.containsKey('fast-start')
          && boolArg('fast-start')
          && !runningWithPrebuiltApplication,
        nullAssertions: boolArg('null-assertions'),
226
        nativeNullAssertions: boolArg('native-null-assertions'),
227 228 229
      );
    }
  }
230
}
231

232
class RunCommand extends RunCommandBase {
233
  RunCommand({ bool verboseHelp = false }) : super(verboseHelp: verboseHelp) {
234
    requiresPubspecYaml();
235
    usesFilesystemOptions(hide: !verboseHelp);
236
    usesExtraDartFlagOptions(verboseHelp: verboseHelp);
237
    addEnableExperimentation(hide: !verboseHelp);
238 239 240 241 242

    // By default, the app should to publish the VM service port over mDNS.
    // This will allow subsequent "flutter attach" commands to connect to the VM
    // without needing to know the port.
    addPublishPort(enabledByDefault: true, verboseHelp: verboseHelp);
243 244
    argParser
      ..addFlag('enable-software-rendering',
245
        negatable: false,
246 247 248 249
        help: 'Enable rendering using the Skia software backend. '
              'This is useful when testing Flutter on emulators. By default, '
              'Flutter will attempt to either use OpenGL or Vulkan and fall back '
              'to software when neither is available.',
250 251
      )
      ..addFlag('skia-deterministic-rendering',
252
        negatable: false,
253 254 255
        help: 'When combined with "--enable-software-rendering", this should provide completely '
              'deterministic (i.e. reproducible) Skia rendering. This is useful for testing purposes '
              '(e.g. when comparing screenshots).',
256
      )
257 258 259 260 261 262
      ..addFlag('await-first-frame-when-tracing',
        defaultsTo: true,
        help: 'Whether to wait for the first frame when tracing startup ("--trace-startup"), '
              'or just dump the trace as soon as the application is running. The first frame '
              'is detected by looking for a Timeline event with the name '
              '"${Tracing.firstUsefulFrameEventName}". '
263
              'By default, the widgets library\'s binding takes care of sending this event.',
264
      )
265
      ..addFlag('use-test-fonts',
266
        negatable: true,
267 268 269 270
        help: 'Enable (and default to) the "Ahem" font. This is a special font '
              'used in tests to remove any dependencies on the font metrics. It '
              'is enabled when you use "flutter test". Set this flag when running '
              'a test using "flutter run" for debugging purposes. This flag is '
271 272 273
              'only available when running in debug mode.',
      )
      ..addFlag('build',
274
        defaultsTo: true,
275 276 277
        help: 'If necessary, build the app before running.',
      )
      ..addOption('project-root',
278
        hide: !verboseHelp,
279 280 281
        help: 'Specify the project root directory.',
      )
      ..addFlag('machine',
282
        hide: !verboseHelp,
283
        negatable: false,
284
        help: 'Handle machine structured JSON command input and provide output '
285 286 287
              'and progress in machine friendly format.',
      )
      ..addFlag('hot',
288 289
        negatable: true,
        defaultsTo: kHotReloadDefault,
290
        help: 'Run with support for hot reloading. Only available for debug mode. Not available with "--trace-startup".',
291 292
      )
      ..addFlag('resident',
293 294 295
        negatable: true,
        defaultsTo: true,
        hide: !verboseHelp,
296 297 298
        help: 'Stay resident after launching the application. Not available with "--trace-startup".',
      )
      ..addOption('pid-file',
299
        help: 'Specify a file to write the process ID to. '
300
              'You can send SIGUSR1 to trigger a hot reload '
301 302 303 304 305 306 307
              'and SIGUSR2 to trigger a hot restart. '
              'The file is created when the signal handlers '
              'are hooked and deleted when they are removed.',
      )..addFlag(
        'report-ready',
        help: 'Print "ready" to the console after handling a keyboard command.\n'
              'This is primarily useful for tests and other automation, but consider '
308
              'using "--machine" instead.',
309 310
        hide: !verboseHelp,
      )..addFlag('benchmark',
311
        negatable: false,
312
        hide: !verboseHelp,
313 314 315
        help: 'Enable a benchmarking mode. This will run the given application, '
              'measure the startup time and the app restart time, write the '
              'results out to "refresh_benchmark.json", and exit. This flag is '
316 317
              'intended for use in generating automated flutter benchmarks.',
      )
318 319 320
      // TODO(jonahwilliams): Off by default with investigating whether this
      // is slower for certain use cases.
      // See: https://github.com/flutter/flutter/issues/49499
321 322
      ..addFlag('fast-start',
        negatable: true,
323
        defaultsTo: false,
324 325
        help: 'Whether to quickly bootstrap applications with a minimal app. '
              'Currently this is only supported on Android devices. This option '
326 327
              'cannot be paired with "--use-application-binary".',
        hide: !verboseHelp,
328
      );
329 330
  }

331 332 333 334 335 336
  @override
  final String name = 'run';

  @override
  final String description = 'Run your Flutter app on an attached device.';

337
  List<Device> devices;
338
  bool webMode = false;
339

340 341
  String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);

342 343 344
  @override
  bool get startPausedDefault => false;

345
  @override
346
  Future<String> get usagePath async {
347
    final String command = await super.usagePath;
348

349
    if (devices == null) {
350
      return command;
351 352
    }
    if (devices.length > 1) {
353
      return '$command/all';
354 355
    }
    return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}';
356 357
  }

358
  @override
359
  Future<Map<CustomDimensions, String>> get usageValues async {
360
    String deviceType, deviceOsVersion;
361
    bool isEmulator;
362 363
    bool anyAndroidDevices = false;
    bool anyIOSDevices = false;
364 365 366 367 368 369

    if (devices == null || devices.isEmpty) {
      deviceType = 'none';
      deviceOsVersion = 'none';
      isEmulator = false;
    } else if (devices.length == 1) {
370 371 372 373
      final TargetPlatform platform = await devices[0].targetPlatform;
      anyAndroidDevices = platform == TargetPlatform.android;
      anyIOSDevices = platform == TargetPlatform.ios;
      deviceType = getNameForTargetPlatform(platform);
374
      deviceOsVersion = await devices[0].sdkNameAndVersion;
375
      isEmulator = await devices[0].isLocalEmulator;
376 377 378
    } else {
      deviceType = 'multiple';
      deviceOsVersion = 'multiple';
379
      isEmulator = false;
380
      for (final Device device in devices) {
381 382 383 384 385 386 387
        final TargetPlatform platform = await device.targetPlatform;
        anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android);
        anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios);
        if (anyAndroidDevices && anyIOSDevices) {
          break;
        }
      }
388
    }
389

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
    String androidEmbeddingVersion;
    final List<String> hostLanguage = <String>[];
    if (anyAndroidDevices) {
      final AndroidProject androidProject = FlutterProject.current().android;
      if (androidProject != null && androidProject.existsSync()) {
        hostLanguage.add(androidProject.isKotlin ? 'kotlin' : 'java');
        androidEmbeddingVersion = androidProject.getEmbeddingVersion().toString().split('.').last;
      }
    }
    if (anyIOSDevices) {
      final IosProject iosProject = FlutterProject.current().ios;
      if (iosProject != null && iosProject.exists) {
        final Iterable<File> swiftFiles = iosProject.hostAppRoot
            .listSync(recursive: true, followLinks: false)
            .whereType<File>()
405
            .where((File file) => globals.fs.path.extension(file.path) == '.swift');
406 407 408 409
        hostLanguage.add(swiftFiles.isNotEmpty ? 'swift' : 'objc');
      }
    }

410
    final BuildInfo buildInfo = await getBuildInfo();
411
    final String modeName = buildInfo.modeName;
412 413 414 415 416 417 418
    return <CustomDimensions, String>{
      CustomDimensions.commandRunIsEmulator: '$isEmulator',
      CustomDimensions.commandRunTargetName: deviceType,
      CustomDimensions.commandRunTargetOsVersion: deviceOsVersion,
      CustomDimensions.commandRunModeName: modeName,
      CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}',
      CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','),
419 420
      if (androidEmbeddingVersion != null)
        CustomDimensions.commandRunAndroidEmbeddingVersion: androidEmbeddingVersion,
421
    };
422 423
  }

424 425 426
  @override
  bool get shouldRunPub {
    // If we are running with a prebuilt application, do not run pub.
427
    if (runningWithPrebuiltApplication) {
428
      return false;
429
    }
430 431 432 433

    return super.shouldRunPub;
  }

434
  bool shouldUseHotMode(BuildInfo buildInfo) {
435
    final bool hotArg = boolArg('hot') ?? false;
436
    final bool shouldUseHotMode = hotArg && !traceStartup;
437
    return buildInfo.isDebug && shouldUseHotMode;
438 439
  }

440 441
  bool get stayResident => boolArg('resident');
  bool get awaitFirstFrameWhenTracing => boolArg('await-first-frame-when-tracing');
442

443
  @override
444
  Future<void> validateCommand() async {
445 446
    // When running with a prebuilt application, no command validation is
    // necessary.
447
    if (!runningWithPrebuiltApplication) {
448
      await super.validateCommand();
449
    }
450

451 452 453 454
    devices = await findAllTargetDevices();
    if (devices == null) {
      throwToolExit(null);
    }
455
    if (globals.deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
456
      throwToolExit('Using "-d all" with "--use-application-binary" is not supported');
457
    }
458 459 460 461 462 463 464

    if (userIdentifier != null
      && devices.every((Device device) => device is! AndroidDevice)) {
      throwToolExit(
        '--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.'
      );
    }
465 466 467 468 469
    // Only support "web mode" with a single web device due to resident runner
    // refactoring required otherwise.
    webMode = featureFlags.isWebEnabled &&
      devices.length == 1  &&
      await devices.single.targetPlatform == TargetPlatform.web_javascript;
470 471
  }

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
  @visibleForTesting
  Future<ResidentRunner> createRunner({
    @required bool hotMode,
    @required List<FlutterDevice> flutterDevices,
    @required String applicationBinaryPath,
    @required FlutterProject flutterProject,
  }) async {
    if (hotMode && !webMode) {
      return HotRunner(
        flutterDevices,
        target: targetFile,
        debuggingOptions: await createDebuggingOptions(webMode),
        benchmarkMode: boolArg('benchmark'),
        applicationBinary: applicationBinaryPath == null
            ? null
            : globals.fs.file(applicationBinaryPath),
        projectRootPath: stringArg('project-root'),
        dillOutputPath: stringArg('output-dill'),
        stayResident: stayResident,
        ipv6: ipv6,
      );
    } else if (webMode) {
      return webRunnerFactory.createWebRunner(
        flutterDevices.single,
        target: targetFile,
        flutterProject: flutterProject,
        ipv6: ipv6,
        debuggingOptions: await createDebuggingOptions(webMode),
        stayResident: stayResident,
        urlTunneller: null,
502 503 504 505
        fileSystem: globals.fs,
        usage: globals.flutterUsage,
        logger: globals.logger,
        systemClock: globals.systemClock,
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
      );
    }
    return ColdRunner(
      flutterDevices,
      target: targetFile,
      debuggingOptions: await createDebuggingOptions(webMode),
      traceStartup: traceStartup,
      awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
      applicationBinary: applicationBinaryPath == null
          ? null
          : globals.fs.file(applicationBinaryPath),
      ipv6: ipv6,
      stayResident: stayResident,
    );
  }

522
  @override
523
  Future<FlutterCommandResult> runCommand() async {
524 525
    // Enable hot mode by default if `--no-hot` was not passed and we are in
    // debug mode.
526
    final BuildInfo buildInfo = await getBuildInfo();
527
    final bool hotMode = shouldUseHotMode(buildInfo);
528
    final String applicationBinaryPath = stringArg('use-application-binary');
529

530
    if (boolArg('machine')) {
531
      if (devices.length > 1) {
532
        throwToolExit('"--machine" does not support "-d all".');
533
      }
534 535 536
      final Daemon daemon = Daemon(
        stdinCommandStream,
        stdoutCommandResponse,
537 538
        notifyingLogger: (globals.logger is NotifyingLogger)
          ? globals.logger as NotifyingLogger
539
          : NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
540 541
        logToStdout: true,
      );
542 543
      AppInstance app;
      try {
544
        app = await daemon.appDomain.startApp(
545
          devices.first, globals.fs.currentDirectory.path, targetFile, route,
546
          await createDebuggingOptions(webMode), hotMode,
547 548
          applicationBinary: applicationBinaryPath == null
              ? null
549
              : globals.fs.file(applicationBinaryPath),
550
          trackWidgetCreation: trackWidgetCreation,
551 552 553
          projectRootPath: stringArg('project-root'),
          packagesFilePath: globalResults['packages'] as String,
          dillOutputPath: stringArg('output-dill'),
554
          ipv6: ipv6,
555
          machine: true,
556
        );
557
      } on Exception catch (error) {
558 559
        throwToolExit(error.toString());
      }
560
      final DateTime appStartedTime = globals.systemClock.now();
561
      final int result = await app.runner.waitForAppToFinish();
562
      if (result != 0) {
563
        throwToolExit(null, exitCode: result);
564
      }
565
      return FlutterCommandResult(
566
        ExitStatus.success,
567
        timingLabelParts: <String>['daemon'],
568 569
        endTimeOverride: appStartedTime,
      );
570
    }
571
    globals.terminal.usesTerminalUi = true;
572

573
    final BuildMode buildMode = getBuildMode();
574
    for (final Device device in devices) {
575 576 577 578 579
      if (!await device.supportsRuntimeMode(buildMode)) {
        throwToolExit(
          '${toTitleCase(getFriendlyModeName(buildMode))} '
          'mode is not supported by ${device.name}.',
        );
580
      }
581
      if (hotMode) {
582
        if (!device.supportsHotReload) {
583
          throwToolExit('Hot reload is not supported by ${device.name}. Run with "--no-hot".');
584
        }
585
      }
586 587 588 589 590 591 592 593 594 595 596 597 598
      if (await device.isLocalEmulator && await device.supportsHardwareRendering) {
        if (boolArg('enable-software-rendering')) {
           globals.printStatus(
            'Using software rendering with device ${device.name}. You may get better performance '
            'with hardware mode by configuring hardware rendering for your device.'
           );
        } else {
          globals.printStatus(
            'Using hardware rendering with device ${device.name}. If you notice graphics artifacts, '
            'consider enabling software rendering with "--enable-software-rendering".'
          );
        }
      }
599 600
    }

601 602
    List<String> expFlags;
    if (argParser.options.containsKey(FlutterOptions.kEnableExperiment) &&
603 604
        stringsArg(FlutterOptions.kEnableExperiment).isNotEmpty) {
      expFlags = stringsArg(FlutterOptions.kEnableExperiment);
605
    }
606
    final FlutterProject flutterProject = FlutterProject.current();
607
    final List<FlutterDevice> flutterDevices = <FlutterDevice>[
608
      for (final Device device in devices)
609 610
        await FlutterDevice.create(
          device,
611 612
          fileSystemRoots: stringsArg(FlutterOptions.kFileSystemRoot),
          fileSystemScheme: stringArg(FlutterOptions.kFileSystemScheme),
613
          experimentalFlags: expFlags,
614
          target: targetFile,
615
          buildInfo: buildInfo,
616
          userIdentifier: userIdentifier,
617
          platform: globals.platform,
618 619
        ),
    ];
620

621 622 623 624 625 626
    final ResidentRunner runner = await createRunner(
      applicationBinaryPath: applicationBinaryPath,
      flutterDevices: flutterDevices,
      flutterProject: flutterProject,
      hotMode: hotMode,
    );
627

628
    DateTime appStartedTime;
629 630
    // Sync completer so the completing agent attaching to the resident doesn't
    // need to know about analytics.
631 632
    //
    // Do not add more operations to the future.
633
    final Completer<void> appStartedTimeRecorder = Completer<void>.sync();
634 635

    TerminalHandler handler;
636
    // This callback can't throw.
637
    unawaited(appStartedTimeRecorder.future.then<void>(
638
      (_) {
639
        appStartedTime = globals.systemClock.now();
640
        if (stayResident) {
641
          handler = TerminalHandler(
642 643 644 645
            runner,
            logger: globals.logger,
            terminal: globals.terminal,
            signals: globals.signals,
646
            processInfo: globals.processInfo,
647 648
            reportReady: boolArg('report-ready'),
            pidFile: stringArg('pid-file'),
649
          )
650 651
            ..registerSignalHandlers()
            ..setupTerminal();
652 653
        }
      }
654
    ));
655 656 657 658 659 660
    try {
      final int result = await runner.run(
        appStartedCompleter: appStartedTimeRecorder,
        enableDevTools: stayResident && boolArg(FlutterCommand.kEnableDevTools),
        route: route,
      );
661
      handler?.stop();
662 663 664
      if (result != 0) {
        throwToolExit(null, exitCode: result);
      }
665 666
    } on RPCError catch (error) {
      if (error.code == RPCErrorCodes.kServiceDisappeared) {
667 668 669
        throwToolExit('Lost connection to device.');
      }
      rethrow;
670
    }
671
    return FlutterCommandResult(
672
      ExitStatus.success,
673
      timingLabelParts: <String>[
674
        if (hotMode) 'hot' else 'cold',
675
        getModeName(getBuildMode()),
676 677 678 679 680 681 682 683
        if (devices.length == 1)
          getNameForTargetPlatform(await devices[0].targetPlatform)
        else
          'multiple',
        if (devices.length == 1 && await devices[0].isLocalEmulator)
          'emulator'
        else
          null,
684 685 686
      ],
      endTimeOverride: appStartedTime,
    );
Adam Barth's avatar
Adam Barth committed
687 688
  }
}