// 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 'application_package.dart'; import 'base/common.dart'; import 'base/io.dart'; import 'build_info.dart'; import 'cache.dart'; import 'convert.dart'; import 'device.dart'; import 'globals.dart' as globals; import 'protocol_discovery.dart'; /// A partial implementation of Device for desktop-class devices to inherit /// from, containing implementations that are common to all desktop devices. abstract class DesktopDevice extends Device { DesktopDevice(String identifier, {@required PlatformType platformType, @required bool ephemeral}) : super( identifier, category: Category.desktop, platformType: platformType, ephemeral: ephemeral, ); final Set _runningProcesses = {}; final DesktopLogReader _deviceLogReader = DesktopLogReader(); // Since the host and target devices are the same, no work needs to be done // to install the application. @override Future isAppInstalled(ApplicationPackage app) async => true; // Since the host and target devices are the same, no work needs to be done // to install the application. @override Future isLatestBuildInstalled(ApplicationPackage app) async => true; // Since the host and target devices are the same, no work needs to be done // to install the application. @override Future installApp(ApplicationPackage app) async => true; // Since the host and target devices are the same, no work needs to be done // to uninstall the application. @override Future uninstallApp(ApplicationPackage app) async => true; @override Future get isLocalEmulator async => false; @override Future get emulatorId async => null; @override DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder(); @override Future get sdkNameAndVersion async => globals.os.name; @override DeviceLogReader getLogReader({ ApplicationPackage app }) { return _deviceLogReader; } @override void clearLogs() {} @override Future startApp( ApplicationPackage package, { String mainPath, String route, DebuggingOptions debuggingOptions, Map platformArgs, bool prebuiltApplication = false, bool ipv6 = false, }) async { if (!prebuiltApplication) { Cache.releaseLockEarly(); await buildForDevice( package, buildInfo: debuggingOptions?.buildInfo, mainPath: mainPath, ); } // Ensure that the executable is locatable. final BuildMode buildMode = debuggingOptions?.buildInfo?.mode; final String executable = executablePathForDevice(package, buildMode); if (executable == null) { globals.printError('Unable to find executable to run'); return LaunchResult.failed(); } final Process process = await globals.processManager.start([ executable, ]); _runningProcesses.add(process); unawaited(process.exitCode.then((_) => _runningProcesses.remove(process))); if (debuggingOptions?.buildInfo?.isRelease == true) { return LaunchResult.succeeded(); } _deviceLogReader.initializeProcess(process); final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_deviceLogReader, devicePort: debuggingOptions?.deviceVmServicePort, hostPort: debuggingOptions?.hostVmServicePort, ipv6: ipv6, ); try { final Uri observatoryUri = await observatoryDiscovery.uri; onAttached(package, buildMode, process); return LaunchResult.succeeded(observatoryUri: observatoryUri); } catch (error) { globals.printError('Error waiting for a debug connection: $error'); return LaunchResult.failed(); } finally { await observatoryDiscovery.cancel(); } } @override Future stopApp(ApplicationPackage app) async { bool succeeded = true; // Walk a copy of _runningProcesses, since the exit handler removes from the // set. for (final Process process in Set.from(_runningProcesses)) { succeeded &= process.kill(); } return succeeded; } @override Future dispose() async { await portForwarder?.dispose(); } /// Builds the current project for this device, with the given options. Future buildForDevice( ApplicationPackage package, { String mainPath, BuildInfo buildInfo, }); /// Returns the path to the executable to run for [package] on this device for /// the given [buildMode]. String executablePathForDevice(ApplicationPackage package, BuildMode buildMode); /// Called after a process is attached, allowing any device-specific extra /// steps to be run. void onAttached(ApplicationPackage package, BuildMode buildMode, Process process) {} } class DesktopLogReader extends DeviceLogReader { final StreamController> _inputController = StreamController>.broadcast(); void initializeProcess(Process process) { process.stdout.listen(_inputController.add); process.stderr.listen(_inputController.add); process.exitCode.then((int result) { _inputController.close(); }); } @override Stream get logLines { return _inputController.stream .transform(utf8.decoder) .transform(const LineSplitter()); } @override String get name => 'desktop'; @override void dispose() { // Nothing to dispose. } }