run.dart 12.9 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 29 30
    argParser.addFlag('trace-startup',
        negatable: true,
        defaultsTo: false,
        help: 'Start tracing during startup.');
31
    argParser.addOption('route',
32
        help: 'Which route to load when running the app.');
33
    usesTargetOption();
34 35
    usesPortOptions();
    usesPubOption();
36
  }
37 38 39

  bool get traceStartup => argResults['trace-startup'];
  String get route => argResults['route'];
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

  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.'
    );
    argParser.addOption('diagnostic-port',
        help: 'Listen to the given port for a diagnostic connection.\n'
              'Specifying port 0 will find a random free port.\n'
              'Defaults to the first available port after $kDefaultDiagnosticPort.'
    );
  }

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

  int get diagnosticPort {
    if (argResults['diagnostic-port'] != null) {
      try {
        return int.parse(argResults['diagnostic-port']);
      } catch (error) {
        throwToolExit('Invalid port for `--diagnostic-port`: $error');
      }
    }
    return null;
  }
75
}
76

77
class RunCommand extends RunCommandBase {
78
  @override
79
  final String name = 'run';
80 81

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

Ian Hickson's avatar
Ian Hickson committed
84
  RunCommand({ bool verboseHelp: false }) {
85 86
    requiresPubspecYaml();

87
    argParser.addFlag('full-restart',
88
        defaultsTo: true,
89
        help: 'Stop any currently running application process before running the app.');
90 91 92 93
    argParser.addFlag('start-paused',
        defaultsTo: false,
        negatable: false,
        help: 'Start in a paused mode and wait for a debugger to connect.');
94 95 96 97 98 99 100
    argParser.addFlag('enable-software-rendering',
        defaultsTo: false,
        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.');
101 102 103 104 105
    argParser.addFlag('trace-skia',
        defaultsTo: false,
        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.');
106 107 108 109 110 111 112 113
    argParser.addFlag('use-test-fonts',
        negatable: true,
        defaultsTo: false,
        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.');
114 115 116
    argParser.addFlag('build',
        defaultsTo: true,
        help: 'If necessary, build the app before running.');
117
    argParser.addOption('use-application-binary',
118
        hide: !verboseHelp,
119
        help: 'Specify a pre-built application binary to use when running.');
120
    argParser.addFlag('preview-dart-2',
121
        hide: !verboseHelp,
122 123
        defaultsTo: false,
        help: 'Preview Dart 2.0 functionality.');
124 125 126
    argParser.addOption('packages',
        hide: !verboseHelp,
        help: 'Specify the path to the .packages file.');
127
    argParser.addOption('project-root',
128 129
        hide: !verboseHelp,
        help: 'Specify the project root directory.');
130 131 132
    argParser.addOption('project-assets',
        hide: !verboseHelp,
        help: 'Specify the project assets relative to the root directory.');
133 134 135 136
    argParser.addFlag('machine',
        hide: !verboseHelp,
        help: 'Handle machine structured JSON command input\n'
              'and provide output and progress in machine friendly format.');
137 138 139 140 141 142 143 144 145 146 147 148 149
    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.');
150

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

    argParser.addOption(FlutterOptions.kExtraFrontEndOptions, hide: true);
    argParser.addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true);
161 162
  }

163
  List<Device> devices;
164

165
  @override
166
  Future<String> get usagePath async {
167
    final String command = shouldUseHotMode() ? 'hotrun' : name;
168

169
    if (devices == null)
170
      return command;
171 172

    // Return 'run/ios'.
173 174 175 176
    if (devices.length > 1)
      return '$command/all';
    else
      return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}';
177 178
  }

179 180 181
  @override
  Future<Map<String, String>> get usageValues async {
    final bool isEmulator = await devices[0].isLocalEmulator;
182
    final String deviceType = devices.length == 1
183 184 185 186 187 188
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple';

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

189 190 191 192
  @override
  void printNoConnectedDevices() {
    super.printNoConnectedDevices();
    if (getCurrentHostPlatform() == HostPlatform.darwin_x64 &&
193
        xcode.isInstalledAndMeetsVersionCheck) {
194
      printStatus('');
195
      printStatus('To run on a simulator, launch it first: open -a Simulator.app');
196
      printStatus('');
197 198
      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.');
199 200 201
    }
  }

202 203 204 205 206 207 208 209 210
  @override
  bool get shouldRunPub {
    // If we are running with a prebuilt application, do not run pub.
    if (runningWithPrebuiltApplication)
      return false;

    return super.shouldRunPub;
  }

211
  bool shouldUseHotMode() {
212
    final bool hotArg = argResults['hot'] ?? false;
213
    final bool shouldUseHotMode = hotArg;
214
    return getBuildInfo().isDebug && shouldUseHotMode;
215 216
  }

217 218 219
  bool get runningWithPrebuiltApplication =>
      argResults['use-application-binary'] != null;

220 221
  bool get stayResident => argResults['resident'];

222
  @override
223 224 225 226 227
  Future<Null> validateCommand() async {
    // When running with a prebuilt application, no command validation is
    // necessary.
    if (!runningWithPrebuiltApplication)
      await super.validateCommand();
228 229
    devices = await findAllTargetDevices();
    if (devices == null)
230
      throwToolExit(null);
231 232
    if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication)
      throwToolExit('Using -d all with --use-application-binary is not supported');
233 234
  }

235
  DebuggingOptions _createDebuggingOptions() {
236 237 238
    final BuildInfo buildInfo = getBuildInfo();
    if (buildInfo.isRelease) {
      return new DebuggingOptions.disabled(buildInfo);
239 240
    } else {
      return new DebuggingOptions.enabled(
241
        buildInfo,
242 243
        startPaused: argResults['start-paused'],
        useTestFonts: argResults['use-test-fonts'],
244
        enableSoftwareRendering: argResults['enable-software-rendering'],
245
        traceSkia: argResults['trace-skia'],
246 247 248 249 250 251
        observatoryPort: observatoryPort,
        diagnosticPort: diagnosticPort,
      );
    }
  }

252
  @override
253
  Future<FlutterCommandResult> runCommand() async {
254 255 256 257 258 259 260
    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']) {
261 262
      if (devices.length > 1)
        throwToolExit('--machine does not support -d all.');
263
      final Daemon daemon = new Daemon(stdinCommandStream, stdoutCommandResponse,
264
          notifyingLogger: new NotifyingLogger(), logToStdout: true);
265 266
      AppInstance app;
      try {
267
        app = await daemon.appDomain.startApp(
268
          devices.first, fs.currentDirectory.path, targetFile, route,
269
          _createDebuggingOptions(), hotMode,
270 271 272
          applicationBinary: argResults['use-application-binary'],
          projectRootPath: argResults['project-root'],
          packagesFilePath: argResults['packages'],
273 274
          projectAssets: argResults['project-assets']
        );
275 276 277
      } catch (error) {
        throwToolExit(error.toString());
      }
278
      final DateTime appStartedTime = clock.now();
279
      final int result = await app.runner.waitForAppToFinish();
280 281
      if (result != 0)
        throwToolExit(null, exitCode: result);
282 283
      return new FlutterCommandResult(
        ExitStatus.success,
284
        timingLabelParts: <String>['daemon'],
285 286
        endTimeOverride: appStartedTime,
      );
287 288
    }

289 290 291 292
    for (Device device in devices) {
      if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
        throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
    }
293

294
    if (hotMode) {
295 296 297 298
      for (Device device in devices) {
        if (!device.supportsHotMode)
          throwToolExit('Hot mode is not supported by ${device.name}. Run with --no-hot.');
      }
299 300
    }

301
    final String pidFile = argResults['pid-file'];
302 303
    if (pidFile != null) {
      // Write our pid to the file.
304
      fs.file(pidFile).writeAsStringSync(pid.toString());
305
    }
306

307
    final List<FlutterDevice> flutterDevices = devices.map((Device device) {
308
      return new FlutterDevice(device, previewDart2: argResults['preview-dart-2']);
309 310 311
    }).toList();

    ResidentRunner runner;
312
    if (hotMode) {
313
      runner = new HotRunner(
314
        flutterDevices,
315
        target: targetFile,
316
        debuggingOptions: _createDebuggingOptions(),
317 318
        benchmarkMode: argResults['benchmark'],
        applicationBinary: argResults['use-application-binary'],
319
        previewDart2: argResults['preview-dart-2'],
320
        projectRootPath: argResults['project-root'],
321
        packagesFilePath: argResults['packages'],
322 323
        projectAssets: argResults['project-assets'],
        stayResident: stayResident,
324
      );
Devon Carew's avatar
Devon Carew committed
325
    } else {
326
      runner = new ColdRunner(
327
        flutterDevices,
328
        target: targetFile,
329
        debuggingOptions: _createDebuggingOptions(),
Devon Carew's avatar
Devon Carew committed
330
        traceStartup: traceStartup,
331
        applicationBinary: argResults['use-application-binary'],
332
        previewDart2: argResults['preview-dart-2'],
333
        stayResident: stayResident,
Devon Carew's avatar
Devon Carew committed
334 335
      );
    }
336

337
    DateTime appStartedTime;
338 339
    // Sync completer so the completing agent attaching to the resident doesn't
    // need to know about analytics.
340 341 342
    //
    // Do not add more operations to the future.
    final Completer<Null> appStartedTimeRecorder = new Completer<Null>.sync();
343 344
    // This callback can't throw.
    appStartedTimeRecorder.future.then( // ignore: unawaited_futures
345 346 347
      (_) { appStartedTime = clock.now(); }
    );

348
    final int result = await runner.run(
349
      appStartedCompleter: appStartedTimeRecorder,
350 351 352 353 354
      route: route,
      shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
    );
    if (result != 0)
      throwToolExit(null, exitCode: result);
355 356
    return new FlutterCommandResult(
      ExitStatus.success,
357
      timingLabelParts: <String>[
358 359
        hotMode ? 'hot' : 'cold',
        getModeName(getBuildMode()),
360
        devices.length == 1
361 362 363 364 365 366
            ? getNameForTargetPlatform(await devices[0].targetPlatform)
            : 'multiple',
        devices.length == 1 && await devices[0].isLocalEmulator ? 'emulator' : null
      ],
      endTimeOverride: appStartedTime,
    );
Adam Barth's avatar
Adam Barth committed
367 368
  }
}