run.dart 8.15 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 '../hot.dart';
16
import '../ios/mac.dart';
17
import '../resident_runner.dart';
18
import '../run.dart';
19
import '../runner/flutter_command.dart';
20
import 'daemon.dart';
21

22 23
abstract class RunCommandBase extends FlutterCommand {
  RunCommandBase() {
24
    addBuildModeFlags(defaultToRelease: false);
25 26 27 28
    argParser.addFlag('trace-startup',
        negatable: true,
        defaultsTo: false,
        help: 'Start tracing during startup.');
29
    argParser.addOption('route',
30
        help: 'Which route to load when running the app.');
31
    usesTargetOption();
32
  }
33 34 35

  bool get traceStartup => argResults['trace-startup'];
  String get route => argResults['route'];
36
}
37

38
class RunCommand extends RunCommandBase {
39
  @override
40
  final String name = 'run';
41 42

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

Ian Hickson's avatar
Ian Hickson committed
45
  RunCommand({ bool verboseHelp: false }) {
46
    argParser.addFlag('full-restart',
47
        defaultsTo: true,
48
        help: 'Stop any currently running application process before running the app.');
49 50 51 52 53
    argParser.addFlag('start-paused',
        defaultsTo: false,
        negatable: false,
        help: 'Start in a paused mode and wait for a debugger to connect.');
    argParser.addOption('debug-port',
54
        help: 'Listen to the given port for a debug connection (defaults to $kDefaultObservatoryPort).');
55 56 57
    argParser.addFlag('build',
        defaultsTo: true,
        help: 'If necessary, build the app before running.');
58
    argParser.addOption('use-application-binary',
59
        hide: !verboseHelp,
60
        help: 'Specify a pre-built application binary to use when running.');
61 62 63 64 65 66
    argParser.addOption('snapshotter',
        hide: !verboseHelp,
        help: 'Specify the path to the sky_snapshot binary.');
    argParser.addOption('packages',
        hide: !verboseHelp,
        help: 'Specify the path to the .packages file.');
67
    argParser.addOption('project-root',
68 69
        hide: !verboseHelp,
        help: 'Specify the project root directory.');
70 71 72
    argParser.addOption('project-assets',
        hide: !verboseHelp,
        help: 'Specify the project assets relative to the root directory.');
73 74 75 76
    argParser.addFlag('machine',
        hide: !verboseHelp,
        help: 'Handle machine structured JSON command input\n'
              'and provide output and progress in machine friendly format.');
77
    usesPubOption();
78 79

    // Option to enable hot reloading.
Ian Hickson's avatar
Ian Hickson committed
80 81 82 83 84 85
    argParser.addFlag(
      'hot',
      negatable: true,
      defaultsTo: kHotReloadDefault,
      help: 'Run with support for hot reloading.'
    );
86

87
    // Option to write the pid to a file.
Ian Hickson's avatar
Ian Hickson committed
88 89 90 91 92 93
    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.'
    );
94

95 96 97 98
    // Hidden option to 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 intended
    // for use in generating automated flutter benchmarks.
99
    argParser.addFlag('benchmark', negatable: false, hide: !verboseHelp);
100 101

    commandValidator = () {
102 103 104
      if (!runningWithPrebuiltApplication)
        commonCommandValidator();

105 106 107
      // When running with a prebuilt application, no command validation is
      // necessary.
    };
108 109
  }

110
  Device device;
111

112 113
  @override
  String get usagePath {
114
    String command = shouldUseHotMode() ? 'hotrun' : name;
115

116
    if (device == null)
117
      return command;
118 119

    // Return 'run/ios'.
120
    return '$command/${getNameForTargetPlatform(device.platform)}';
121 122
  }

123 124 125 126 127 128
  @override
  void printNoConnectedDevices() {
    super.printNoConnectedDevices();
    if (getCurrentHostPlatform() == HostPlatform.darwin_x64 &&
        XCode.instance.isInstalledAndMeetsVersionCheck) {
      printStatus('');
129
      printStatus('To run on a simulator, launch it first: open -a Simulator.app');
130
      printStatus('');
131 132
      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.');
133 134 135
    }
  }

136 137 138 139 140 141 142 143 144
  @override
  bool get shouldRunPub {
    // If we are running with a prebuilt application, do not run pub.
    if (runningWithPrebuiltApplication)
      return false;

    return super.shouldRunPub;
  }

145
  bool shouldUseHotMode() {
146
    bool hotArg = argResults['hot'] ?? false;
147
    final bool shouldUseHotMode = hotArg;
148
    return (getBuildMode() == BuildMode.debug) && shouldUseHotMode;
149 150
  }

151 152 153
  bool get runningWithPrebuiltApplication =>
      argResults['use-application-binary'] != null;

154
  @override
155
  Future<Null> verifyThenRunCommand() async {
156
    commandValidator();
157 158
    device = await findTargetDevice();
    if (device == null)
159
      throwToolExit(null);
160 161 162 163
    return super.verifyThenRunCommand();
  }

  @override
164
  Future<Null> runCommand() async {
165

166 167 168 169 170 171 172 173
    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']) {
      Daemon daemon = new Daemon(stdinCommandStream, stdoutCommandResponse,
174
          notifyingLogger: new NotifyingLogger(), logToStdout: true);
175 176 177
      AppInstance app;
      try {
        app = daemon.appDomain.startApp(
178
          device, fs.currentDirectory.path, targetFile, route,
179 180 181 182 183
          getBuildMode(), argResults['start-paused'], hotMode,
          applicationBinary: argResults['use-application-binary'],
          projectRootPath: argResults['project-root'],
          packagesFilePath: argResults['packages'],
          projectAssets: argResults['project-assets']);
184 185 186
      } catch (error) {
        throwToolExit(error.toString());
      }
187 188 189
      int result = await app.runner.waitForAppToFinish();
      if (result != 0)
        throwToolExit(null, exitCode: result);
190
      return null;
191 192 193
    }

    int debugPort;
Devon Carew's avatar
Devon Carew committed
194 195 196 197
    if (argResults['debug-port'] != null) {
      try {
        debugPort = int.parse(argResults['debug-port']);
      } catch (error) {
198
        throwToolExit('Invalid port for `--debug-port`: $error');
Devon Carew's avatar
Devon Carew committed
199
      }
200 201
    }

202 203
    if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
      throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
204

Devon Carew's avatar
Devon Carew committed
205 206
    DebuggingOptions options;

207
    if (getBuildMode() == BuildMode.release) {
208
      options = new DebuggingOptions.disabled(getBuildMode());
Devon Carew's avatar
Devon Carew committed
209 210
    } else {
      options = new DebuggingOptions.enabled(
211
        getBuildMode(),
Devon Carew's avatar
Devon Carew committed
212 213 214 215
        startPaused: argResults['start-paused'],
        observatoryPort: debugPort
      );
    }
216

217
    if (hotMode) {
218 219
      if (!device.supportsHotMode)
        throwToolExit('Hot mode is not supported by this device. Run with --no-hot.');
220 221
    }

222 223 224
    String pidFile = argResults['pid-file'];
    if (pidFile != null) {
      // Write our pid to the file.
225
      fs.file(pidFile).writeAsStringSync(pid.toString());
226
    }
227 228
    ResidentRunner runner;

229
    if (hotMode) {
230
      runner = new HotRunner(
231
        device,
232
        target: targetFile,
233
        debuggingOptions: options,
234 235
        benchmarkMode: argResults['benchmark'],
        applicationBinary: argResults['use-application-binary'],
236
        projectRootPath: argResults['project-root'],
237
        packagesFilePath: argResults['packages'],
238
        projectAssets: argResults['project-assets']
239
      );
Devon Carew's avatar
Devon Carew committed
240
    } else {
241
      runner = new RunAndStayResident(
242
        device,
243
        target: targetFile,
244
        debuggingOptions: options,
Devon Carew's avatar
Devon Carew committed
245
        traceStartup: traceStartup,
246
        applicationBinary: argResults['use-application-binary']
Devon Carew's avatar
Devon Carew committed
247 248
      );
    }
249

250 251 252 253 254 255
    int result = await runner.run(
      route: route,
      shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
    );
    if (result != 0)
      throwToolExit(null, exitCode: result);
Adam Barth's avatar
Adam Barth committed
256 257
  }
}