run.dart 8.24 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2016 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';
import 'dart:io';

8 9
import 'package:stack_trace/stack_trace.dart';

10 11 12
import 'application_package.dart';
import 'base/logger.dart';
import 'base/utils.dart';
13
import 'build_info.dart';
14 15 16 17 18
import 'commands/build_apk.dart';
import 'commands/install.dart';
import 'commands/trace.dart';
import 'device.dart';
import 'globals.dart';
19
import 'vmservice.dart';
20
import 'resident_runner.dart';
21

22
class RunAndStayResident extends ResidentRunner {
23
  RunAndStayResident(
24 25 26
    Device device, {
    String target,
    DebuggingOptions debuggingOptions,
27 28
    bool usesTerminalUI: true,
    this.traceStartup: false,
29 30
    this.benchmark: false,
    this.applicationBinary
31 32 33 34
  }) : super(device,
             target: target,
             debuggingOptions: debuggingOptions,
             usesTerminalUI: usesTerminalUI);
35 36 37 38

  ApplicationPackage _package;
  String _mainPath;
  LaunchResult _result;
39 40 41
  final bool traceStartup;
  final bool benchmark;
  final String applicationBinary;
42

43 44
  bool get prebuiltMode => applicationBinary != null;

45
  @override
46
  Future<int> run({
47
    Completer<DebugConnectionInfo> connectionInfoCompleter,
48 49 50
    String route,
    bool shouldBuild: true
  }) {
51
    // Don't let uncaught errors kill the process.
52
    return Chain.capture(() {
53
      assert(shouldBuild == !prebuiltMode);
54 55 56
      return _run(
        traceStartup: traceStartup,
        benchmark: benchmark,
57
        connectionInfoCompleter: connectionInfoCompleter,
58 59
        route: route,
        shouldBuild: shouldBuild
60 61 62 63 64 65
      );
    }, onError: (dynamic error, StackTrace stackTrace) {
      printError('Exception from flutter run: $error', stackTrace);
    });
  }

66
  @override
Devon Carew's avatar
Devon Carew committed
67
  Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) async {
68
    if (vmService == null) {
69
      printError('Debugging is not enabled.');
Devon Carew's avatar
Devon Carew committed
70
      return new OperationResult(1, 'debugging not enabled');
71 72 73
    } else {
      Status status = logger.startProgress('Re-starting application...');

74
      Future<ServiceEvent> extensionAddedEvent;
75 76

      if (device.restartSendsFrameworkInitEvent) {
77 78
        extensionAddedEvent = vmService.onExtensionEvent
          .where((ServiceEvent event) => event.extensionKind == 'Flutter.FrameworkInitialization')
79 80
          .first;
      }
81

Devon Carew's avatar
Devon Carew committed
82
      bool result = await device.restartApp(
83 84 85
        _package,
        _result,
        mainPath: _mainPath,
86 87
        observatory: vmService,
        prebuiltApplication: prebuiltMode
88 89 90 91
      );

      status.stop(showElapsedTime: true);

Devon Carew's avatar
Devon Carew committed
92
      if (result && extensionAddedEvent != null) {
93 94 95
        await extensionAddedEvent;
      }

Devon Carew's avatar
Devon Carew committed
96
      return result ? OperationResult.ok : new OperationResult(1, 'restart error');
97 98 99 100 101 102
    }
  }

  Future<int> _run({
    bool traceStartup: false,
    bool benchmark: false,
103
    Completer<DebugConnectionInfo> connectionInfoCompleter,
104 105
    String route,
    bool shouldBuild: true
106
  }) async {
107 108 109 110 111 112 113 114 115
    if (!prebuiltMode) {
      _mainPath = findMainDartFile(target);
      if (!FileSystemEntity.isFileSync(_mainPath)) {
        String message = 'Tried to run $_mainPath, but that file does not exist.';
        if (target == null)
          message += '\nConsider using the -t option to specify the Dart file to start.';
        printError(message);
        return 1;
      }
116 117
    }

118
    _package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary);
119 120 121 122 123 124 125 126 127 128 129 130 131

    if (_package == null) {
      String message = 'No application found for ${device.platform}.';
      String hint = getMissingPackageHintForPlatform(device.platform);
      if (hint != null)
        message += '\n$hint';
      printError(message);
      return 1;
    }

    Stopwatch startTime = new Stopwatch()..start();

    // TODO(devoncarew): We shouldn't have to do type checks here.
132
    if (shouldBuild && device is AndroidDevice) {
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
      printTrace('Running build command.');

      int result = await buildApk(
        device.platform,
        target: target,
        buildMode: debuggingOptions.buildMode
      );

      if (result != 0)
        return result;
    }

    // TODO(devoncarew): Move this into the device.startApp() impls.
    if (_package != null) {
      printTrace("Stopping app '${_package.name}' on ${device.name}.");
148
      await device.stopApp(_package);
149 150 151
    }

    // TODO(devoncarew): This fails for ios devices - we haven't built yet.
152
    if (prebuiltMode || device is AndroidDevice) {
153
      printTrace('Running install command.');
154
      if (!(installApp(device, _package, uninstall: false)))
155 156 157 158 159 160 161
        return 1;
    }

    Map<String, dynamic> platformArgs;
    if (traceStartup != null)
      platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };

162
    await startEchoingDeviceLog();
163 164 165 166 167 168
    if (_mainPath == null) {
      assert(prebuiltMode);
      printStatus('Running ${_package.displayName} on ${device.name}');
    } else {
      printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
    }
169 170 171 172

    _result = await device.startApp(
      _package,
      debuggingOptions.buildMode,
173
      mainPath: _mainPath,
174
      debuggingOptions: debuggingOptions,
Devon Carew's avatar
Devon Carew committed
175
      platformArgs: platformArgs,
176 177
      route: route,
      prebuiltApplication: prebuiltMode
178 179 180 181
    );

    if (!_result.started) {
      printError('Error running application on ${device.name}.');
182
      await stopEchoingDeviceLog();
183 184 185 186 187
      return 2;
    }

    startTime.stop();

188 189
    if (connectionInfoCompleter != null && _result.hasObservatory)
      connectionInfoCompleter.complete(new DebugConnectionInfo(_result.observatoryPort));
190 191 192

    // Connect to observatory.
    if (debuggingOptions.debuggingEnabled) {
193
      await connectToServiceProtocol(_result.observatoryPort);
194

195 196 197
      if (benchmark) {
        await vmService.getVM();
      }
198 199 200
    }

    printStatus('Application running.');
201 202
    if (debuggingOptions.buildMode == BuildMode.release)
      return 0;
203

204 205
    if (vmService != null) {
      await vmService.vm.refreshViews();
206
      printStatus('Connected to ${vmService.vm.mainView}\.');
207
    }
208 209

    if (vmService != null && traceStartup) {
210
      printStatus('Downloading startup trace info...');
211
      try {
212
        await downloadStartupTrace(vmService);
213 214 215 216
      } catch(error) {
        printError(error);
        return 2;
      }
217
      appFinished();
218
    } else {
219 220
      setupTerminal();
      registerSignalHandlers();
221 222 223 224 225 226 227 228 229 230
    }

    if (benchmark) {
      await new Future<Null>.delayed(new Duration(seconds: 4));

      // Touch the file.
      File mainFile = new File(_mainPath);
      mainFile.writeAsBytesSync(mainFile.readAsBytesSync());

      Stopwatch restartTime = new Stopwatch()..start();
Devon Carew's avatar
Devon Carew committed
231
      OperationResult result = await restart();
232
      restartTime.stop();
Devon Carew's avatar
Devon Carew committed
233
      writeRunBenchmarkFile(startTime, result.isOk ? restartTime : null);
234 235 236 237
      await new Future<Null>.delayed(new Duration(seconds: 2));
      stop();
    }

238
    return waitForAppToFinish();
239 240
  }

241
  @override
242
  Future<Null> handleTerminalCommand(String code) async {
243 244 245 246
    String lower = code.toLowerCase();
    if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
      if (device.supportsRestart) {
        // F5, restart
247
        await restart();
248
      }
249
    }
250 251
  }

252 253 254 255
  @override
  Future<Null> cleanupAfterSignal() async {
    await stopEchoingDeviceLog();
    await stopApp();
256 257
  }

258 259 260
  @override
  Future<Null> cleanupAtFinish() async {
    await stopEchoingDeviceLog();
261 262
  }

263 264
  @override
  void printHelp() {
265 266
    final bool showRestartText = !prebuiltMode && device.supportsRestart;
    String restartText = showRestartText ? ', "r" or F5 to restart the app,' : '';
267 268
    printStatus('Type "h" or F1 for help$restartText and "q", F10, or ctrl-c to quit.');
    printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.');
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
  }
}

void writeRunBenchmarkFile(Stopwatch startTime, [Stopwatch restartTime]) {
  final String benchmarkOut = 'refresh_benchmark.json';
  Map<String, dynamic> data = <String, dynamic>{
    'start': startTime.elapsedMilliseconds,
    'time': (restartTime ?? startTime).elapsedMilliseconds // time and restart are the same
  };
  if (restartTime != null)
    data['restart'] = restartTime.elapsedMilliseconds;

  new File(benchmarkOut).writeAsStringSync(toPrettyJson(data));
  printStatus('Run benchmark written to $benchmarkOut ($data).');
}