Create abstraction layer for dart:io's Process commands (#7100)

With this change, they're run via instance methods on an object
obtained through the context. This will allow us to substitute
that object in tests with replay/record versions to allow us to
mock out the os-layer in tests.
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index f709d4d..01c46d3 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -10,6 +10,7 @@
 
 import 'context.dart';
 import 'process.dart';
+import 'process_manager.dart';
 
 /// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
 OperatingSystemUtils get os => context[OperatingSystemUtils];
@@ -49,14 +50,14 @@
 
   @override
   ProcessResult makeExecutable(File file) {
-    return Process.runSync('chmod', <String>['a+x', file.path]);
+    return processManager.runSync('chmod', <String>['a+x', file.path]);
   }
 
   /// Return the path to the given executable, or `null` if `which` was not able
   /// to locate the binary.
   @override
   File which(String execName) {
-    ProcessResult result = Process.runSync('which', <String>[execName]);
+    ProcessResult result = processManager.runSync('which', <String>[execName]);
     if (result.exitCode != 0)
       return null;
     String path = result.stdout.trim().split('\n').first.trim();
@@ -87,7 +88,7 @@
 
   @override
   File which(String execName) {
-    ProcessResult result = Process.runSync('where', <String>[execName]);
+    ProcessResult result = processManager.runSync('where', <String>[execName]);
     if (result.exitCode != 0)
       return null;
     return new File(result.stdout.trim().split('\n').first.trim());
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 104db6c..8b20f90 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -6,6 +6,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'process_manager.dart';
 import '../globals.dart';
 
 typedef String StringConverter(String string);
@@ -44,7 +45,7 @@
   _traceCommand(cmd, workingDirectory: workingDirectory);
   String executable = cmd[0];
   List<String> arguments = cmd.length > 1 ? cmd.sublist(1) : <String>[];
-  Process process = await Process.start(
+  Process process = await processManager.start(
     executable,
     arguments,
     workingDirectory: workingDirectory,
@@ -108,13 +109,13 @@
   Future<Process> proc = runDetached(cmd);
   return new Future<Null>.delayed(timeout, () async {
     printTrace('Intentionally killing ${cmd[0]}');
-    Process.killPid((await proc).pid);
+    processManager.killPid((await proc).pid);
   });
 }
 
 Future<Process> runDetached(List<String> cmd) {
   _traceCommand(cmd);
-  Future<Process> proc = Process.start(
+  Future<Process> proc = processManager.start(
     cmd[0], cmd.getRange(1, cmd.length).toList(),
     mode: ProcessStartMode.DETACHED
   );
@@ -126,7 +127,7 @@
   bool allowReentrantFlutter: false
 }) async {
   _traceCommand(cmd, workingDirectory: workingDirectory);
-  ProcessResult results = await Process.run(
+  ProcessResult results = await processManager.run(
     cmd[0],
     cmd.getRange(1, cmd.length).toList(),
     workingDirectory: workingDirectory,
@@ -140,7 +141,7 @@
 bool exitsHappy(List<String> cli) {
   _traceCommand(cli);
   try {
-    return Process.runSync(cli.first, cli.sublist(1)).exitCode == 0;
+    return processManager.runSync(cli.first, cli.sublist(1)).exitCode == 0;
   } catch (error) {
     return false;
   }
@@ -203,7 +204,7 @@
   bool hideStdout: false,
 }) {
   _traceCommand(cmd, workingDirectory: workingDirectory);
-  ProcessResult results = Process.runSync(
+  ProcessResult results = processManager.runSync(
     cmd[0],
     cmd.getRange(1, cmd.length).toList(),
     workingDirectory: workingDirectory,
diff --git a/packages/flutter_tools/lib/src/base/process_manager.dart b/packages/flutter_tools/lib/src/base/process_manager.dart
new file mode 100644
index 0000000..0cfc1f5
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/process_manager.dart
@@ -0,0 +1,66 @@
+// Copyright 2016 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'context.dart';
+
+ProcessManager get processManager => context[ProcessManager];
+
+class ProcessManager {
+  Future<Process> start(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       ProcessStartMode mode: ProcessStartMode.NORMAL}) {
+    return Process.start(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      mode: mode,
+    );
+  }
+
+  Future<ProcessResult> run(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       Encoding stdoutEncoding: SYSTEM_ENCODING,
+       Encoding stderrEncoding: SYSTEM_ENCODING}) {
+    return Process.run(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      stdoutEncoding: stdoutEncoding,
+      stderrEncoding: stderrEncoding,
+    );
+  }
+
+  ProcessResult runSync(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       Encoding stdoutEncoding: SYSTEM_ENCODING,
+       Encoding stderrEncoding: SYSTEM_ENCODING}) {
+    return Process.runSync(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      stdoutEncoding: stdoutEncoding,
+      stderrEncoding: stderrEncoding,
+    );
+  }
+
+  bool killPid(int pid, [ProcessSignal signal = ProcessSignal.SIGTERM]) {
+    return Process.killPid(pid, signal);
+  }
+}