run.dart 12.5 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
    argParser.addFlag('trace-startup',
28
        negatable: false,
29
        help: 'Start tracing during startup.');
30 31 32 33 34
    argParser.addFlag('ipv6',
        hide: true,
        negatable: false,
        help: 'Binds to IPv6 localhost instead of IPv4 when the flutter tool\n'
              'forwards the host port to a device port.');
35
    argParser.addOption('route',
36
        help: 'Which route to load when running the app.');
37
    usesTargetOption();
38 39
    usesPortOptions();
    usesPubOption();
40
  }
41 42

  bool get traceStartup => argResults['trace-startup'];
43
  bool get ipv6 => argResults['ipv6'];
44
  String get route => argResults['route'];
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

  void usesPortOptions() {
    argParser.addOption('observatory-port',
        help: 'Listen to the given port for an observatory debugger connection.\n'
              'Specifying port 0 will find a random free port.\n'
              'Defaults to the first available port after $kDefaultObservatoryPort.'
    );
  }

  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;
  }
64
}
65

66
class RunCommand extends RunCommandBase {
67
  @override
68
  final String name = 'run';
69 70

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

Ian Hickson's avatar
Ian Hickson committed
73
  RunCommand({ bool verboseHelp: false }) {
74 75
    requiresPubspecYaml();

76
    argParser.addFlag('full-restart',
77
        defaultsTo: true,
78
        help: 'Stop any currently running application process before running the app.');
79 80 81
    argParser.addFlag('start-paused',
        negatable: false,
        help: 'Start in a paused mode and wait for a debugger to connect.');
82 83 84 85 86 87
    argParser.addFlag('enable-software-rendering',
        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'
              'when neither is available.');
88 89 90 91
    argParser.addFlag('trace-skia',
        negatable: false,
        help: 'Enable tracing of Skia code. This is useful when debugging\n'
              'the GPU thread. By default, Flutter will not log skia code.');
92 93 94 95 96 97 98
    argParser.addFlag('use-test-fonts',
        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'
              'only available when running in debug mode.');
99 100 101
    argParser.addFlag('build',
        defaultsTo: true,
        help: 'If necessary, build the app before running.');
102
    argParser.addOption('use-application-binary',
103
        hide: !verboseHelp,
104
        help: 'Specify a pre-built application binary to use when running.');
105
    argParser.addFlag('preview-dart-2',
106
        hide: !verboseHelp,
107
        help: 'Preview Dart 2.0 functionality.');
108 109
    argParser.addOption('packages',
        hide: !verboseHelp,
110
        valueHelp: 'path',
111
        help: 'Specify the path to the .packages file.');
112
    argParser.addOption('project-root',
113 114
        hide: !verboseHelp,
        help: 'Specify the project root directory.');
115 116 117
    argParser.addOption('project-assets',
        hide: !verboseHelp,
        help: 'Specify the project assets relative to the root directory.');
118 119
    argParser.addFlag('machine',
        hide: !verboseHelp,
120 121 122
        negatable: false,
        help: 'Handle machine structured JSON command input and provide output\n'
              'and progress in machine friendly format.');
123 124 125 126 127 128 129 130 131 132 133 134 135
    argParser.addFlag('hot',
        negatable: true,
        defaultsTo: kHotReloadDefault,
        help: 'Run with support for hot reloading.');
    argParser.addOption('pid-file',
        help: 'Specify a file to write the process id to.\n'
              'You can send SIGUSR1 to trigger a hot reload\n'
              'and SIGUSR2 to trigger a full restart.');
    argParser.addFlag('resident',
        negatable: true,
        defaultsTo: true,
        hide: !verboseHelp,
        help: 'Stay resident after launching the application.');
136

137 138 139 140 141 142 143
    argParser.addFlag('benchmark',
      negatable: false,
      hide: !verboseHelp,
      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.');
144 145 146

    argParser.addOption(FlutterOptions.kExtraFrontEndOptions, hide: true);
    argParser.addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true);
147 148
  }

149
  List<Device> devices;
150

151
  @override
152
  Future<String> get usagePath async {
153
    final String command = shouldUseHotMode() ? 'hotrun' : name;
154

155
    if (devices == null)
156
      return command;
157 158

    // Return 'run/ios'.
159 160 161 162
    if (devices.length > 1)
      return '$command/all';
    else
      return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}';
163 164
  }

165 166 167
  @override
  Future<Map<String, String>> get usageValues async {
    final bool isEmulator = await devices[0].isLocalEmulator;
168
    final String deviceType = devices.length == 1
169 170 171 172 173 174
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple';

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

175 176 177 178
  @override
  void printNoConnectedDevices() {
    super.printNoConnectedDevices();
    if (getCurrentHostPlatform() == HostPlatform.darwin_x64 &&
179
        xcode.isInstalledAndMeetsVersionCheck) {
180
      printStatus('');
181
      printStatus('To run on a simulator, launch it first: open -a Simulator.app');
182
      printStatus('');
183 184
      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.');
185 186 187
    }
  }

188 189 190 191 192 193 194 195 196
  @override
  bool get shouldRunPub {
    // If we are running with a prebuilt application, do not run pub.
    if (runningWithPrebuiltApplication)
      return false;

    return super.shouldRunPub;
  }

197
  bool shouldUseHotMode() {
198
    final bool hotArg = argResults['hot'] ?? false;
199
    final bool shouldUseHotMode = hotArg;
200
    return getBuildInfo().isDebug && shouldUseHotMode;
201 202
  }

203 204 205
  bool get runningWithPrebuiltApplication =>
      argResults['use-application-binary'] != null;

206 207
  bool get stayResident => argResults['resident'];

208
  @override
209 210 211 212 213
  Future<Null> validateCommand() async {
    // When running with a prebuilt application, no command validation is
    // necessary.
    if (!runningWithPrebuiltApplication)
      await super.validateCommand();
214 215
    devices = await findAllTargetDevices();
    if (devices == null)
216
      throwToolExit(null);
217 218
    if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication)
      throwToolExit('Using -d all with --use-application-binary is not supported');
219 220
  }

221
  DebuggingOptions _createDebuggingOptions() {
222 223 224
    final BuildInfo buildInfo = getBuildInfo();
    if (buildInfo.isRelease) {
      return new DebuggingOptions.disabled(buildInfo);
225 226
    } else {
      return new DebuggingOptions.enabled(
227
        buildInfo,
228 229
        startPaused: argResults['start-paused'],
        useTestFonts: argResults['use-test-fonts'],
230
        enableSoftwareRendering: argResults['enable-software-rendering'],
231
        traceSkia: argResults['trace-skia'],
232 233 234 235 236
        observatoryPort: observatoryPort,
      );
    }
  }

237
  @override
238
  Future<FlutterCommandResult> runCommand() async {
239 240 241 242 243 244 245
    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']) {
246 247
      if (devices.length > 1)
        throwToolExit('--machine does not support -d all.');
248
      final Daemon daemon = new Daemon(stdinCommandStream, stdoutCommandResponse,
249
          notifyingLogger: new NotifyingLogger(), logToStdout: true);
250 251
      AppInstance app;
      try {
252
        app = await daemon.appDomain.startApp(
253
          devices.first, fs.currentDirectory.path, targetFile, route,
254
          _createDebuggingOptions(), hotMode,
255 256 257
          applicationBinary: argResults['use-application-binary'],
          projectRootPath: argResults['project-root'],
          packagesFilePath: argResults['packages'],
258 259
          projectAssets: argResults['project-assets']
        );
260 261 262
      } catch (error) {
        throwToolExit(error.toString());
      }
263
      final DateTime appStartedTime = clock.now();
264
      final int result = await app.runner.waitForAppToFinish();
265 266
      if (result != 0)
        throwToolExit(null, exitCode: result);
267 268
      return new FlutterCommandResult(
        ExitStatus.success,
269
        timingLabelParts: <String>['daemon'],
270 271
        endTimeOverride: appStartedTime,
      );
272 273
    }

274 275 276 277
    for (Device device in devices) {
      if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
        throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
    }
278

279
    if (hotMode) {
280 281 282 283
      for (Device device in devices) {
        if (!device.supportsHotMode)
          throwToolExit('Hot mode is not supported by ${device.name}. Run with --no-hot.');
      }
284 285
    }

286
    final String pidFile = argResults['pid-file'];
287 288
    if (pidFile != null) {
      // Write our pid to the file.
289
      fs.file(pidFile).writeAsStringSync(pid.toString());
290
    }
291

292
    final List<FlutterDevice> flutterDevices = devices.map((Device device) {
293
      return new FlutterDevice(device, previewDart2: argResults['preview-dart-2']);
294 295 296
    }).toList();

    ResidentRunner runner;
297
    if (hotMode) {
298
      runner = new HotRunner(
299
        flutterDevices,
300
        target: targetFile,
301
        debuggingOptions: _createDebuggingOptions(),
302 303
        benchmarkMode: argResults['benchmark'],
        applicationBinary: argResults['use-application-binary'],
304
        previewDart2: argResults['preview-dart-2'],
305
        projectRootPath: argResults['project-root'],
306
        packagesFilePath: argResults['packages'],
307 308
        projectAssets: argResults['project-assets'],
        stayResident: stayResident,
309
        ipv6: ipv6,
310
      );
Devon Carew's avatar
Devon Carew committed
311
    } else {
312
      runner = new ColdRunner(
313
        flutterDevices,
314
        target: targetFile,
315
        debuggingOptions: _createDebuggingOptions(),
Devon Carew's avatar
Devon Carew committed
316
        traceStartup: traceStartup,
317
        applicationBinary: argResults['use-application-binary'],
318
        previewDart2: argResults['preview-dart-2'],
319
        stayResident: stayResident,
320
        ipv6: ipv6,
Devon Carew's avatar
Devon Carew committed
321 322
      );
    }
323

324
    DateTime appStartedTime;
325 326
    // Sync completer so the completing agent attaching to the resident doesn't
    // need to know about analytics.
327 328 329
    //
    // Do not add more operations to the future.
    final Completer<Null> appStartedTimeRecorder = new Completer<Null>.sync();
330 331
    // This callback can't throw.
    appStartedTimeRecorder.future.then( // ignore: unawaited_futures
332 333 334
      (_) { appStartedTime = clock.now(); }
    );

335
    final int result = await runner.run(
336
      appStartedCompleter: appStartedTimeRecorder,
337 338 339 340 341
      route: route,
      shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
    );
    if (result != 0)
      throwToolExit(null, exitCode: result);
342 343
    return new FlutterCommandResult(
      ExitStatus.success,
344
      timingLabelParts: <String>[
345 346
        hotMode ? 'hot' : 'cold',
        getModeName(getBuildMode()),
347
        devices.length == 1
348 349 350 351 352 353
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple',
        devices.length == 1 && await devices[0].isLocalEmulator ? 'emulator' : null
      ],
      endTimeOverride: appStartedTime,
    );
Adam Barth's avatar
Adam Barth committed
354 355
  }
}