• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:meta/meta.dart';
8
9import 'base/file_system.dart';
10import 'device.dart';
11import 'globals.dart';
12import 'resident_runner.dart';
13import 'tracing.dart';
14import 'vmservice.dart';
15
16// TODO(mklim): Test this, flutter/flutter#23031.
17class ColdRunner extends ResidentRunner {
18  ColdRunner(
19    List<FlutterDevice> devices, {
20    String target,
21    DebuggingOptions debuggingOptions,
22    this.traceStartup = false,
23    this.awaitFirstFrameWhenTracing = true,
24    this.applicationBinary,
25    bool ipv6 = false,
26    bool usesTerminalUi = false,
27    bool stayResident = true,
28  }) : super(devices,
29             target: target,
30             debuggingOptions: debuggingOptions,
31             hotMode: false,
32             usesTerminalUi: usesTerminalUi,
33             stayResident: stayResident,
34             ipv6: ipv6);
35
36  final bool traceStartup;
37  final bool awaitFirstFrameWhenTracing;
38  final File applicationBinary;
39  bool _didAttach = false;
40
41  @override
42  bool get canHotReload => false;
43
44  @override
45  bool get canHotRestart => false;
46
47  @override
48  Future<int> run({
49    Completer<DebugConnectionInfo> connectionInfoCompleter,
50    Completer<void> appStartedCompleter,
51    String route,
52  }) async {
53    final bool prebuiltMode = applicationBinary != null;
54    if (!prebuiltMode) {
55      if (!fs.isFileSync(mainPath)) {
56        String message = 'Tried to run $mainPath, but that file does not exist.';
57        if (target == null)
58          message += '\nConsider using the -t option to specify the Dart file to start.';
59        printError(message);
60        return 1;
61      }
62    }
63
64    for (FlutterDevice device in flutterDevices) {
65      final int result = await device.runCold(
66        coldRunner: this,
67        route: route,
68      );
69      if (result != 0)
70        return result;
71    }
72
73    // Connect to observatory.
74    if (debuggingOptions.debuggingEnabled) {
75      try {
76        await connectToServiceProtocol();
77      } on String catch (message) {
78        printError(message);
79        return 2;
80      }
81    }
82
83    if (flutterDevices.first.observatoryUris != null) {
84      // For now, only support one debugger connection.
85      connectionInfoCompleter?.complete(DebugConnectionInfo(
86        httpUri: flutterDevices.first.observatoryUris.first,
87        wsUri: flutterDevices.first.vmServices.first.wsAddress,
88      ));
89    }
90
91    printTrace('Application running.');
92
93    for (FlutterDevice device in flutterDevices) {
94      if (device.vmServices == null)
95        continue;
96      device.initLogReader();
97      await device.refreshViews();
98      printTrace('Connected to ${device.device.name}');
99    }
100
101    if (traceStartup) {
102      // Only trace startup for the first device.
103      final FlutterDevice device = flutterDevices.first;
104      if (device.vmServices != null && device.vmServices.isNotEmpty) {
105        printStatus('Tracing startup on ${device.device.name}.');
106        await downloadStartupTrace(
107          device.vmServices.first,
108          awaitFirstFrame: awaitFirstFrameWhenTracing,
109        );
110      }
111      appFinished();
112    }
113
114    appStartedCompleter?.complete();
115
116    if (stayResident && !traceStartup)
117      return waitForAppToFinish();
118    await cleanupAtFinish();
119    return 0;
120  }
121
122  @override
123  Future<int> attach({
124    Completer<DebugConnectionInfo> connectionInfoCompleter,
125    Completer<void> appStartedCompleter,
126  }) async {
127    _didAttach = true;
128    try {
129      await connectToServiceProtocol();
130    } catch (error) {
131      printError('Error connecting to the service protocol: $error');
132      // https://github.com/flutter/flutter/issues/33050
133      // TODO(blasten): Remove this check once https://issuetracker.google.com/issues/132325318 has been fixed.
134      if (await hasDeviceRunningAndroidQ(flutterDevices) &&
135          error.toString().contains(kAndroidQHttpConnectionClosedExp)) {
136        printStatus('�� If you are using an emulator running Android Q Beta, consider using an emulator running API level 29 or lower.');
137        printStatus('Learn more about the status of this issue on https://issuetracker.google.com/issues/132325318');
138      }
139      return 2;
140    }
141    for (FlutterDevice device in flutterDevices) {
142      device.initLogReader();
143    }
144    await refreshViews();
145    for (FlutterDevice device in flutterDevices) {
146      for (FlutterView view in device.views) {
147        printTrace('Connected to $view.');
148      }
149    }
150    appStartedCompleter?.complete();
151    if (stayResident) {
152      return waitForAppToFinish();
153    }
154    await cleanupAtFinish();
155    return 0;
156  }
157
158  @override
159  Future<void> cleanupAfterSignal() async {
160    await stopEchoingDeviceLog();
161    if (_didAttach) {
162      appFinished();
163    }
164    await exitApp();
165  }
166
167  @override
168  Future<void> cleanupAtFinish() async {
169    await stopEchoingDeviceLog();
170  }
171
172  @override
173  void printHelp({ @required bool details }) {
174    bool haveDetails = false;
175    bool haveAnything = false;
176    for (FlutterDevice device in flutterDevices) {
177      final String dname = device.device.name;
178      if (device.observatoryUris != null) {
179        for (Uri uri in device.observatoryUris) {
180          printStatus('An Observatory debugger and profiler on $dname is available at $uri');
181          haveAnything = true;
182        }
183      }
184    }
185    if (supportsServiceProtocol) {
186      haveDetails = true;
187      if (details) {
188        printHelpDetails();
189        haveAnything = true;
190      }
191    }
192    final String quitMessage = _didAttach
193      ? 'To detach, press "d"; to quit, press "q".'
194      : 'To quit, press "q".';
195    if (haveDetails && !details) {
196      printStatus('For a more detailed help message, press "h". $quitMessage');
197    } else if (haveAnything) {
198      printStatus('To repeat this help message, press "h". $quitMessage');
199    } else {
200      printStatus(quitMessage);
201    }
202  }
203
204  @override
205  Future<void> preExit() async {
206    for (FlutterDevice device in flutterDevices) {
207      // If we're running in release mode, stop the app using the device logic.
208      if (device.vmServices == null || device.vmServices.isEmpty)
209        await device.device.stopApp(device.package);
210    }
211  }
212}
213