// 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(); } }