// Copyright 2018 The Chromium 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 '../application_package.dart'; import '../base/io.dart'; import '../base/os.dart'; import '../base/platform.dart'; import '../base/process_manager.dart'; import '../build_info.dart'; import '../convert.dart'; import '../device.dart'; import '../globals.dart'; import '../macos/application_package.dart'; import '../protocol_discovery.dart'; import 'macos_workflow.dart'; /// A device that represents a desktop MacOS target. class MacOSDevice extends Device { MacOSDevice() : super('macOS'); @override void clearLogs() {} @override DeviceLogReader getLogReader({ ApplicationPackage app }) => NoOpDeviceLogReader('macos'); // 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 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; @override Future get isLocalEmulator async => false; @override bool isSupported() => true; @override String get name => 'macOS'; @override DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder(); @override Future get sdkNameAndVersion async => os.name; @override Future startApp( covariant MacOSApp package, { String mainPath, String route, DebuggingOptions debuggingOptions, Map platformArgs, bool prebuiltApplication = false, bool applicationNeedsRebuild = false, bool usesTerminalUi = true, bool ipv6 = false, }) async { if (!prebuiltApplication) { return LaunchResult.failed(); } // Stop any running applications with the same executable. await stopApp(package); final Process process = await processManager.start([package.executable]); final MacOSLogReader logReader = MacOSLogReader(package, process); final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(logReader); try { final Uri observatoryUri = await observatoryDiscovery.uri; return LaunchResult.succeeded(observatoryUri: observatoryUri); } catch (error) { printError('Error waiting for a debug connection: $error'); return LaunchResult.failed(); } finally { await observatoryDiscovery.cancel(); } } // TODO(jonahwilliams): implement using process manager. // currently we rely on killing the isolate taking down the application. @override Future stopApp(covariant MacOSApp app) async { final RegExp whitespace = RegExp(r'\s+'); bool succeeded = true; try { final ProcessResult result = await processManager.run([ 'ps', 'aux', ]); if (result.exitCode != 0) { return false; } final List lines = result.stdout.split('\n'); for (String line in lines) { if (!line.contains(app.executable)) { continue; } final List values = line.split(whitespace); if (values.length < 2) { continue; } final String pid = values[1]; final ProcessResult killResult = await processManager.run([ 'kill', pid, ]); succeeded &= killResult.exitCode == 0; } return true; } on ArgumentError { succeeded = false; } return succeeded; } @override Future get targetPlatform async => TargetPlatform.darwin_x64; // 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; } class MacOSDevices extends PollingDeviceDiscovery { MacOSDevices() : super('macOS devices'); @override bool get supportsPlatform => platform.isMacOS; @override bool get canListAnything => macOSWorkflow.canListDevices; @override Future> pollingGetDevices() async { if (!canListAnything) { return const []; } return [ MacOSDevice(), ]; } @override Future> getDiagnostics() async => const []; } class MacOSLogReader extends DeviceLogReader { MacOSLogReader(this.macOSApp, this.process); final MacOSApp macOSApp; final Process process; @override Stream get logLines { return process.stdout.transform(utf8.decoder); } @override String get name => macOSApp.displayName; }