blob: 6dbc7d6ffad21c375deb85e8af1f3c0138262e4b [file] [log] [blame]
// Copyright 2014 The Flutter 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 'package:meta/meta.dart';
import 'base/file_system.dart';
import 'device.dart';
import 'globals.dart' as globals;
import 'resident_runner.dart';
import 'tracing.dart';
import 'vmservice.dart';
// TODO(mklim): Test this, flutter/flutter#23031.
class ColdRunner extends ResidentRunner {
ColdRunner(
List<FlutterDevice> devices, {
String target,
DebuggingOptions debuggingOptions,
this.traceStartup = false,
this.awaitFirstFrameWhenTracing = true,
this.applicationBinary,
bool ipv6 = false,
bool stayResident = true,
}) : super(devices,
target: target,
debuggingOptions: debuggingOptions,
hotMode: false,
stayResident: stayResident,
ipv6: ipv6);
final bool traceStartup;
final bool awaitFirstFrameWhenTracing;
final File applicationBinary;
bool _didAttach = false;
@override
bool get canHotReload => false;
@override
bool get canHotRestart => false;
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
}) async {
final bool prebuiltMode = applicationBinary != null;
if (!prebuiltMode) {
if (!globals.fs.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.';
}
globals.printError(message);
return 1;
}
}
for (final FlutterDevice device in flutterDevices) {
final int result = await device.runCold(
coldRunner: this,
route: route,
);
if (result != 0) {
return result;
}
}
// Connect to observatory.
if (debuggingOptions.debuggingEnabled) {
try {
await connectToServiceProtocol();
} on String catch (message) {
globals.printError(message);
return 2;
}
}
if (flutterDevices.first.observatoryUris != null) {
// For now, only support one debugger connection.
connectionInfoCompleter?.complete(DebugConnectionInfo(
httpUri: flutterDevices.first.vmService.httpAddress,
wsUri: flutterDevices.first.vmService.wsAddress,
));
}
globals.printTrace('Application running.');
for (final FlutterDevice device in flutterDevices) {
if (device.vmService == null) {
continue;
}
device.initLogReader();
await device.refreshViews();
globals.printTrace('Connected to ${device.device.name}');
}
if (traceStartup) {
// Only trace startup for the first device.
final FlutterDevice device = flutterDevices.first;
if (device.vmService != null) {
globals.printStatus('Tracing startup on ${device.device.name}.');
await downloadStartupTrace(
device.vmService,
awaitFirstFrame: awaitFirstFrameWhenTracing,
);
}
appFinished();
}
appStartedCompleter?.complete();
writeVmserviceFile();
if (stayResident && !traceStartup) {
return waitForAppToFinish();
}
await cleanupAtFinish();
return 0;
}
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
_didAttach = true;
try {
await connectToServiceProtocol();
} catch (error) {
globals.printError('Error connecting to the service protocol: $error');
// https://github.com/flutter/flutter/issues/33050
// TODO(blasten): Remove this check once https://issuetracker.google.com/issues/132325318 has been fixed.
if (await hasDeviceRunningAndroidQ(flutterDevices) &&
error.toString().contains(kAndroidQHttpConnectionClosedExp)) {
globals.printStatus('🔨 If you are using an emulator running Android Q Beta, consider using an emulator running API level 29 or lower.');
globals.printStatus('Learn more about the status of this issue on https://issuetracker.google.com/issues/132325318');
}
return 2;
}
for (final FlutterDevice device in flutterDevices) {
device.initLogReader();
}
await refreshViews();
for (final FlutterDevice device in flutterDevices) {
for (final FlutterView view in device.views) {
globals.printTrace('Connected to $view.');
}
}
appStartedCompleter?.complete();
if (stayResident) {
return waitForAppToFinish();
}
await cleanupAtFinish();
return 0;
}
@override
Future<void> cleanupAfterSignal() async {
await stopEchoingDeviceLog();
if (_didAttach) {
appFinished();
}
await exitApp();
}
@override
Future<void> cleanupAtFinish() async {
for (final FlutterDevice flutterDevice in flutterDevices) {
await flutterDevice.device.dispose();
}
await stopEchoingDeviceLog();
}
@override
void printHelp({ @required bool details }) {
globals.printStatus('Flutter run key commands.');
if (supportsServiceProtocol) {
if (details) {
printHelpDetails();
}
}
commandHelp.h.print();
if (_didAttach) {
commandHelp.d.print();
}
commandHelp.c.print();
commandHelp.q.print();
for (final FlutterDevice device in flutterDevices) {
final String dname = device.device.name;
if (device.vmService != null) {
// Caution: This log line is parsed by device lab tests.
globals.printStatus(
'An Observatory debugger and profiler on $dname is available at: '
'${device.vmService.httpAddress}',
);
}
}
}
@override
Future<void> preExit() async {
for (final FlutterDevice device in flutterDevices) {
// If we're running in release mode, stop the app using the device logic.
if (device.vmService == null) {
await device.device.stopApp(device.package);
}
}
await super.preExit();
}
}