blob: 47577634648d65d42a3d52e5848beb942924ffc9 [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 'base/file_system.dart';
import 'base/logger.dart';
import 'build_info.dart';
import 'globals.dart' as globals;
import 'resident_runner.dart';
import 'tracing.dart';
import 'vmservice.dart';
const String kFlutterTestOutputsDirEnvName = 'FLUTTER_TEST_OUTPUTS_DIR';
class ColdRunner extends ResidentRunner {
ColdRunner(
super.devices, {
required super.target,
required super.debuggingOptions,
this.traceStartup = false,
this.awaitFirstFrameWhenTracing = true,
this.applicationBinary,
this.multidexEnabled = false,
bool super.ipv6 = false,
super.stayResident,
super.machine,
super.devtoolsHandler,
}) : super(
hotMode: false,
);
final bool traceStartup;
final bool awaitFirstFrameWhenTracing;
final File? applicationBinary;
final bool multidexEnabled;
bool _didAttach = false;
@override
bool get canHotReload => false;
@override
Logger get logger => globals.logger;
@override
FileSystem get fileSystem => globals.fs;
@override
Future<int> run({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool enableDevTools = false,
String? route,
}) async {
try {
for (final FlutterDevice? device in flutterDevices) {
final int result = await device!.runCold(
coldRunner: this,
route: route,
);
if (result != 0) {
appFailedToStart();
return result;
}
}
} on Exception catch (err, stack) {
globals.printError('$err\n$stack');
appFailedToStart();
return 1;
}
// Connect to observatory.
if (debuggingEnabled) {
try {
await connectToServiceProtocol(allowExistingDdsInstance: false);
} on Exception catch (exception) {
globals.printError(exception.toString());
appFailedToStart();
return 2;
}
}
if (enableDevTools && debuggingEnabled) {
// The method below is guaranteed never to return a failing future.
unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
flutterDevices: flutterDevices,
));
}
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;
}
await device.initLogReader();
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}.');
final String outputPath = globals.platform.environment[kFlutterTestOutputsDirEnvName] ?? getBuildDirectory();
await downloadStartupTrace(
device.vmService!,
awaitFirstFrame: awaitFirstFrameWhenTracing,
logger: globals.logger,
output: globals.fs.directory(outputPath),
);
}
appFinished();
}
appStartedCompleter?.complete();
writeVmServiceFile();
if (stayResident && !traceStartup) {
return waitForAppToFinish();
}
await cleanupAtFinish();
return 0;
}
@override
Future<int> attach({
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool allowExistingDdsInstance = false,
bool enableDevTools = false,
bool needsFullRestart = true,
}) async {
_didAttach = true;
try {
await connectToServiceProtocol(
getSkSLMethod: writeSkSL,
allowExistingDdsInstance: allowExistingDdsInstance,
);
} on Exception catch (error) {
globals.printError('Error connecting to the service protocol: $error');
return 2;
}
for (final FlutterDevice? device in flutterDevices) {
await device!.initLogReader();
}
for (final FlutterDevice? device in flutterDevices) {
final List<FlutterView> views = await device!.vmService!.getFlutterViews();
for (final FlutterView view in views) {
globals.printTrace('Connected to $view.');
}
}
if (enableDevTools && debuggingEnabled) {
// The method below is guaranteed never to return a failing future.
unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
flutterDevices: flutterDevices,
));
}
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 residentDevtoolsHandler!.shutdown();
await stopEchoingDeviceLog();
}
@override
void printHelp({ required bool details }) {
globals.printStatus('Flutter run key commands.');
if (details) {
printHelpDetails();
commandHelp.hWithDetails.print();
} else {
commandHelp.hWithoutDetails.print();
}
if (_didAttach) {
commandHelp.d.print();
}
commandHelp.c.print();
commandHelp.q.print();
printDebuggerList();
}
@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, userIdentifier: device.userIdentifier);
}
}
await super.preExit();
}
}