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