run.dart 14.7 KB
Newer Older
1 2 3 4 5 6
// 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';

7
import '../base/common.dart';
8
import '../base/file_system.dart';
9
import '../base/io.dart';
10
import '../base/utils.dart';
11
import '../build_info.dart';
12
import '../cache.dart';
13
import '../device.dart';
14
import '../globals.dart';
15
import '../ios/mac.dart';
16
import '../resident_runner.dart';
17 18
import '../run_cold.dart';
import '../run_hot.dart';
19
import '../runner/flutter_command.dart';
20
import 'daemon.dart';
21

22
abstract class RunCommandBase extends FlutterCommand {
23
  // Used by run and drive commands.
24
  RunCommandBase() {
25
    addBuildModeFlags(defaultToRelease: false);
26
    usesFlavorOption();
27 28
    argParser
      ..addFlag('trace-startup',
29
        negatable: false,
30 31 32
        help: 'Start tracing during startup.',
      )
      ..addFlag('ipv6',
33 34 35
        hide: true,
        negatable: false,
        help: 'Binds to IPv6 localhost instead of IPv4 when the flutter tool\n'
36 37 38 39 40 41
              'forwards the host port to a device port.',
      )
      ..addOption('route',
        help: 'Which route to load when running the app.',
      )
      ..addOption('target-platform',
42 43 44 45
        defaultsTo: 'default',
        allowed: <String>['default', 'android-arm', 'android-arm64'],
        help: 'Specify the target platform when building the app for an '
              'Android device.\nIgnored on iOS.');
46
    usesTargetOption();
47 48
    usesPortOptions();
    usesPubOption();
49
  }
50 51

  bool get traceStartup => argResults['trace-startup'];
52
  bool get ipv6 => argResults['ipv6'];
53
  String get route => argResults['route'];
54 55 56 57

  void usesPortOptions() {
    argParser.addOption('observatory-port',
        help: 'Listen to the given port for an observatory debugger connection.\n'
58
              'Specifying port 0 (the default) will find a random free port.'
59 60 61 62 63 64 65 66 67 68 69 70 71
    );
  }

  int get observatoryPort {
    if (argResults['observatory-port'] != null) {
      try {
        return int.parse(argResults['observatory-port']);
      } catch (error) {
        throwToolExit('Invalid port for `--observatory-port`: $error');
      }
    }
    return null;
  }
72
}
73

74
class RunCommand extends RunCommandBase {
75
  @override
76
  final String name = 'run';
77 78

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

81
  RunCommand({ bool verboseHelp = false }) {
82 83
    requiresPubspecYaml();

84 85
    argParser
      ..addFlag('start-paused',
86
        negatable: false,
87 88 89
        help: 'Start in a paused mode and wait for a debugger to connect.',
      )
      ..addFlag('enable-software-rendering',
90 91 92 93
        negatable: false,
        help: 'Enable rendering using the Skia software backend. This is useful\n'
              'when testing Flutter on emulators. By default, Flutter will\n'
              'attempt to either use OpenGL or Vulkan and fall back to software\n'
94 95 96
              'when neither is available.',
      )
      ..addFlag('skia-deterministic-rendering',
97 98
        negatable: false,
        help: 'When combined with --enable-software-rendering, provides 100%\n'
99 100 101
              'deterministic Skia rendering.',
      )
      ..addFlag('trace-skia',
102 103
        negatable: false,
        help: 'Enable tracing of Skia code. This is useful when debugging\n'
104 105 106
              'the GPU thread. By default, Flutter will not log skia code.',
      )
      ..addFlag('use-test-fonts',
107 108 109 110 111
        negatable: true,
        help: 'Enable (and default to) the "Ahem" font. This is a special font\n'
              'used in tests to remove any dependencies on the font metrics. It\n'
              'is enabled when you use "flutter test". Set this flag when running\n'
              'a test using "flutter run" for debugging purposes. This flag is\n'
112 113 114
              'only available when running in debug mode.',
      )
      ..addFlag('build',
115
        defaultsTo: true,
116 117 118
        help: 'If necessary, build the app before running.',
      )
      ..addOption('use-application-binary',
119
        hide: !verboseHelp,
120 121
        help: 'Specify a pre-built application binary to use when running.',
      )
122 123 124 125 126
      ..addFlag('preview-dart-2',
        defaultsTo: true,
        hide: !verboseHelp,
        help: 'Preview Dart 2.0 functionality.',
      )
127 128 129 130 131 132
      ..addFlag('build-snapshot',
        hide: !verboseHelp,
        defaultsTo: false,
        help: 'Build and use application-specific VM snapshot instead of\n'
              'prebuilt one provided by the engine.',
      )
133
      ..addFlag('track-widget-creation',
134
        hide: !verboseHelp,
135 136 137
        help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
      )
      ..addOption('project-root',
138
        hide: !verboseHelp,
139 140 141
        help: 'Specify the project root directory.',
      )
      ..addFlag('machine',
142
        hide: !verboseHelp,
143 144
        negatable: false,
        help: 'Handle machine structured JSON command input and provide output\n'
145 146 147
              'and progress in machine friendly format.',
      )
      ..addFlag('hot',
148 149
        negatable: true,
        defaultsTo: kHotReloadDefault,
150 151 152
        help: 'Run with support for hot reloading.',
      )
      ..addOption('pid-file',
153 154
        help: 'Specify a file to write the process id to.\n'
              'You can send SIGUSR1 to trigger a hot reload\n'
155
              'and SIGUSR2 to trigger a hot restart.',
156 157
      )
      ..addFlag('resident',
158 159 160
        negatable: true,
        defaultsTo: true,
        hide: !verboseHelp,
161 162 163 164
        help: 'Stay resident after launching the application.',
      )
      ..addFlag('benchmark',
        negatable: false,
165
        hide: !verboseHelp,
166 167 168 169 170 171 172 173 174 175
        help: 'Enable a benchmarking mode. This will run the given application,\n'
              'measure the startup time and the app restart time, write the\n'
              'results out to "refresh_benchmark.json", and exit. This flag is\n'
              'intended for use in generating automated flutter benchmarks.',
      )
      ..addOption('output-dill',
        hide: !verboseHelp,
        help: 'Specify the path to frontend server output kernel file.',
      )
      ..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
176 177 178 179 180 181 182 183 184 185 186 187
      ..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
      ..addMultiOption('filesystem-root',
        hide: !verboseHelp,
        help: 'Specify the path, that is used as root in a virtual file system\n'
            'for compilation. Input file name should be specified as Uri in\n'
            'filesystem-scheme scheme. Use only in Dart 2 mode.\n'
            'Requires --output-dill option to be explicitly specified.\n')
      ..addOption('filesystem-scheme',
        defaultsTo: 'org-dartlang-root',
        hide: !verboseHelp,
        help: 'Specify the scheme that is used for virtual file system used in\n'
            'compilation. See more details on filesystem-root option.\n');
188 189
  }

190
  List<Device> devices;
191

192
  @override
193
  Future<String> get usagePath async {
194
    final String command = await super.usagePath;
195

196
    if (devices == null)
197
      return command;
198
    else if (devices.length > 1)
199 200 201
      return '$command/all';
    else
      return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}';
202 203
  }

204 205 206
  @override
  Future<Map<String, String>> get usageValues async {
    final bool isEmulator = await devices[0].isLocalEmulator;
207
    final String deviceType = devices.length == 1
208 209 210 211 212 213
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple';

    return <String, String>{ 'cd3': '$isEmulator', 'cd4': deviceType };
  }

214 215 216 217
  @override
  void printNoConnectedDevices() {
    super.printNoConnectedDevices();
    if (getCurrentHostPlatform() == HostPlatform.darwin_x64 &&
218
        xcode.isInstalledAndMeetsVersionCheck) {
219
      printStatus('');
220
      printStatus("Run 'flutter emulators' to list and start any available device emulators.");
221
      printStatus('');
222 223
      printStatus('If you expected your device to be detected, please run "flutter doctor" to diagnose');
      printStatus('potential issues, or visit https://flutter.io/setup/ for troubleshooting tips.');
224 225 226
    }
  }

227 228 229 230 231 232 233 234 235
  @override
  bool get shouldRunPub {
    // If we are running with a prebuilt application, do not run pub.
    if (runningWithPrebuiltApplication)
      return false;

    return super.shouldRunPub;
  }

236
  bool shouldUseHotMode() {
237
    final bool hotArg = argResults['hot'] ?? false;
238
    final bool shouldUseHotMode = hotArg;
239
    return getBuildInfo().isDebug && shouldUseHotMode;
240 241
  }

242 243 244
  bool get runningWithPrebuiltApplication =>
      argResults['use-application-binary'] != null;

245 246
  bool get stayResident => argResults['resident'];

247
  @override
248 249 250 251 252
  Future<Null> validateCommand() async {
    // When running with a prebuilt application, no command validation is
    // necessary.
    if (!runningWithPrebuiltApplication)
      await super.validateCommand();
253 254
    devices = await findAllTargetDevices();
    if (devices == null)
255
      throwToolExit(null);
256 257
    if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication)
      throwToolExit('Using -d all with --use-application-binary is not supported');
258 259
  }

260
  DebuggingOptions _createDebuggingOptions() {
261 262 263
    final BuildInfo buildInfo = getBuildInfo();
    if (buildInfo.isRelease) {
      return new DebuggingOptions.disabled(buildInfo);
264 265
    } else {
      return new DebuggingOptions.enabled(
266
        buildInfo,
267 268
        startPaused: argResults['start-paused'],
        useTestFonts: argResults['use-test-fonts'],
269
        enableSoftwareRendering: argResults['enable-software-rendering'],
270
        skiaDeterministicRendering: argResults['skia-deterministic-rendering'],
271
        traceSkia: argResults['trace-skia'],
272 273 274 275 276
        observatoryPort: observatoryPort,
      );
    }
  }

277
  @override
278
  Future<FlutterCommandResult> runCommand() async {
279 280 281 282 283 284 285
    Cache.releaseLockEarly();

    // Enable hot mode by default if `--no-hot` was not passed and we are in
    // debug mode.
    final bool hotMode = shouldUseHotMode();

    if (argResults['machine']) {
286 287
      if (devices.length > 1)
        throwToolExit('--machine does not support -d all.');
288
      final Daemon daemon = new Daemon(stdinCommandStream, stdoutCommandResponse,
289
          notifyingLogger: new NotifyingLogger(), logToStdout: true);
290 291
      AppInstance app;
      try {
292
        app = await daemon.appDomain.startApp(
293
          devices.first, fs.currentDirectory.path, targetFile, route,
294
          _createDebuggingOptions(), hotMode,
295
          applicationBinary: argResults['use-application-binary'],
296
          trackWidgetCreation: argResults['track-widget-creation'],
297
          projectRootPath: argResults['project-root'],
298
          packagesFilePath: globalResults['packages'],
299
          dillOutputPath: argResults['output-dill'],
300
          ipv6: ipv6,
301
        );
302 303 304
      } catch (error) {
        throwToolExit(error.toString());
      }
305
      final DateTime appStartedTime = clock.now();
306
      final int result = await app.runner.waitForAppToFinish();
307 308
      if (result != 0)
        throwToolExit(null, exitCode: result);
309 310
      return new FlutterCommandResult(
        ExitStatus.success,
311
        timingLabelParts: <String>['daemon'],
312 313
        endTimeOverride: appStartedTime,
      );
314 315
    }

316
    for (Device device in devices) {
317 318 319 320 321
      if (await device.isLocalEmulator) {
        if (await device.supportsHardwareRendering) {
          final bool enableSoftwareRendering = argResults['enable-software-rendering'] == true;
          if (enableSoftwareRendering) {
            printStatus(
322
              'Using software rendering with device ${device.name}. You may get better performance '
323 324 325 326
              'with hardware mode by configuring hardware rendering for your device.'
            );
          } else {
            printStatus(
327
              'Using hardware rendering with device ${device.name}. If you get graphics artifacts, '
328 329 330 331 332 333 334 335 336
              'consider enabling software rendering with "--enable-software-rendering".'
            );
          }
        }

        if (!isEmulatorBuildMode(getBuildMode())) {
          throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
        }
      }
337
    }
338

339
    if (hotMode) {
340 341 342 343
      for (Device device in devices) {
        if (!device.supportsHotMode)
          throwToolExit('Hot mode is not supported by ${device.name}. Run with --no-hot.');
      }
344 345
    }

346
    final String pidFile = argResults['pid-file'];
347 348
    if (pidFile != null) {
      // Write our pid to the file.
349
      fs.file(pidFile).writeAsStringSync(pid.toString());
350
    }
351

352
    final List<FlutterDevice> flutterDevices = devices.map((Device device) {
353 354
      return new FlutterDevice(
        device,
355
        previewDart2: argResults['preview-dart-2'],
356
        trackWidgetCreation: argResults['track-widget-creation'],
357
        dillOutputPath: argResults['output-dill'],
358 359
        fileSystemRoots: argResults['filesystem-root'],
        fileSystemScheme: argResults['filesystem-scheme'],
360
      );
361 362 363
    }).toList();

    ResidentRunner runner;
364
    if (hotMode) {
365
      runner = new HotRunner(
366
        flutterDevices,
367
        target: targetFile,
368
        debuggingOptions: _createDebuggingOptions(),
369 370
        benchmarkMode: argResults['benchmark'],
        applicationBinary: argResults['use-application-binary'],
371
        projectRootPath: argResults['project-root'],
372
        packagesFilePath: globalResults['packages'],
373
        dillOutputPath: argResults['output-dill'],
374
        stayResident: stayResident,
375
        ipv6: ipv6,
376
      );
Devon Carew's avatar
Devon Carew committed
377
    } else {
378
      runner = new ColdRunner(
379
        flutterDevices,
380
        target: targetFile,
381
        debuggingOptions: _createDebuggingOptions(),
Devon Carew's avatar
Devon Carew committed
382
        traceStartup: traceStartup,
383 384
        applicationBinary: argResults['use-application-binary'],
        stayResident: stayResident,
385
        ipv6: ipv6,
Devon Carew's avatar
Devon Carew committed
386 387
      );
    }
388

389
    DateTime appStartedTime;
390 391
    // Sync completer so the completing agent attaching to the resident doesn't
    // need to know about analytics.
392 393 394
    //
    // Do not add more operations to the future.
    final Completer<Null> appStartedTimeRecorder = new Completer<Null>.sync();
395 396
    // This callback can't throw.
    appStartedTimeRecorder.future.then( // ignore: unawaited_futures
397 398 399
      (_) { appStartedTime = clock.now(); }
    );

400
    final int result = await runner.run(
401
      appStartedCompleter: appStartedTimeRecorder,
402 403 404 405 406
      route: route,
      shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
    );
    if (result != 0)
      throwToolExit(null, exitCode: result);
407 408
    return new FlutterCommandResult(
      ExitStatus.success,
409
      timingLabelParts: <String>[
410 411
        hotMode ? 'hot' : 'cold',
        getModeName(getBuildMode()),
412
        devices.length == 1
413 414 415 416 417 418
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple',
        devices.length == 1 && await devices[0].isLocalEmulator ? 'emulator' : null
      ],
      endTimeOverride: appStartedTime,
    );
Adam Barth's avatar
Adam Barth committed
419 420
  }
}