Add flutter run support for linux and windows (#31229)
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index 6f85e89..bf0fe9e 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -20,10 +20,12 @@ import 'globals.dart'; import 'ios/ios_workflow.dart'; import 'ios/plist_utils.dart' as plist; +import 'linux/application_package.dart'; import 'macos/application_package.dart'; import 'project.dart'; import 'tester/flutter_tester.dart'; import 'web/web_device.dart'; +import 'windows/application_package.dart'; class ApplicationPackageFactory { static ApplicationPackageFactory get instance => context[ApplicationPackageFactory]; @@ -51,12 +53,18 @@ return FlutterTesterApp.fromCurrentDirectory(); case TargetPlatform.darwin_x64: return applicationBinary == null - ? MacOSApp.fromMacOSProject((await FlutterProject.current()).macos) - : MacOSApp.fromPrebuiltApp(applicationBinary); + ? MacOSApp.fromMacOSProject((await FlutterProject.current()).macos) + : MacOSApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.web: return WebApplicationPackage(await FlutterProject.current()); case TargetPlatform.linux_x64: + return applicationBinary == null + ? LinuxApp.fromLinuxProject((await FlutterProject.current()).linux) + : LinuxApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.windows_x64: + return applicationBinary == null + ? WindowsApp.fromWindowsProject((await FlutterProject.current()).windows) + : WindowsApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.fuchsia: return null; }
diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart index a832682..5f11a77 100644 --- a/packages/flutter_tools/lib/src/commands/build_linux.dart +++ b/packages/flutter_tools/lib/src/commands/build_linux.dart
@@ -5,13 +5,10 @@ import 'dart:async'; import '../base/common.dart'; -import '../base/io.dart'; import '../base/platform.dart'; -import '../base/process_manager.dart'; import '../build_info.dart'; import '../cache.dart'; -import '../convert.dart'; -import '../globals.dart'; +import '../linux/build_linux.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; @@ -62,24 +59,7 @@ if (!flutterProject.linux.existsSync()) { throwToolExit('No Linux desktop project configured.'); } - final Process process = await processManager.start(<String>[ - flutterProject.linux.buildScript.path, - Cache.flutterRoot, - buildInfo.isDebug ? 'debug' : 'release', - buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation', - ], runInShell: true); - process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printError); - process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printStatus); - final int result = await process.exitCode; - if (result != 0) { - throwToolExit('Build process failed'); - } + await buildLinux(flutterProject.linux, buildInfo); return null; } }
diff --git a/packages/flutter_tools/lib/src/commands/build_windows.dart b/packages/flutter_tools/lib/src/commands/build_windows.dart index a8d1740..8db6cdb 100644 --- a/packages/flutter_tools/lib/src/commands/build_windows.dart +++ b/packages/flutter_tools/lib/src/commands/build_windows.dart
@@ -5,15 +5,12 @@ import 'dart:async'; import '../base/common.dart'; -import '../base/io.dart'; import '../base/platform.dart'; -import '../base/process_manager.dart'; import '../build_info.dart'; import '../cache.dart'; -import '../convert.dart'; -import '../globals.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; +import '../windows/build_windows.dart'; import 'build.dart'; /// A command to build a windows desktop target through a build shell script. @@ -62,24 +59,7 @@ if (!flutterProject.windows.existsSync()) { throwToolExit('No Windows desktop project configured.'); } - final Process process = await processManager.start(<String>[ - flutterProject.windows.buildScript.path, - Cache.flutterRoot, - buildInfo.isDebug ? 'debug' : 'release', - buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation', - ], runInShell: true); - process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printError); - process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printStatus); - final int result = await process.exitCode; - if (result != 0) { - throwToolExit('Build process failed'); - } + await buildWindows(flutterProject.windows, buildInfo); return null; } }
diff --git a/packages/flutter_tools/lib/src/desktop.dart b/packages/flutter_tools/lib/src/desktop.dart index 791d33c..e00342d 100644 --- a/packages/flutter_tools/lib/src/desktop.dart +++ b/packages/flutter_tools/lib/src/desktop.dart
@@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'base/io.dart'; import 'base/platform.dart'; +import 'base/process_manager.dart'; import 'version.dart'; // Only launch or display desktop embedding devices if @@ -12,3 +14,36 @@ return _flutterDesktopEnabled && !FlutterVersion.instance.isStable; } bool _flutterDesktopEnabled; + +/// Kills a process on linux or macOS. +Future<bool> killProcess(String executable) async { + final RegExp whitespace = RegExp(r'\s+'); + bool succeeded = true; + try { + final ProcessResult result = await processManager.run(<String>[ + 'ps', 'aux', + ]); + if (result.exitCode != 0) { + return false; + } + final List<String> lines = result.stdout.split('\n'); + for (String line in lines) { + if (!line.contains(executable)) { + continue; + } + final List<String> values = line.split(whitespace); + if (values.length < 2) { + continue; + } + final String pid = values[1]; + final ProcessResult killResult = await processManager.run(<String>[ + 'kill', pid, + ]); + succeeded &= killResult.exitCode == 0; + } + return true; + } on ArgumentError { + succeeded = false; + } + return succeeded; +}
diff --git a/packages/flutter_tools/lib/src/linux/application_package.dart b/packages/flutter_tools/lib/src/linux/application_package.dart new file mode 100644 index 0000000..7bf5c6a --- /dev/null +++ b/packages/flutter_tools/lib/src/linux/application_package.dart
@@ -0,0 +1,74 @@ +// Copyright 2019 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 'package:meta/meta.dart'; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../project.dart'; + +abstract class LinuxApp extends ApplicationPackage { + LinuxApp({@required String projectBundleId}) : super(id: projectBundleId); + + /// Creates a new [LinuxApp] from a linux sub project. + factory LinuxApp.fromLinuxProject(LinuxProject project) { + return BuildableLinuxApp( + project: project, + ); + } + + /// Creates a new [LinuxApp] from an existing executable. + /// + /// `applicationBinary` is the path to the executable. + factory LinuxApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { + return PrebuiltLinuxApp( + executable: applicationBinary.path, + ); + } + + @override + String get displayName => id; + + String executable(BuildMode buildMode); +} + +class PrebuiltLinuxApp extends LinuxApp { + PrebuiltLinuxApp({ + @required String executable, + }) : _executable = executable, + super(projectBundleId: executable); + + final String _executable; + + @override + String executable(BuildMode buildMode) => _executable; + + @override + String get name => _executable; +} + +class BuildableLinuxApp extends LinuxApp { + BuildableLinuxApp({this.project}) : super(projectBundleId: project.project.manifest.appName); + + final LinuxProject project; + + @override + String executable(BuildMode buildMode) { + final ProcessResult result = processManager.runSync(<String>[ + project.nameScript.path, + buildMode == BuildMode.debug ? 'debug' : 'release', + ]); + if (result.exitCode != 0) { + throwToolExit('Failed to find Linux project name'); + } + return result.stdout.toString().trim(); + } + + @override + String get name => project.project.manifest.appName; +}
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart new file mode 100644 index 0000000..465259e --- /dev/null +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -0,0 +1,44 @@ +// Copyright 2019 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 '../base/common.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../cache.dart'; +import '../convert.dart'; +import '../globals.dart'; +import '../project.dart'; + +/// Builds the Linux project through the project shell script. +Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo) async { + final Process process = await processManager.start(<String>[ + linuxProject.buildScript.path, + Cache.flutterRoot, + buildInfo?.isDebug == true ? 'debug' : 'release', + buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation', + ], runInShell: true); + final Status status = logger.startProgress( + 'Building Linux application...', + timeout: null, + ); + int result; + try { + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printError); + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printTrace); + result = await process.exitCode; + } finally { + status.cancel(); + } + if (result != 0) { + throwToolExit('Build process failed'); + } +}
diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart index fb88fbf..72fe802 100644 --- a/packages/flutter_tools/lib/src/linux/linux_device.dart +++ b/packages/flutter_tools/lib/src/linux/linux_device.dart
@@ -3,10 +3,19 @@ // 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 '../desktop.dart'; import '../device.dart'; +import '../globals.dart'; +import '../project.dart'; +import '../protocol_discovery.dart'; +import 'application_package.dart'; +import 'build_linux.dart'; import 'linux_workflow.dart'; /// A device that represents a desktop Linux target. @@ -19,20 +28,20 @@ @override DeviceLogReader getLogReader({ ApplicationPackage app }) => NoOpDeviceLogReader('linux'); + // Since the host and target devices are the same, no work needs to be done + // to install the application. @override - Future<bool> installApp(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> 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<bool> isAppInstalled(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> 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<bool> isLatestBuildInstalled(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; @override Future<bool> get isLocalEmulator async => false; @@ -51,7 +60,7 @@ @override Future<LaunchResult> startApp( - ApplicationPackage package, { + covariant LinuxApp package, { String mainPath, String route, DebuggingOptions debuggingOptions, @@ -59,22 +68,43 @@ bool prebuiltApplication = false, bool usesTerminalUi = true, bool ipv6 = false, - }) { - throw UnimplementedError(); + }) async { + if (!prebuiltApplication) { + await buildLinux((await FlutterProject.current()).linux, debuggingOptions.buildInfo); + } + await stopApp(package); + final Process process = await processManager.start(<String>[ + package.executable(debuggingOptions?.buildInfo?.mode) + ]); + if (debuggingOptions?.buildInfo?.isRelease == true) { + return LaunchResult.succeeded(); + } + final LinuxLogReader logReader = LinuxLogReader(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(); + } } @override - Future<bool> stopApp(ApplicationPackage app) { - throw UnimplementedError(); + Future<bool> stopApp(covariant LinuxApp app) async { + // Assume debug for now. + return killProcess(app.executable(BuildMode.debug)); } @override Future<TargetPlatform> get targetPlatform async => TargetPlatform.linux_x64; + // Since the host and target devices are the same, no work needs to be done + // to uninstall the application. @override - Future<bool> uninstallApp(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> uninstallApp(ApplicationPackage app) async => true; } class LinuxDevices extends PollingDeviceDiscovery { @@ -99,3 +129,18 @@ @override Future<List<String>> getDiagnostics() async => const <String>[]; } + +class LinuxLogReader extends DeviceLogReader { + LinuxLogReader(this.linuxApp, this.process); + + final LinuxApp linuxApp; + final Process process; + + @override + Stream<String> get logLines { + return process.stdout.transform(utf8.decoder); + } + + @override + String get name => linuxApp.displayName; +}
diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index ccfd40e..1cc9028 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart
@@ -10,6 +10,7 @@ import '../build_info.dart'; import '../cache.dart'; import '../convert.dart'; +import '../desktop.dart'; import '../device.dart'; import '../globals.dart'; import '../macos/application_package.dart'; @@ -103,37 +104,8 @@ // currently we rely on killing the isolate taking down the application. @override Future<bool> stopApp(covariant MacOSApp app) async { - final RegExp whitespace = RegExp(r'\s+'); - bool succeeded = true; - // assume debug for now. - final String executable = app.executable(BuildMode.debug); - try { - final ProcessResult result = await processManager.run(<String>[ - 'ps', 'aux', - ]); - if (result.exitCode != 0) { - return false; - } - final List<String> lines = result.stdout.split('\n'); - for (String line in lines) { - if (!line.contains(executable)) { - continue; - } - final List<String> values = line.split(whitespace); - if (values.length < 2) { - continue; - } - final String pid = values[1]; - final ProcessResult killResult = await processManager.run(<String>[ - 'kill', pid, - ]); - succeeded &= killResult.exitCode == 0; - } - return true; - } on ArgumentError { - succeeded = false; - } - return succeeded; + // Assume debug for now. + return killProcess(app.executable(BuildMode.debug)); } @override
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index ae80a3d..2cd22e8 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -555,7 +555,7 @@ File get buildScript => project.directory.childDirectory('windows').childFile('build.bat'); // Note: The name script file exists as a temporary shim. - File get nameScript => project.directory.childDirectory('windows').childFile('name_output.sh'); + File get nameScript => project.directory.childDirectory('windows').childFile('name_output.bat'); } /// The Linux sub project.
diff --git a/packages/flutter_tools/lib/src/windows/application_package.dart b/packages/flutter_tools/lib/src/windows/application_package.dart new file mode 100644 index 0000000..01ece5f --- /dev/null +++ b/packages/flutter_tools/lib/src/windows/application_package.dart
@@ -0,0 +1,76 @@ +// Copyright 2019 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 'package:meta/meta.dart'; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../project.dart'; + +abstract class WindowsApp extends ApplicationPackage { + WindowsApp({@required String projectBundleId}) : super(id: projectBundleId); + + /// Creates a new [WindowsApp] from a windows sub project. + factory WindowsApp.fromWindowsProject(WindowsProject project) { + return BuildableWindowsApp( + project: project, + ); + } + + /// Creates a new [WindowsApp] from an existing executable. + /// + /// `applicationBinary` is the path to the executable. + factory WindowsApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { + return PrebuiltWindowsApp( + executable: applicationBinary.path, + ); + } + + @override + String get displayName => id; + + String executable(BuildMode buildMode); +} + +class PrebuiltWindowsApp extends WindowsApp { + PrebuiltWindowsApp({ + @required String executable, + }) : _executable = executable, + super(projectBundleId: executable); + + final String _executable; + + @override + String executable(BuildMode buildMode) => _executable; + + @override + String get name => _executable; +} + +class BuildableWindowsApp extends WindowsApp { + BuildableWindowsApp({ + @required this.project, + }) : super(projectBundleId: project.project.manifest.appName); + + final WindowsProject project; + + @override + String executable(BuildMode buildMode) { + final ProcessResult result = processManager.runSync(<String>[ + project.nameScript.path, + buildMode == BuildMode.debug ? 'debug' : 'release', + ]); + if (result.exitCode != 0) { + throwToolExit('Failed to find Windows project name'); + } + return result.stdout.toString().trim(); + } + + @override + String get name => project.project.manifest.appName; +}
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart new file mode 100644 index 0000000..d2aabfe --- /dev/null +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -0,0 +1,44 @@ +// Copyright 2019 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 '../base/common.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../cache.dart'; +import '../convert.dart'; +import '../globals.dart'; +import '../project.dart'; + +/// Builds the Windows project through the project bat script. +Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo) async { + final Process process = await processManager.start(<String>[ + windowsProject.buildScript.path, + Cache.flutterRoot, + buildInfo.isDebug ? 'debug' : 'release', + buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation', + ], runInShell: true); + final Status status = logger.startProgress( + 'Building Windows application...', + timeout: null, + ); + int result; + try { + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printError); + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printTrace); + result = await process.exitCode; + } finally { + status.cancel(); + } + if (result != 0) { + throwToolExit('Build process failed'); + } +}
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart index c019581..837e23d 100644 --- a/packages/flutter_tools/lib/src/windows/windows_device.dart +++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -2,11 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; + 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 '../project.dart'; +import '../protocol_discovery.dart'; +import 'application_package.dart'; +import 'build_windows.dart'; import 'windows_workflow.dart'; /// A device that represents a desktop Windows target. @@ -19,20 +29,20 @@ @override DeviceLogReader getLogReader({ ApplicationPackage app }) => NoOpDeviceLogReader('windows'); + // Since the host and target devices are the same, no work needs to be done + // to install the application. @override - Future<bool> installApp(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> 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<bool> isAppInstalled(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> 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<bool> isLatestBuildInstalled(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true; @override Future<bool> get isLocalEmulator async => false; @@ -51,7 +61,7 @@ @override Future<LaunchResult> startApp( - ApplicationPackage package, { + covariant WindowsApp package, { String mainPath, String route, DebuggingOptions debuggingOptions, @@ -59,22 +69,48 @@ bool prebuiltApplication = false, bool usesTerminalUi = true, bool ipv6 = false, - }) { - throw UnimplementedError(); + }) async { + if (!prebuiltApplication) { + await buildWindows((await FlutterProject.current()).windows, debuggingOptions.buildInfo); + } + await stopApp(package); + final Process process = await processManager.start(<String>[ + package.executable(debuggingOptions?.buildInfo?.mode) + ]); + if (debuggingOptions?.buildInfo?.isRelease == true) { + return LaunchResult.succeeded(); + } + final WindowsLogReader logReader = WindowsLogReader(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(); + } } @override - Future<bool> stopApp(ApplicationPackage app) { - throw UnimplementedError(); + Future<bool> stopApp(covariant WindowsApp app) async { + // Assume debug for now. + final List<String> process = runningProcess(app.executable(BuildMode.debug)); + if (process == null) { + return false; + } + final ProcessResult result = await processManager.run(<String>['Taskkill', '/PID', process.first, '/F']); + return result.exitCode == 0; } @override Future<TargetPlatform> get targetPlatform async => TargetPlatform.windows_x64; + // Since the host and target devices are the same, no work needs to be done + // to uninstall the application. @override - Future<bool> uninstallApp(ApplicationPackage app) { - throw UnimplementedError(); - } + Future<bool> uninstallApp(ApplicationPackage app) async => true; } class WindowsDevices extends PollingDeviceDiscovery { @@ -99,3 +135,45 @@ @override Future<List<String>> getDiagnostics() async => const <String>[]; } + +final RegExp _whitespace = RegExp(r'\w+'); + +/// Returns the running process matching `process` name. +/// +/// This list contains the process name and id. +@visibleForTesting +List<String> runningProcess(String processName) { + // TODO(jonahwilliams): find a way to do this without powershell. + final ProcessResult result = processManager.runSync(<String>['powershell', '-script="Get-CimInstance Win32_Process"']); + if (result.exitCode != 0) { + return null; + } + for (String rawProcess in result.stdout.split('\n')) { + final String process = rawProcess.trim(); + if (!process.contains(processName)) { + continue; + } + final List<String> parts = process.split(_whitespace); + final List<String> data = <String>[ + parts[0], // ID + parts[1], // Name + ]; + return data; + } + return null; +} + +class WindowsLogReader extends DeviceLogReader { + WindowsLogReader(this.windowsApp, this.process); + + final WindowsApp windowsApp; + final Process process; + + @override + Stream<String> get logLines { + return process.stdout.transform(utf8.decoder); + } + + @override + String get name => windowsApp.displayName; +}
diff --git a/packages/flutter_tools/test/linux/linux_device_test.dart b/packages/flutter_tools/test/linux/linux_device_test.dart index 49d66de..c06743d 100644 --- a/packages/flutter_tools/test/linux/linux_device_test.dart +++ b/packages/flutter_tools/test/linux/linux_device_test.dart
@@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/linux/application_package.dart'; import 'package:flutter_tools/src/linux/linux_device.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; import '../src/common.dart'; import '../src/context.dart'; @@ -15,21 +19,30 @@ group(LinuxDevice, () { final LinuxDevice device = LinuxDevice(); final MockPlatform notLinux = MockPlatform(); + final MockProcessManager mockProcessManager = MockProcessManager(); + when(notLinux.isLinux).thenReturn(false); when(notLinux.environment).thenReturn(const <String, String>{}); - - test('defaults', () async { - expect(await device.targetPlatform, TargetPlatform.linux_x64); - expect(device.name, 'Linux'); + when(mockProcessManager.run(<String>[ + 'ps', 'aux', + ])).thenAnswer((Invocation invocation) async { + final MockProcessResult result = MockProcessResult(); + when(result.exitCode).thenReturn(0); + when<String>(result.stdout).thenReturn(''); + return result; }); - test('unimplemented methods', () { - expect(() => device.installApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.uninstallApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.isLatestBuildInstalled(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.startApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.stopApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.isAppInstalled(null), throwsA(isInstanceOf<UnimplementedError>())); + testUsingContext('defaults', () async { + final PrebuiltLinuxApp linuxApp = PrebuiltLinuxApp(executable: 'foo'); + expect(await device.targetPlatform, TargetPlatform.linux_x64); + expect(device.name, 'Linux'); + expect(await device.installApp(linuxApp), true); + expect(await device.uninstallApp(linuxApp), true); + expect(await device.isLatestBuildInstalled(linuxApp), true); + expect(await device.isAppInstalled(linuxApp), true); + expect(await device.stopApp(linuxApp), true); + }, overrides: <Type, Generator>{ + ProcessManager: () => mockProcessManager, }); test('noop port forwarding', () async { @@ -49,3 +62,13 @@ } class MockPlatform extends Mock implements Platform {} + +class MockFileSystem extends Mock implements FileSystem {} + +class MockFile extends Mock implements File {} + +class MockProcessManager extends Mock implements ProcessManager {} + +class MockProcess extends Mock implements Process {} + +class MockProcessResult extends Mock implements ProcessResult {} \ No newline at end of file
diff --git a/packages/flutter_tools/test/windows/windows_device_test.dart b/packages/flutter_tools/test/windows/windows_device_test.dart index ff9cb5f..7a18c2c 100644 --- a/packages/flutter_tools/test/windows/windows_device_test.dart +++ b/packages/flutter_tools/test/windows/windows_device_test.dart
@@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/windows/application_package.dart'; import 'package:flutter_tools/src/windows/windows_device.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; import '../src/common.dart'; import '../src/context.dart'; @@ -15,21 +19,30 @@ group(WindowsDevice, () { final WindowsDevice device = WindowsDevice(); final MockPlatform notWindows = MockPlatform(); + final MockProcessManager mockProcessManager = MockProcessManager(); + when(notWindows.isWindows).thenReturn(false); when(notWindows.environment).thenReturn(const <String, String>{}); - - test('defaults', () async { - expect(await device.targetPlatform, TargetPlatform.windows_x64); - expect(device.name, 'Windows'); + when(mockProcessManager.runSync(<String>[ + 'powershell', '-script="Get-CimInstance Win32_Process"' + ])).thenAnswer((Invocation invocation) { + final MockProcessResult result = MockProcessResult(); + when(result.exitCode).thenReturn(0); + when<String>(result.stdout).thenReturn(''); + return result; }); - test('unimplemented methods', () { - expect(() => device.installApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.uninstallApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.isLatestBuildInstalled(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.startApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.stopApp(null), throwsA(isInstanceOf<UnimplementedError>())); - expect(() => device.isAppInstalled(null), throwsA(isInstanceOf<UnimplementedError>())); + testUsingContext('defaults', () async { + final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo'); + expect(await device.targetPlatform, TargetPlatform.windows_x64); + expect(device.name, 'Windows'); + expect(await device.installApp(windowsApp), true); + expect(await device.uninstallApp(windowsApp), true); + expect(await device.isLatestBuildInstalled(windowsApp), true); + expect(await device.isAppInstalled(windowsApp), true); + expect(await device.stopApp(windowsApp), false); + }, overrides: <Type, Generator>{ + ProcessManager: () => mockProcessManager, }); test('noop port forwarding', () async { @@ -49,3 +62,13 @@ } class MockPlatform extends Mock implements Platform {} + +class MockFileSystem extends Mock implements FileSystem {} + +class MockFile extends Mock implements File {} + +class MockProcessManager extends Mock implements ProcessManager {} + +class MockProcess extends Mock implements Process {} + +class MockProcessResult extends Mock implements ProcessResult {} \ No newline at end of file