[flutter_tool] process.dart cleanup (#39899)

diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 327197e..d42b4c6 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -227,11 +227,14 @@
         String workingDirectory,
         bool allowReentrantFlutter = false,
         Map<String, String> environment}) {
-    return runCheckedSync(adbCommandForDevice(params), workingDirectory: workingDirectory,
-        allowReentrantFlutter: allowReentrantFlutter,
-        environment: environment,
-        whiteListFailures: allowHeapCorruptionOnWindows
-    );
+    return processUtils.runSync(
+      adbCommandForDevice(params),
+      throwOnError: true,
+      workingDirectory: workingDirectory,
+      allowReentrantFlutter: allowReentrantFlutter,
+      environment: environment,
+      whiteListFailures: allowHeapCorruptionOnWindows,
+    ).stdout.trim();
   }
 
   Future<RunResult> runAdbCheckedAsync(
@@ -239,9 +242,13 @@
         String workingDirectory,
         bool allowReentrantFlutter = false,
       }) async {
-    return runCheckedAsync(adbCommandForDevice(params), workingDirectory: workingDirectory,
-        allowReentrantFlutter: allowReentrantFlutter,
-        whiteListFailures: allowHeapCorruptionOnWindows);
+    return processUtils.run(
+      adbCommandForDevice(params),
+      throwOnError: true,
+      workingDirectory: workingDirectory,
+      allowReentrantFlutter: allowReentrantFlutter,
+      whiteListFailures: allowHeapCorruptionOnWindows,
+    );
   }
 
   bool _isValidAdbVersion(String adbVersion) {
@@ -268,13 +275,18 @@
   }
 
   Future<bool> _checkForSupportedAdbVersion() async {
-    if (androidSdk == null)
+    if (androidSdk == null) {
       return false;
+    }
 
     try {
-      final RunResult adbVersion = await runCheckedAsync(<String>[getAdbPath(androidSdk), 'version']);
-      if (_isValidAdbVersion(adbVersion.stdout))
+      final RunResult adbVersion = await processUtils.run(
+        <String>[getAdbPath(androidSdk), 'version'],
+        throwOnError: true,
+      );
+      if (_isValidAdbVersion(adbVersion.stdout)) {
         return true;
+      }
       printError('The ADB at "${getAdbPath(androidSdk)}" is too old; please install version 1.0.39 or later.');
     } catch (error, trace) {
       printError('Error running ADB: $error', stackTrace: trace);
@@ -289,7 +301,10 @@
       // output lines like this, which we want to ignore:
       //   adb server is out of date.  killing..
       //   * daemon started successfully *
-      await runCheckedAsync(<String>[getAdbPath(androidSdk), 'start-server']);
+      await processUtils.run(
+        <String>[getAdbPath(androidSdk), 'start-server'],
+        throwOnError: true,
+      );
 
       // Sample output: '22'
       final String sdkVersion = await _getProperty('ro.build.version.sdk');
@@ -320,7 +335,8 @@
   }
 
   Future<String> _getDeviceApkSha1(ApplicationPackage app) async {
-    final RunResult result = await runAsync(adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
+    final RunResult result = await processUtils.run(
+      adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
     return result.stdout;
   }
 
@@ -363,7 +379,8 @@
       return false;
 
     final Status status = logger.startProgress('Installing ${fs.path.relative(apk.file.path)}...', timeout: timeoutConfiguration.slowOperation);
-    final RunResult installResult = await runAsync(adbCommandForDevice(<String>['install', '-t', '-r', apk.file.path]));
+    final RunResult installResult = await processUtils.run(
+      adbCommandForDevice(<String>['install', '-t', '-r', apk.file.path]));
     status.stop();
     // Some versions of adb exit with exit code 0 even on failure :(
     // Parsing the output to check for failures.
@@ -396,7 +413,11 @@
 
     String uninstallOut;
     try {
-      uninstallOut = (await runCheckedAsync(adbCommandForDevice(<String>['uninstall', app.id]))).stdout;
+      final RunResult uninstallResult = await processUtils.run(
+        adbCommandForDevice(<String>['uninstall', app.id]),
+        throwOnError: true,
+      );
+      uninstallOut = uninstallResult.stdout;
     } catch (error) {
       printError('adb uninstall failed: $error');
       return false;
@@ -603,13 +624,13 @@
   @override
   Future<bool> stopApp(ApplicationPackage app) {
     final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
-    return runCommandAndStreamOutput(command).then<bool>(
+    return processUtils.stream(command).then<bool>(
         (int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode));
   }
 
   @override
   void clearLogs() {
-    runSync(adbCommandForDevice(<String>['logcat', '-c']));
+    processUtils.runSync(adbCommandForDevice(<String>['logcat', '-c']));
   }
 
   @override
@@ -650,7 +671,10 @@
   Future<void> takeScreenshot(File outputFile) async {
     const String remotePath = '/data/local/tmp/flutter_screenshot.png';
     await runAdbCheckedAsync(<String>['shell', 'screencap', '-p', remotePath]);
-    await runCheckedAsync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
+    await processUtils.run(
+      adbCommandForDevice(<String>['pull', remotePath, outputFile.path]),
+      throwOnError: true,
+    );
     await runAdbCheckedAsync(<String>['shell', 'rm', remotePath]);
   }
 
@@ -675,7 +699,10 @@
     return <AndroidDevice>[];
   String text;
   try {
-    text = runSync(<String>[adbPath, 'devices', '-l']);
+    text = processUtils.runSync(
+      <String>[adbPath, 'devices', '-l'],
+      throwOnError: true,
+    ).stdout.trim();
   } on ArgumentError catch (exception) {
     throwToolExit('Unable to find "adb", check your Android SDK installation and '
       'ANDROID_HOME environment variable: ${exception.message}');
@@ -694,7 +721,7 @@
   if (adbPath == null)
     return <String>[];
 
-  final RunResult result = await runAsync(<String>[adbPath, 'devices', '-l']);
+  final RunResult result = await processUtils.run(<String>[adbPath, 'devices', '-l']);
   if (result.exitCode != 0) {
     return <String>[];
   } else {
@@ -816,11 +843,12 @@
     // Start the adb logcat process.
     final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time'];
     final String lastTimestamp = device.lastLogcatTimestamp;
-    if (lastTimestamp != null)
+    if (lastTimestamp != null) {
       _timeOrigin = _adbTimestampToDateTime(lastTimestamp);
-    else
+    } else {
       _timeOrigin = null;
-    runCommand(device.adbCommandForDevice(args)).then<void>((Process process) {
+    }
+    processUtils.start(device.adbCommandForDevice(args)).then<void>((Process process) {
       _process = process;
       // We expect logcat streams to occasionally contain invalid utf-8,
       // see: https://github.com/flutter/flutter/pull/8864.
@@ -959,33 +987,37 @@
 
     String stdout;
     try {
-      stdout = runCheckedSync(device.adbCommandForDevice(
-        <String>['forward', '--list']
-      ));
-    } catch (error) {
+      stdout = processUtils.runSync(
+        device.adbCommandForDevice(<String>['forward', '--list']),
+        throwOnError: true,
+      ).stdout.trim();
+    } on ProcessException catch (error) {
       printError('Failed to list forwarded ports: $error.');
       return ports;
     }
 
     final List<String> lines = LineSplitter.split(stdout).toList();
     for (String line in lines) {
-      if (line.startsWith(device.id)) {
-        final List<String> splitLine = line.split('tcp:');
-
-        // Sanity check splitLine.
-        if (splitLine.length != 3)
-          continue;
-
-        // Attempt to extract ports.
-        final int hostPort = _extractPort(splitLine[1]);
-        final int devicePort = _extractPort(splitLine[2]);
-
-        // Failed, skip.
-        if (hostPort == null || devicePort == null)
-          continue;
-
-        ports.add(ForwardedPort(hostPort, devicePort));
+      if (!line.startsWith(device.id)) {
+        continue;
       }
+      final List<String> splitLine = line.split('tcp:');
+
+      // Sanity check splitLine.
+      if (splitLine.length != 3) {
+        continue;
+      }
+
+      // Attempt to extract ports.
+      final int hostPort = _extractPort(splitLine[1]);
+      final int devicePort = _extractPort(splitLine[2]);
+
+      // Failed, skip.
+      if (hostPort == null || devicePort == null) {
+        continue;
+      }
+
+      ports.add(ForwardedPort(hostPort, devicePort));
     }
 
     return ports;
@@ -994,23 +1026,35 @@
   @override
   Future<int> forward(int devicePort, { int hostPort }) async {
     hostPort ??= 0;
-    final RunResult process = await runCheckedAsync(device.adbCommandForDevice(
-      <String>['forward', 'tcp:$hostPort', 'tcp:$devicePort']
-    ));
+    final List<String> forwardCommand = <String>[
+      'forward',
+      'tcp:$hostPort',
+      'tcp:$devicePort',
+    ];
+    final RunResult process = await processUtils.run(
+      device.adbCommandForDevice(forwardCommand),
+      throwOnError: true,
+    );
 
-    if (process.stderr.isNotEmpty)
+    if (process.stderr.isNotEmpty) {
       process.throwException('adb returned error:\n${process.stderr}');
+    }
 
     if (process.exitCode != 0) {
-      if (process.stdout.isNotEmpty)
+      if (process.stdout.isNotEmpty) {
         process.throwException('adb returned error:\n${process.stdout}');
+      }
       process.throwException('adb failed without a message');
     }
 
     if (hostPort == 0) {
-      if (process.stdout.isEmpty)
+      if (process.stdout.isEmpty) {
         process.throwException('adb did not report forwarded port');
-      hostPort = int.tryParse(process.stdout) ?? (throw 'adb returned invalid port number:\n${process.stdout}');
+      }
+      hostPort = int.tryParse(process.stdout);
+      if (hostPort == null) {
+        process.throwException('adb returned invalid port number:\n${process.stdout}');
+      }
     } else {
       // stdout may be empty or the port we asked it to forward, though it's
       // not documented (or obvious) what triggers each case.
@@ -1025,8 +1069,9 @@
       // To cover all cases, we accept the output being either empty or exactly
       // the port number, but treat any other output as probably being an error
       // message.
-      if (process.stdout.isNotEmpty && process.stdout.trim() != '$hostPort')
+      if (process.stdout.isNotEmpty && process.stdout.trim() != '$hostPort') {
         process.throwException('adb returned error:\n${process.stdout}');
+      }
     }
 
     return hostPort;
@@ -1034,8 +1079,14 @@
 
   @override
   Future<void> unforward(ForwardedPort forwardedPort) async {
-    await runCheckedAsync(device.adbCommandForDevice(
-      <String>['forward', '--remove', 'tcp:${forwardedPort.hostPort}']
-    ));
+    final List<String> unforwardCommand = <String>[
+      'forward',
+      '--remove',
+      'tcp:${forwardedPort.hostPort}',
+    ];
+    await processUtils.run(
+      device.adbCommandForDevice(unforwardCommand),
+      throwOnError: true,
+    );
   }
 }
diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart
index 65a2118..4b8f7e3 100644
--- a/packages/flutter_tools/lib/src/android/android_emulator.dart
+++ b/packages/flutter_tools/lib/src/android/android_emulator.dart
@@ -9,8 +9,7 @@
 import '../android/android_sdk.dart';
 import '../android/android_workflow.dart';
 import '../base/file_system.dart';
-import '../base/io.dart';
-import '../base/process_manager.dart';
+import '../base/process.dart';
 import '../device.dart';
 import '../emulator.dart';
 import 'android_sdk.dart';
@@ -50,13 +49,10 @@
 
   @override
   Future<void> launch() async {
-    final Future<void> launchResult =
-        processManager.run(<String>[getEmulatorPath(), '-avd', id])
-            .then((ProcessResult runResult) {
-              if (runResult.exitCode != 0) {
-                throw '${runResult.stdout}\n${runResult.stderr}'.trimRight();
-              }
-            });
+    final Future<void> launchResult = processUtils.run(
+      <String>[getEmulatorPath(), '-avd', id],
+      throwOnError: true,
+    );
     // The emulator continues running on a successful launch, so if it hasn't
     // quit within 3 seconds we assume that's a success and just return. This
     // means that on a slow machine, a failure that takes more than three
@@ -75,7 +71,8 @@
     return <AndroidEmulator>[];
   }
 
-  final String listAvdsOutput = processManager.runSync(<String>[emulatorPath, '-list-avds']).stdout;
+  final String listAvdsOutput = processUtils.runSync(
+    <String>[emulatorPath, '-list-avds']).stdout.trim();
 
   final List<AndroidEmulator> emulators = <AndroidEmulator>[];
   if (listAvdsOutput != null) {
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index 65f40aa..fe13948 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -7,7 +7,6 @@
 import '../base/common.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
-import '../base/io.dart' show ProcessResult;
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
@@ -526,9 +525,9 @@
 
   /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
   static String findJavaBinary() {
-
-    if (android_studio.javaPath != null)
+    if (android_studio.javaPath != null) {
       return fs.path.join(android_studio.javaPath, 'bin', 'java');
+    }
 
     final String javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable];
     if (javaHomeEnv != null) {
@@ -540,7 +539,11 @@
     // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
     if (platform.isMacOS) {
       try {
-        final String javaHomeOutput = runCheckedSync(<String>['/usr/libexec/java_home'], hideStdout: true);
+        final String javaHomeOutput = processUtils.runSync(
+          <String>['/usr/libexec/java_home'],
+          throwOnError: true,
+          hideStdout: true,
+        ).stdout.trim();
         if (javaHomeOutput != null) {
           final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
           if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
@@ -575,7 +578,10 @@
   String get sdkManagerVersion {
     if (!processManager.canRun(sdkManagerPath))
       throwToolExit('Android sdkmanager not found. Update to the latest Android SDK to resolve this.');
-    final ProcessResult result = processManager.runSync(<String>[sdkManagerPath, '--version'], environment: sdkManagerEnv);
+    final RunResult result = processUtils.runSync(
+      <String>[sdkManagerPath, '--version'],
+      environment: sdkManagerEnv,
+    );
     if (result.exitCode != 0) {
       printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
       return null;
diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart
index 2fae44a..82e6b4b 100644
--- a/packages/flutter_tools/lib/src/android/android_studio.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio.dart
@@ -5,8 +5,8 @@
 import '../base/common.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
-import '../base/io.dart';
 import '../base/platform.dart';
+import '../base/process.dart';
 import '../base/process_manager.dart';
 import '../base/version.dart';
 import '../globals.dart';
@@ -290,7 +290,7 @@
     if (!processManager.canRun(javaExecutable)) {
       _validationMessages.add('Unable to find bundled Java version.');
     } else {
-      final ProcessResult result = processManager.runSync(<String>[javaExecutable, '-version']);
+      final RunResult result = processUtils.runSync(<String>[javaExecutable, '-version']);
       if (result.exitCode == 0) {
         final List<String> versionLines = result.stderr.split('\n');
         final String javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 8f0a852..7b6b3e6 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -259,7 +259,7 @@
     }
 
     try {
-      final Process process = await runCommand(
+      final Process process = await processUtils.start(
         <String>[androidSdk.sdkManagerPath, '--licenses'],
         environment: androidSdk.sdkManagerEnv,
       );
@@ -302,7 +302,7 @@
     }
 
     try {
-      final Process process = await runCommand(
+      final Process process = await processUtils.start(
         <String>[androidSdk.sdkManagerPath, '--licenses'],
         environment: androidSdk.sdkManagerEnv,
       );
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index d69b99f..2d6f93b 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -145,8 +145,9 @@
   final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
   final FlutterProject flutterProject = FlutterProject.current();
   final String gradlew = await gradleUtils.getExecutable(flutterProject);
-  await runCheckedAsync(
+  await processUtils.run(
     <String>[gradlew, 'dependencies'],
+    throwOnError: true,
     workingDirectory: flutterProject.android.hostAppGradleRoot.path,
     environment: _gradleEnv,
   );
@@ -234,13 +235,15 @@
   // Get the properties and tasks from Gradle, so we can determinate the `buildDir`,
   // flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
   try {
-    final RunResult propertiesRunResult = await runCheckedAsync(
+    final RunResult propertiesRunResult = await processUtils.run(
       <String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
+      throwOnError: true,
       workingDirectory: hostAppGradleRoot.path,
       environment: _gradleEnv,
     );
-    final RunResult tasksRunResult = await runCheckedAsync(
+    final RunResult tasksRunResult = await processUtils.run(
       <String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
+      throwOnError: true,
       workingDirectory: hostAppGradleRoot.path,
       environment: _gradleEnv,
     );
@@ -306,22 +309,29 @@
   injectGradleWrapperIfNeeded(android);
 
   final String gradle = _locateGradlewExecutable(android);
-  if (gradle == null)
+  if (gradle == null) {
+    status.stop();
     throwToolExit('Unable to locate gradlew script');
+  }
   printTrace('Using gradle from $gradle.');
   // Validates the Gradle executable by asking for its version.
   // Makes Gradle Wrapper download and install Gradle distribution, if needed.
   try {
-    await runCheckedAsync(<String>[gradle, '-v'], environment: _gradleEnv);
-  } catch (e) {
-    if (e is ProcessException &&
-        e.toString().contains('java.io.FileNotFoundException: https://downloads.gradle.org') ||
-        e.toString().contains('java.io.IOException: Unable to tunnel through proxy')) {
+    await processUtils.run(
+      <String>[gradle, '-v'],
+      throwOnError: true,
+      environment: _gradleEnv,
+    );
+  } on ProcessException catch (e) {
+    final String error = e.toString();
+    if (error.contains('java.io.FileNotFoundException: https://downloads.gradle.org') ||
+        error.contains('java.io.IOException: Unable to tunnel through proxy')) {
       throwToolExit('$gradle threw an error while trying to update itself.\n$e');
     }
     rethrow;
+  } finally {
+    status.stop();
   }
-  status.stop();
   return gradle;
 }
 
@@ -595,7 +605,7 @@
   int exitCode = 1;
 
   try {
-    exitCode = await runCommandAndStreamOutput(
+    exitCode = await processUtils.stream(
       command,
       workingDirectory: project.android.hostAppGradleRoot.path,
       allowReentrantFlutter: true,
@@ -633,7 +643,7 @@
     multilineOutput: true,
   );
   final Stopwatch sw = Stopwatch()..start();
-  final int exitCode = await runCommandAndStreamOutput(
+  final int exitCode = await processUtils.stream(
     <String>[fs.file(gradlew).absolute.path, 'build'],
     workingDirectory: project.android.hostAppGradleRoot.path,
     allowReentrantFlutter: true,
@@ -756,7 +766,7 @@
   final Stopwatch sw = Stopwatch()..start();
   int exitCode = 1;
   try {
-    exitCode = await runCommandAndStreamOutput(
+    exitCode = await processUtils.stream(
       command,
       workingDirectory: flutterProject.android.hostAppGradleRoot.path,
       allowReentrantFlutter: true,
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 9da49e9..59c577f 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -13,6 +13,7 @@
 import 'base/common.dart';
 import 'base/context.dart';
 import 'base/file_system.dart';
+import 'base/io.dart';
 import 'base/os.dart' show os;
 import 'base/process.dart';
 import 'base/user_messages.dart';
@@ -115,14 +116,17 @@
 
     String apptStdout;
     try {
-      apptStdout = runCheckedSync(<String>[
-        aaptPath,
-        'dump',
-        'xmltree',
-        apk.path,
-        'AndroidManifest.xml',
-      ]);
-    } catch (error) {
+      apptStdout = processUtils.runSync(
+        <String>[
+          aaptPath,
+          'dump',
+          'xmltree',
+          apk.path,
+          'AndroidManifest.xml',
+        ],
+        throwOnError: true,
+      ).stdout.trim();
+    } on ProcessException catch (error) {
       printError('Failed to extract manifest from APK: $error.');
       return null;
     }
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 256f3a4..3e8062d 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -69,7 +69,10 @@
       outputFilter = (String line) => line != kStripWarning ? line : null;
     }
 
-    return runCommandAndStreamOutput(<String>[snapshotterPath, ...args], mapFunction: outputFilter);
+    return processUtils.stream(
+      <String>[snapshotterPath, ...args],
+      mapFunction: outputFilter,
+    );
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index 7286e84..8e1d714 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -154,30 +154,45 @@
 
   @override
   void zip(Directory data, File zipFile) {
-    runSync(<String>['zip', '-r', '-q', zipFile.path, '.'], workingDirectory: data.path);
+    processUtils.runSync(
+      <String>['zip', '-r', '-q', zipFile.path, '.'],
+      workingDirectory: data.path,
+      throwOnError: true,
+    );
   }
 
   // unzip -o -q zipfile -d dest
   @override
   void unzip(File file, Directory targetDirectory) {
-    runSync(<String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]);
+    processUtils.runSync(
+      <String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path],
+      throwOnError: true,
+    );
   }
 
   @override
-  bool verifyZip(File zipFile) => exitsHappy(<String>['zip', '-T', zipFile.path]);
+  bool verifyZip(File zipFile) =>
+      processUtils.exitsHappySync(<String>['zip', '-T', zipFile.path]);
 
   // tar -xzf tarball -C dest
   @override
   void unpack(File gzippedTarFile, Directory targetDirectory) {
-    runSync(<String>['tar', '-xzf', gzippedTarFile.path, '-C', targetDirectory.path]);
+    processUtils.runSync(
+      <String>['tar', '-xzf', gzippedTarFile.path, '-C', targetDirectory.path],
+      throwOnError: true,
+    );
   }
 
   @override
-  bool verifyGzip(File gzippedFile) => exitsHappy(<String>['gzip', '-t', gzippedFile.path]);
+  bool verifyGzip(File gzippedFile) =>
+      processUtils.exitsHappySync(<String>['gzip', '-t', gzippedFile.path]);
 
   @override
   File makePipe(String path) {
-    runSync(<String>['mkfifo', path]);
+    processUtils.runSync(
+      <String>['mkfifo', path],
+      throwOnError: true,
+    );
     return fs.file(path);
   }
 
@@ -187,12 +202,12 @@
   String get name {
     if (_name == null) {
       if (platform.isMacOS) {
-        final List<ProcessResult> results = <ProcessResult>[
-          processManager.runSync(<String>['sw_vers', '-productName']),
-          processManager.runSync(<String>['sw_vers', '-productVersion']),
-          processManager.runSync(<String>['sw_vers', '-buildVersion']),
+        final List<RunResult> results = <RunResult>[
+          processUtils.runSync(<String>['sw_vers', '-productName']),
+          processUtils.runSync(<String>['sw_vers', '-productVersion']),
+          processUtils.runSync(<String>['sw_vers', '-buildVersion']),
         ];
-        if (results.every((ProcessResult result) => result.exitCode == 0)) {
+        if (results.every((RunResult result) => result.exitCode == 0)) {
           _name = '${results[0].stdout.trim()} ${results[1].stdout
               .trim()} ${results[2].stdout.trim()}';
         }
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index b5be56b..99229fd 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -6,7 +6,7 @@
 
 import '../convert.dart';
 import '../globals.dart';
-import 'common.dart';
+import 'context.dart';
 import 'file_system.dart';
 import 'io.dart';
 import 'process_manager.dart';
@@ -98,379 +98,6 @@
   printTrace('Shutdown hooks complete');
 }
 
-Map<String, String> _environment(bool allowReentrantFlutter, [ Map<String, String> environment ]) {
-  if (allowReentrantFlutter) {
-    if (environment == null)
-      environment = <String, String>{'FLUTTER_ALREADY_LOCKED': 'true'};
-    else
-      environment['FLUTTER_ALREADY_LOCKED'] = 'true';
-  }
-
-  return environment;
-}
-
-/// This runs the command in the background from the specified working
-/// directory. Completes when the process has been started.
-Future<Process> runCommand(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  Map<String, String> environment,
-}) {
-  _traceCommand(cmd, workingDirectory: workingDirectory);
-  return processManager.start(
-    cmd,
-    workingDirectory: workingDirectory,
-    environment: _environment(allowReentrantFlutter, environment),
-  );
-}
-
-/// This runs the command and streams stdout/stderr from the child process to
-/// this process' stdout/stderr. Completes with the process's exit code.
-///
-/// If [filter] is null, no lines are removed.
-///
-/// If [filter] is non-null, all lines that do not match it are removed. If
-/// [mapFunction] is present, all lines that match [filter] are also forwarded
-/// to [mapFunction] for further processing.
-Future<int> runCommandAndStreamOutput(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  String prefix = '',
-  bool trace = false,
-  RegExp filter,
-  StringConverter mapFunction,
-  Map<String, String> environment,
-}) async {
-  final Process process = await runCommand(
-    cmd,
-    workingDirectory: workingDirectory,
-    allowReentrantFlutter: allowReentrantFlutter,
-    environment: environment,
-  );
-  final StreamSubscription<String> stdoutSubscription = process.stdout
-    .transform<String>(utf8.decoder)
-    .transform<String>(const LineSplitter())
-    .where((String line) => filter == null || filter.hasMatch(line))
-    .listen((String line) {
-      if (mapFunction != null)
-        line = mapFunction(line);
-      if (line != null) {
-        final String message = '$prefix$line';
-        if (trace)
-          printTrace(message);
-        else
-          printStatus(message, wrap: false);
-      }
-    });
-  final StreamSubscription<String> stderrSubscription = process.stderr
-    .transform<String>(utf8.decoder)
-    .transform<String>(const LineSplitter())
-    .where((String line) => filter == null || filter.hasMatch(line))
-    .listen((String line) {
-      if (mapFunction != null)
-        line = mapFunction(line);
-      if (line != null)
-        printError('$prefix$line', wrap: false);
-    });
-
-  // Wait for stdout to be fully processed
-  // because process.exitCode may complete first causing flaky tests.
-  await waitGroup<void>(<Future<void>>[
-    stdoutSubscription.asFuture<void>(),
-    stderrSubscription.asFuture<void>(),
-  ]);
-
-  await waitGroup<void>(<Future<void>>[
-    stdoutSubscription.cancel(),
-    stderrSubscription.cancel(),
-  ]);
-
-  return await process.exitCode;
-}
-
-/// Runs the [command] interactively, connecting the stdin/stdout/stderr
-/// streams of this process to those of the child process. Completes with
-/// the exit code of the child process.
-Future<int> runInteractively(
-  List<String> command, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  Map<String, String> environment,
-}) async {
-  final Process process = await runCommand(
-    command,
-    workingDirectory: workingDirectory,
-    allowReentrantFlutter: allowReentrantFlutter,
-    environment: environment,
-  );
-  // The real stdin will never finish streaming. Pipe until the child process
-  // finishes.
-  unawaited(process.stdin.addStream(stdin));
-  // Wait for stdout and stderr to be fully processed, because process.exitCode
-  // may complete first.
-  await Future.wait<dynamic>(<Future<dynamic>>[
-    stdout.addStream(process.stdout),
-    stderr.addStream(process.stderr),
-  ]);
-  return await process.exitCode;
-}
-
-Future<Process> runDetached(List<String> cmd) {
-  _traceCommand(cmd);
-  final Future<Process> proc = processManager.start(
-    cmd,
-    mode: ProcessStartMode.detached,
-  );
-  return proc;
-}
-
-Future<RunResult> runAsync(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  Map<String, String> environment,
-  Duration timeout,
-  int timeoutRetries = 0,
-}) async {
-  _traceCommand(cmd, workingDirectory: workingDirectory);
-
-  // When there is no timeout, there's no need to kill a running process, so
-  // we can just use processManager.run().
-  if (timeout == null) {
-    final ProcessResult results = await processManager.run(
-      cmd,
-      workingDirectory: workingDirectory,
-      environment: _environment(allowReentrantFlutter, environment),
-    );
-    final RunResult runResults = RunResult(results, cmd);
-    printTrace(runResults.toString());
-    return runResults;
-  }
-
-  // When there is a timeout, we have to kill the running process, so we have
-  // to use processManager.start() through runCommand() above.
-  while (true) {
-    assert(timeoutRetries >= 0);
-    timeoutRetries = timeoutRetries - 1;
-
-    final Process process = await runCommand(
-        cmd,
-        workingDirectory: workingDirectory,
-        allowReentrantFlutter: allowReentrantFlutter,
-        environment: environment,
-    );
-
-    final StringBuffer stdoutBuffer = StringBuffer();
-    final StringBuffer stderrBuffer = StringBuffer();
-    final Future<void> stdoutFuture = process.stdout
-        .transform<String>(const Utf8Decoder(reportErrors: false))
-        .listen(stdoutBuffer.write)
-        .asFuture<void>(null);
-    final Future<void> stderrFuture = process.stderr
-        .transform<String>(const Utf8Decoder(reportErrors: false))
-        .listen(stderrBuffer.write)
-        .asFuture<void>(null);
-
-    int exitCode;
-    exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
-      // The process timed out. Kill it.
-      processManager.killPid(process.pid);
-      return null;
-    });
-
-    String stdoutString;
-    String stderrString;
-    try {
-      Future<void> stdioFuture =
-          Future.wait<void>(<Future<void>>[stdoutFuture, stderrFuture]);
-      if (exitCode == null) {
-        // If we had to kill the process for a timeout, only wait a short time
-        // for the stdio streams to drain in case killing the process didn't
-        // work.
-        stdioFuture = stdioFuture.timeout(const Duration(seconds: 1));
-      }
-      await stdioFuture;
-    } catch (_) {
-      // Ignore errors on the process' stdout and stderr streams. Just capture
-      // whatever we got, and use the exit code
-    }
-    stdoutString = stdoutBuffer.toString();
-    stderrString = stderrBuffer.toString();
-
-    final ProcessResult result = ProcessResult(
-        process.pid, exitCode ?? -1, stdoutString, stderrString);
-    final RunResult runResult = RunResult(result, cmd);
-
-    // If the process did not timeout. We are done.
-    if (exitCode != null) {
-      printTrace(runResult.toString());
-      return runResult;
-    }
-
-    // If we are out of timeoutRetries, throw a ProcessException.
-    if (timeoutRetries < 0) {
-      throw ProcessException(cmd[0], cmd.sublist(1),
-          'Process "${cmd[0]}" timed out: $runResult', exitCode);
-    }
-
-    // Log the timeout with a trace message in verbose mode.
-    printTrace('Process "${cmd[0]}" timed out. $timeoutRetries attempts left: $runResult');
-  }
-
-  // Unreachable.
-}
-
-typedef RunResultChecker = bool Function(int);
-
-Future<RunResult> runCheckedAsync(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  Map<String, String> environment,
-  RunResultChecker whiteListFailures,
-  Duration timeout,
-  int timeoutRetries = 0,
-}) async {
-  final RunResult result = await runAsync(
-    cmd,
-    workingDirectory: workingDirectory,
-    allowReentrantFlutter: allowReentrantFlutter,
-    environment: environment,
-    timeout: timeout,
-    timeoutRetries: timeoutRetries,
-  );
-  if (result.exitCode != 0) {
-    if (whiteListFailures == null || !whiteListFailures(result.exitCode)) {
-      throw ProcessException(cmd[0], cmd.sublist(1),
-          'Process "${cmd[0]}" exited abnormally:\n$result', result.exitCode);
-    }
-  }
-  return result;
-}
-
-bool exitsHappy(
-  List<String> cli, {
-  Map<String, String> environment,
-}) {
-  _traceCommand(cli);
-  try {
-    return processManager.runSync(cli, environment: environment).exitCode == 0;
-  } catch (error) {
-    printTrace('$cli failed with $error');
-    return false;
-  }
-}
-
-Future<bool> exitsHappyAsync(
-  List<String> cli, {
-  Map<String, String> environment,
-}) async {
-  _traceCommand(cli);
-  try {
-    return (await processManager.run(cli, environment: environment)).exitCode == 0;
-  } catch (error) {
-    printTrace('$cli failed with $error');
-    return false;
-  }
-}
-
-/// Run cmd and return stdout.
-///
-/// Throws an error if cmd exits with a non-zero value.
-String runCheckedSync(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  bool hideStdout = false,
-  Map<String, String> environment,
-  RunResultChecker whiteListFailures,
-}) {
-  return _runWithLoggingSync(
-    cmd,
-    workingDirectory: workingDirectory,
-    allowReentrantFlutter: allowReentrantFlutter,
-    hideStdout: hideStdout,
-    checked: true,
-    noisyErrors: true,
-    environment: environment,
-    whiteListFailures: whiteListFailures
-  );
-}
-
-/// Run cmd and return stdout.
-String runSync(
-  List<String> cmd, {
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-}) {
-  return _runWithLoggingSync(
-    cmd,
-    workingDirectory: workingDirectory,
-    allowReentrantFlutter: allowReentrantFlutter,
-  );
-}
-
-void _traceCommand(List<String> args, { String workingDirectory }) {
-  final String argsText = args.join(' ');
-  if (workingDirectory == null) {
-    printTrace('executing: $argsText');
-  } else {
-    printTrace('executing: [$workingDirectory${fs.path.separator}] $argsText');
-  }
-}
-
-String _runWithLoggingSync(
-  List<String> cmd, {
-  bool checked = false,
-  bool noisyErrors = false,
-  bool throwStandardErrorOnError = false,
-  String workingDirectory,
-  bool allowReentrantFlutter = false,
-  bool hideStdout = false,
-  Map<String, String> environment,
-  RunResultChecker whiteListFailures,
-}) {
-  _traceCommand(cmd, workingDirectory: workingDirectory);
-  final ProcessResult results = processManager.runSync(
-    cmd,
-    workingDirectory: workingDirectory,
-    environment: _environment(allowReentrantFlutter, environment),
-  );
-
-  printTrace('Exit code ${results.exitCode} from: ${cmd.join(' ')}');
-
-  bool failedExitCode = results.exitCode != 0;
-  if (whiteListFailures != null && failedExitCode) {
-    failedExitCode = !whiteListFailures(results.exitCode);
-  }
-
-  if (results.stdout.isNotEmpty && !hideStdout) {
-    if (failedExitCode && noisyErrors)
-      printStatus(results.stdout.trim());
-    else
-      printTrace(results.stdout.trim());
-  }
-
-  if (failedExitCode) {
-    if (results.stderr.isNotEmpty) {
-      if (noisyErrors)
-        printError(results.stderr.trim());
-      else
-        printTrace(results.stderr.trim());
-    }
-
-    if (throwStandardErrorOnError)
-      throw results.stderr.trim();
-
-    if (checked)
-      throw 'Exit code ${results.exitCode} from: ${cmd.join(' ')}';
-  }
-
-  return results.stdout.trim();
-}
-
 class ProcessExit implements Exception {
   ProcessExit(this.exitCode, {this.immediate = false});
 
@@ -516,3 +143,382 @@
     );
   }
 }
+
+typedef RunResultChecker = bool Function(int);
+
+ProcessUtils get processUtils => ProcessUtils.instance;
+
+abstract class ProcessUtils {
+  factory ProcessUtils() => _DefaultProcessUtils();
+
+  static ProcessUtils get instance => context.get<ProcessUtils>();
+
+  /// Spawns a child process to run the command [cmd].
+  ///
+  /// When [throwOnError] is `true`, if the child process finishes with a non-zero
+  /// exit code, a [ProcessException] is thrown.
+  ///
+  /// If [throwOnError] is `true`, and [whiteListFailures] is supplied,
+  /// a [ProcessException] is only thrown on a non-zero exit code if
+  /// [whiteListFailures] returns false when passed the exit code.
+  ///
+  /// When [workingDirectory] is set, it is the working directory of the child
+  /// process.
+  ///
+  /// When [allowReentrantFlutter] is set to `true`, the child process is
+  /// permitted to call the Flutter tool. By default it is not.
+  ///
+  /// When [environment] is supplied, it is used as the environment for the child
+  /// process.
+  ///
+  /// When [timeout] is supplied, [runAsync] will kill the child process and
+  /// throw a [ProcessException] when it doesn't finish in time.
+  ///
+  /// If [timeout] is supplied, the command will be retried [timeoutRetries] times
+  /// if it times out.
+  Future<RunResult> run(
+    List<String> cmd, {
+    bool throwOnError = false,
+    RunResultChecker whiteListFailures,
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    Map<String, String> environment,
+    Duration timeout,
+    int timeoutRetries = 0,
+  });
+
+  /// Run the command and block waiting for its result.
+  RunResult runSync(
+    List<String> cmd, {
+    bool throwOnError = false,
+    RunResultChecker whiteListFailures,
+    bool hideStdout = false,
+    String workingDirectory,
+    Map<String, String> environment,
+    bool allowReentrantFlutter = false,
+  });
+
+  /// This runs the command in the background from the specified working
+  /// directory. Completes when the process has been started.
+  Future<Process> start(
+    List<String> cmd, {
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    Map<String, String> environment,
+  });
+
+  /// This runs the command and streams stdout/stderr from the child process to
+  /// this process' stdout/stderr. Completes with the process's exit code.
+  ///
+  /// If [filter] is null, no lines are removed.
+  ///
+  /// If [filter] is non-null, all lines that do not match it are removed. If
+  /// [mapFunction] is present, all lines that match [filter] are also forwarded
+  /// to [mapFunction] for further processing.
+  Future<int> stream(
+    List<String> cmd, {
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    String prefix = '',
+    bool trace = false,
+    RegExp filter,
+    StringConverter mapFunction,
+    Map<String, String> environment,
+  });
+
+  bool exitsHappySync(
+    List<String> cli, {
+    Map<String, String> environment,
+  });
+
+  Future<bool> exitsHappy(
+    List<String> cli, {
+    Map<String, String> environment,
+  });
+}
+
+class _DefaultProcessUtils implements ProcessUtils {
+  @override
+  Future<RunResult> run(
+    List<String> cmd, {
+    bool throwOnError = false,
+    RunResultChecker whiteListFailures,
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    Map<String, String> environment,
+    Duration timeout,
+    int timeoutRetries = 0,
+  }) async {
+    if (cmd == null || cmd.isEmpty) {
+      throw ArgumentError('cmd must be a non-empty list');
+    }
+    if (timeoutRetries < 0) {
+      throw ArgumentError('timeoutRetries must be non-negative');
+    }
+    _traceCommand(cmd, workingDirectory: workingDirectory);
+
+    // When there is no timeout, there's no need to kill a running process, so
+    // we can just use processManager.run().
+    if (timeout == null) {
+      final ProcessResult results = await processManager.run(
+        cmd,
+        workingDirectory: workingDirectory,
+        environment: _environment(allowReentrantFlutter, environment),
+      );
+      final RunResult runResult = RunResult(results, cmd);
+      printTrace(runResult.toString());
+      if (throwOnError && runResult.exitCode != 0 &&
+          (whiteListFailures == null || !whiteListFailures(runResult.exitCode))) {
+        runResult.throwException('Process exited abnormally:\n$runResult');
+      }
+      return runResult;
+    }
+
+    // When there is a timeout, we have to kill the running process, so we have
+    // to use processManager.start() through _runCommand() above.
+    while (true) {
+      assert(timeoutRetries >= 0);
+      timeoutRetries = timeoutRetries - 1;
+
+      final Process process = await start(
+          cmd,
+          workingDirectory: workingDirectory,
+          allowReentrantFlutter: allowReentrantFlutter,
+          environment: environment,
+      );
+
+      final StringBuffer stdoutBuffer = StringBuffer();
+      final StringBuffer stderrBuffer = StringBuffer();
+      final Future<void> stdoutFuture = process.stdout
+          .transform<String>(const Utf8Decoder(reportErrors: false))
+          .listen(stdoutBuffer.write)
+          .asFuture<void>(null);
+      final Future<void> stderrFuture = process.stderr
+          .transform<String>(const Utf8Decoder(reportErrors: false))
+          .listen(stderrBuffer.write)
+          .asFuture<void>(null);
+
+      int exitCode;
+      exitCode = await process.exitCode.timeout(timeout, onTimeout: () {
+        // The process timed out. Kill it.
+        processManager.killPid(process.pid);
+        return null;
+      });
+
+      String stdoutString;
+      String stderrString;
+      try {
+        Future<void> stdioFuture =
+            Future.wait<void>(<Future<void>>[stdoutFuture, stderrFuture]);
+        if (exitCode == null) {
+          // If we had to kill the process for a timeout, only wait a short time
+          // for the stdio streams to drain in case killing the process didn't
+          // work.
+          stdioFuture = stdioFuture.timeout(const Duration(seconds: 1));
+        }
+        await stdioFuture;
+      } catch (_) {
+        // Ignore errors on the process' stdout and stderr streams. Just capture
+        // whatever we got, and use the exit code
+      }
+      stdoutString = stdoutBuffer.toString();
+      stderrString = stderrBuffer.toString();
+
+      final ProcessResult result = ProcessResult(
+          process.pid, exitCode ?? -1, stdoutString, stderrString);
+      final RunResult runResult = RunResult(result, cmd);
+
+      // If the process did not timeout. We are done.
+      if (exitCode != null) {
+        printTrace(runResult.toString());
+        if (throwOnError && runResult.exitCode != 0 &&
+            (whiteListFailures == null || !whiteListFailures(exitCode))) {
+          runResult.throwException('Process exited abnormally:\n$runResult');
+        }
+        return runResult;
+      }
+
+      // If we are out of timeoutRetries, throw a ProcessException.
+      if (timeoutRetries < 0) {
+        runResult.throwException('Process timed out:\n$runResult');
+      }
+
+      // Log the timeout with a trace message in verbose mode.
+      printTrace('Process "${cmd[0]}" timed out. $timeoutRetries attempts left:\n'
+                 '$runResult');
+    }
+
+    // Unreachable.
+  }
+
+  @override
+  RunResult runSync(
+    List<String> cmd, {
+    bool throwOnError = false,
+    RunResultChecker whiteListFailures,
+    bool hideStdout = false,
+    String workingDirectory,
+    Map<String, String> environment,
+    bool allowReentrantFlutter = false,
+  }) {
+    _traceCommand(cmd, workingDirectory: workingDirectory);
+    final ProcessResult results = processManager.runSync(
+      cmd,
+      workingDirectory: workingDirectory,
+      environment: _environment(allowReentrantFlutter, environment),
+    );
+    final RunResult runResult = RunResult(results, cmd);
+
+    printTrace('Exit code ${runResult.exitCode} from: ${cmd.join(' ')}');
+
+    bool failedExitCode = runResult.exitCode != 0;
+    if (whiteListFailures != null && failedExitCode) {
+      failedExitCode = !whiteListFailures(runResult.exitCode);
+    }
+
+    if (runResult.stdout.isNotEmpty && !hideStdout) {
+      if (failedExitCode && throwOnError) {
+        printStatus(runResult.stdout.trim());
+      } else {
+        printTrace(runResult.stdout.trim());
+      }
+    }
+
+    if (runResult.stderr.isNotEmpty) {
+      if (failedExitCode && throwOnError) {
+        printError(runResult.stderr.trim());
+      } else {
+        printTrace(runResult.stderr.trim());
+      }
+    }
+
+    if (failedExitCode && throwOnError) {
+      runResult.throwException('The command failed');
+    }
+
+    return runResult;
+  }
+
+  @override
+  Future<Process> start(
+    List<String> cmd, {
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    Map<String, String> environment,
+  }) {
+    _traceCommand(cmd, workingDirectory: workingDirectory);
+    return processManager.start(
+      cmd,
+      workingDirectory: workingDirectory,
+      environment: _environment(allowReentrantFlutter, environment),
+    );
+  }
+
+  @override
+  Future<int> stream(
+    List<String> cmd, {
+    String workingDirectory,
+    bool allowReentrantFlutter = false,
+    String prefix = '',
+    bool trace = false,
+    RegExp filter,
+    StringConverter mapFunction,
+    Map<String, String> environment,
+  }) async {
+    final Process process = await start(
+      cmd,
+      workingDirectory: workingDirectory,
+      allowReentrantFlutter: allowReentrantFlutter,
+      environment: environment,
+    );
+    final StreamSubscription<String> stdoutSubscription = process.stdout
+      .transform<String>(utf8.decoder)
+      .transform<String>(const LineSplitter())
+      .where((String line) => filter == null || filter.hasMatch(line))
+      .listen((String line) {
+        if (mapFunction != null)
+          line = mapFunction(line);
+        if (line != null) {
+          final String message = '$prefix$line';
+          if (trace)
+            printTrace(message);
+          else
+            printStatus(message, wrap: false);
+        }
+      });
+    final StreamSubscription<String> stderrSubscription = process.stderr
+      .transform<String>(utf8.decoder)
+      .transform<String>(const LineSplitter())
+      .where((String line) => filter == null || filter.hasMatch(line))
+      .listen((String line) {
+        if (mapFunction != null)
+          line = mapFunction(line);
+        if (line != null)
+          printError('$prefix$line', wrap: false);
+      });
+
+    // Wait for stdout to be fully processed
+    // because process.exitCode may complete first causing flaky tests.
+    await waitGroup<void>(<Future<void>>[
+      stdoutSubscription.asFuture<void>(),
+      stderrSubscription.asFuture<void>(),
+    ]);
+
+    await waitGroup<void>(<Future<void>>[
+      stdoutSubscription.cancel(),
+      stderrSubscription.cancel(),
+    ]);
+
+    return await process.exitCode;
+  }
+
+  @override
+  bool exitsHappySync(
+    List<String> cli, {
+    Map<String, String> environment,
+  }) {
+    _traceCommand(cli);
+    try {
+      return processManager.runSync(cli, environment: environment).exitCode == 0;
+    } catch (error) {
+      printTrace('$cli failed with $error');
+      return false;
+    }
+  }
+
+  @override
+  Future<bool> exitsHappy(
+    List<String> cli, {
+    Map<String, String> environment,
+  }) async {
+    _traceCommand(cli);
+    try {
+      return (await processManager.run(cli, environment: environment)).exitCode == 0;
+    } catch (error) {
+      printTrace('$cli failed with $error');
+      return false;
+    }
+  }
+
+  Map<String, String> _environment(bool allowReentrantFlutter, [
+    Map<String, String> environment,
+  ]) {
+    if (allowReentrantFlutter) {
+      if (environment == null)
+        environment = <String, String>{'FLUTTER_ALREADY_LOCKED': 'true'};
+      else
+        environment['FLUTTER_ALREADY_LOCKED'] = 'true';
+    }
+
+    return environment;
+  }
+
+  void _traceCommand(List<String> args, { String workingDirectory }) {
+    final String argsText = args.join(' ');
+    if (workingDirectory == null) {
+      printTrace('executing: $argsText');
+    } else {
+      printTrace('executing: [$workingDirectory${fs.path.separator}] $argsText');
+    }
+  }
+}
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index 763a8b9..f572442 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -9,6 +9,7 @@
 import '../base/common.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
+import '../base/io.dart';
 import '../base/logger.dart';
 import '../base/process.dart';
 import '../base/version.dart';
@@ -142,14 +143,18 @@
 
         // Merge arch-specific App.frameworks into a multi-arch App.framework.
         if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) {
-          final Iterable<String> dylibs = iosBuilds.values.map<String>((String outputDir) => fs.path.join(outputDir, 'App.framework', 'App'));
+          final Iterable<String> dylibs = iosBuilds.values.map<String>(
+              (String outputDir) => fs.path.join(outputDir, 'App.framework', 'App'));
           fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync();
-          await runCheckedAsync(<String>[
-            'lipo',
-            ...dylibs,
-            '-create',
-            '-output', fs.path.join(outputPath, 'App.framework', 'App'),
-          ]);
+          await processUtils.run(
+            <String>[
+              'lipo',
+              ...dylibs,
+              '-create',
+              '-output', fs.path.join(outputPath, 'App.framework', 'App'),
+            ],
+            throwOnError: true,
+          );
         } else {
           status?.cancel();
           exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async {
@@ -173,10 +178,10 @@
           throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
         }
       }
-    } on String catch (error) {
-      // Catch the String exceptions thrown from the `runCheckedSync` methods below.
+    } on ProcessException catch (error) {
+      // Catch the String exceptions thrown from the `runSync` methods below.
       status?.cancel();
-      printError(error);
+      printError(error.toString());
       return null;
     }
     status?.stop();
diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart
index d9672f6..bef80ac 100644
--- a/packages/flutter_tools/lib/src/commands/channel.dart
+++ b/packages/flutter_tools/lib/src/commands/channel.dart
@@ -61,7 +61,7 @@
     showAll = showAll || currentChannel != currentBranch;
 
     printStatus('Flutter channels:');
-    final int result = await runCommandAndStreamOutput(
+    final int result = await processUtils.stream(
       <String>['git', 'branch', '-r'],
       workingDirectory: Cache.flutterRoot,
       mapFunction: (String line) {
@@ -111,28 +111,28 @@
 
   static Future<void> _checkout(String branchName) async {
     // Get latest refs from upstream.
-    int result = await runCommandAndStreamOutput(
+    int result = await processUtils.stream(
       <String>['git', 'fetch'],
       workingDirectory: Cache.flutterRoot,
       prefix: 'git: ',
     );
 
     if (result == 0) {
-      result = await runCommandAndStreamOutput(
+      result = await processUtils.stream(
         <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'],
         workingDirectory: Cache.flutterRoot,
         prefix: 'git: ',
       );
       if (result == 0) {
         // branch already exists, try just switching to it
-        result = await runCommandAndStreamOutput(
+        result = await processUtils.stream(
           <String>['git', 'checkout', branchName, '--'],
           workingDirectory: Cache.flutterRoot,
           prefix: 'git: ',
         );
       } else {
         // branch does not exist, we have to create it
-        result = await runCommandAndStreamOutput(
+        result = await processUtils.stream(
           <String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'],
           workingDirectory: Cache.flutterRoot,
           prefix: 'git: ',
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 61f3128..e63377e 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -292,7 +292,7 @@
 
   PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(PackageMap.globalPackagesPath));
   final String dartVmPath = fs.path.join(dartSdkPath, 'bin', 'dart');
-  final int result = await runCommandAndStreamOutput(
+  final int result = await processUtils.stream(
     <String>[
       dartVmPath,
       ...dartVmFlags,
diff --git a/packages/flutter_tools/lib/src/commands/format.dart b/packages/flutter_tools/lib/src/commands/format.dart
index fd0b331..24a1511 100644
--- a/packages/flutter_tools/lib/src/commands/format.dart
+++ b/packages/flutter_tools/lib/src/commands/format.dart
@@ -76,9 +76,10 @@
       ...argResults.rest,
     ];
 
-    final int result = await runCommandAndStreamOutput(command);
-    if (result != 0)
+    final int result = await processUtils.stream(command);
+    if (result != 0) {
       throwToolExit('Formatting failed: $result', exitCode: result);
+    }
 
     return null;
   }
diff --git a/packages/flutter_tools/lib/src/commands/unpack.dart b/packages/flutter_tools/lib/src/commands/unpack.dart
index 7450bf9..caa4436 100644
--- a/packages/flutter_tools/lib/src/commands/unpack.dart
+++ b/packages/flutter_tools/lib/src/commands/unpack.dart
@@ -5,8 +5,7 @@
 import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
-import '../base/io.dart';
-import '../base/process_manager.dart';
+import '../base/process.dart';
 import '../build_info.dart';
 import '../cache.dart';
 import '../globals.dart';
@@ -284,7 +283,7 @@
     _deleteFrameworkIfPresent(
         fs.path.join(targetDirectory, fs.path.basename(frameworkPath)));
 
-    final ProcessResult result = processManager
+    final RunResult result = processUtils
         .runSync(<String>['cp', '-R', frameworkPath, targetDirectory]);
     if (result.exitCode != 0) {
       throw Exception(
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart
index 542ffba..cd31444 100644
--- a/packages/flutter_tools/lib/src/commands/upgrade.dart
+++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -121,7 +121,7 @@
   }
 
   Future<void> flutterUpgradeContinue() async {
-    final int code = await runCommandAndStreamOutput(
+    final int code = await processUtils.stream(
       <String>[
         fs.path.join('bin', 'flutter'),
         'upgrade',
@@ -146,9 +146,11 @@
 
   Future<bool> hasUncomittedChanges() async {
     try {
-      final RunResult result = await runCheckedAsync(<String>[
-        'git', 'status', '-s'
-      ], workingDirectory: Cache.flutterRoot);
+      final RunResult result = await processUtils.run(
+        <String>['git', 'status', '-s'],
+        throwOnError: true,
+        workingDirectory: Cache.flutterRoot,
+      );
       return result.stdout.trim().isNotEmpty;
     } on ProcessException catch (error) {
       throwToolExit(
@@ -167,9 +169,11 @@
   /// Exits tool if there is no upstream.
   Future<void> verifyUpstreamConfigured() async {
     try {
-      await runCheckedAsync(<String>[
-        'git', 'rev-parse', '@{u}',
-      ], workingDirectory: Cache.flutterRoot);
+      await processUtils.run(
+        <String>[ 'git', 'rev-parse', '@{u}'],
+        throwOnError: true,
+        workingDirectory: Cache.flutterRoot,
+      );
     } catch (e) {
       throwToolExit(
         'Unable to upgrade Flutter: no origin repository configured. '
@@ -191,9 +195,11 @@
       tag = 'v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}';
     }
     try {
-      await runCheckedAsync(<String>[
-        'git', 'reset', '--hard', tag,
-      ], workingDirectory: Cache.flutterRoot);
+      await processUtils.run(
+        <String>['git', 'reset', '--hard', tag],
+        throwOnError: true,
+        workingDirectory: Cache.flutterRoot,
+      );
     } on ProcessException catch (error) {
       throwToolExit(
         'Unable to upgrade Flutter: The tool could not update to the version $tag. '
@@ -218,7 +224,7 @@
   /// If there haven't been any hot fixes or local changes, this is equivalent
   /// to a fast-forward.
   Future<void> attemptFastForward() async {
-    final int code = await runCommandAndStreamOutput(
+    final int code = await processUtils.stream(
       <String>['git', 'pull', '--ff'],
       workingDirectory: Cache.flutterRoot,
       mapFunction: (String line) => matchesGitLine(line) ? null : line,
@@ -236,7 +242,7 @@
   Future<void> precacheArtifacts() async {
     printStatus('');
     printStatus('Upgrading engine...');
-    final int code = await runCommandAndStreamOutput(
+    final int code = await processUtils.stream(
       <String>[
         fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache',
       ],
@@ -263,7 +269,7 @@
   Future<void> runDoctor() async {
     printStatus('');
     printStatus('Running flutter doctor...');
-    await runCommandAndStreamOutput(
+    await processUtils.stream(
       <String>[
         fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor',
       ],
diff --git a/packages/flutter_tools/lib/src/commands/version.dart b/packages/flutter_tools/lib/src/commands/version.dart
index 0fb9e90..2d8fdce 100644
--- a/packages/flutter_tools/lib/src/commands/version.dart
+++ b/packages/flutter_tools/lib/src/commands/version.dart
@@ -37,8 +37,9 @@
   Future<List<String>> getTags() async {
     RunResult runResult;
     try {
-      runResult = await runCheckedAsync(
+      runResult = await processUtils.run(
         <String>['git', 'tag', '-l', 'v*', '--sort=-creatordate'],
+        throwOnError: true,
         workingDirectory: Cache.flutterRoot,
       );
     } on ProcessException catch (error) {
@@ -83,8 +84,9 @@
     }
 
     try {
-      await runCheckedAsync(
+      await processUtils.run(
         <String>['git', 'checkout', 'v$version'],
+        throwOnError: true,
         workingDirectory: Cache.flutterRoot,
       );
     } catch (e) {
@@ -101,7 +103,7 @@
     // if necessary.
     printStatus('');
     printStatus('Downloading engine...');
-    int code = await runCommandAndStreamOutput(<String>[
+    int code = await processUtils.stream(<String>[
       fs.path.join('bin', 'flutter'),
       '--no-color',
       'precache',
@@ -128,7 +130,7 @@
     // Run a doctor check in case system requirements have changed.
     printStatus('');
     printStatus('Running flutter doctor...');
-    code = await runCommandAndStreamOutput(
+    code = await processUtils.stream(
       <String>[
         fs.path.join('bin', 'flutter'),
         'doctor',
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index c761abd..ca885e3 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -19,6 +19,7 @@
 import 'base/logger.dart';
 import 'base/os.dart';
 import 'base/platform.dart';
+import 'base/process.dart';
 import 'base/time.dart';
 import 'base/user_messages.dart';
 import 'base/utils.dart';
@@ -101,6 +102,7 @@
       Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
       MacOSWorkflow: () => const MacOSWorkflow(),
       OperatingSystemUtils: () => OperatingSystemUtils(),
+      ProcessUtils: () => ProcessUtils(),
       SimControl: () => SimControl(),
       Stdio: () => const Stdio(),
       SystemClock: () => const SystemClock(),
diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart
index 696253f..2b25239 100644
--- a/packages/flutter_tools/lib/src/dart/pub.dart
+++ b/packages/flutter_tools/lib/src/dart/pub.dart
@@ -8,6 +8,7 @@
 
 import '../base/common.dart';
 import '../base/file_system.dart';
+import '../base/io.dart' as io;
 import '../base/logger.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
@@ -156,22 +157,25 @@
   int code;
   while (true) {
     attempts += 1;
-    code = await runCommandAndStreamOutput(
+    code = await processUtils.stream(
       _pubCommand(arguments),
       workingDirectory: directory,
       mapFunction: filter,
       environment: _createPubEnvironment(context),
     );
-    if (code != 69) // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
+    if (code != 69) { // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
       break;
+    }
     printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
     await Future<void>.delayed(Duration(seconds: duration));
-    if (duration < 64)
+    if (duration < 64) {
       duration *= 2;
+    }
   }
   assert(code != null);
-  if (code != 0)
+  if (code != 0) {
     throwToolExit('$failureMessage ($code)', exitCode: code);
+  }
 }
 
 /// Runs pub in 'interactive' mode, directly piping the stdin stream of this
@@ -182,13 +186,26 @@
   String directory,
 }) async {
   Cache.releaseLockEarly();
-  final int code = await runInteractively(
+  final io.Process process = await processUtils.start(
     _pubCommand(arguments),
     workingDirectory: directory,
     environment: _createPubEnvironment(PubContext.interactive),
   );
-  if (code != 0)
+
+  // Pipe the Flutter tool stdin to the pub stdin.
+  unawaited(process.stdin.addStream(io.stdin));
+
+  // Pipe the put stdout and stderr to the tool stdout and stderr.
+  await Future.wait<dynamic>(<Future<dynamic>>[
+    io.stdout.addStream(process.stdout),
+    io.stderr.addStream(process.stderr),
+  ]);
+
+  // Wait for pub to exit.
+  final int code = await process.exitCode;
+  if (code != 0) {
     throwToolExit('pub finished with exit code $code', exitCode: code);
+  }
 }
 
 /// The command used for running pub.
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index b3b3c88..8e45bfb 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -14,7 +14,7 @@
 import 'base/logger.dart';
 import 'base/os.dart';
 import 'base/platform.dart';
-import 'base/process_manager.dart';
+import 'base/process.dart';
 import 'base/terminal.dart';
 import 'base/user_messages.dart';
 import 'base/utils.dart';
@@ -607,7 +607,7 @@
 bool _genSnapshotRuns(String genSnapshotPath) {
   const int kExpectedExitCode = 255;
   try {
-    return processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
+    return processUtils.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
   } catch (error) {
     return false;
   }
diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart
index a80c86d..dcbb538 100644
--- a/packages/flutter_tools/lib/src/emulator.dart
+++ b/packages/flutter_tools/lib/src/emulator.dart
@@ -8,8 +8,7 @@
 import 'android/android_emulator.dart';
 import 'android/android_sdk.dart';
 import 'base/context.dart';
-import 'base/io.dart' show ProcessResult;
-import 'base/process_manager.dart';
+import 'base/process.dart';
 import 'device.dart';
 import 'globals.dart';
 import 'ios/ios_emulators.dart';
@@ -118,7 +117,7 @@
       '-k', sdkId,
       '-d', device,
     ];
-    final ProcessResult runResult = processManager.runSync(args,
+    final RunResult runResult = processUtils.runSync(args,
         environment: androidSdk?.sdkManagerEnv);
     return CreateEmulatorResult(
       name,
@@ -139,10 +138,11 @@
       'device',
       '-c',
     ];
-    final ProcessResult runResult = processManager.runSync(args,
+    final RunResult runResult = processUtils.runSync(args,
         environment: androidSdk?.sdkManagerEnv);
-    if (runResult.exitCode != 0)
+    if (runResult.exitCode != 0) {
       return null;
+    }
 
     final List<String> availableDevices = runResult.stdout
         .split('\n')
@@ -165,7 +165,7 @@
       'avd',
       '-n', 'temp',
     ];
-    final ProcessResult runResult = processManager.runSync(args,
+    final RunResult runResult = processUtils.runSync(args,
         environment: androidSdk?.sdkManagerEnv);
 
     // Get the list of IDs that match our criteria
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_dev_finder.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_dev_finder.dart
index ce118e6..5a2db26 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_dev_finder.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_dev_finder.dart
@@ -32,7 +32,7 @@
       'list',
       '-full'
     ];
-    final RunResult result = await runAsync(command);
+    final RunResult result = await processUtils.run(command);
     if (result.exitCode != 0) {
       printError('dev_finder failed: ${result.stderr}');
       return null;
@@ -57,7 +57,7 @@
       '-device-limit', '1',
       deviceName
     ];
-    final RunResult result = await runAsync(command);
+    final RunResult result = await processUtils.run(command);
     if (result.exitCode != 0) {
       printError('dev_finder failed: ${result.stderr}');
       return null;
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index d731321..b0412df 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -454,7 +454,7 @@
       throwToolExit('Cannot interact with device. No ssh config.\n'
                     'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
     }
-    return await runAsync(<String>[
+    return await processUtils.run(<String>[
       'ssh',
       '-F',
       fuchsiaArtifacts.sshConfig.absolute.path,
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart
index 93db6ae..cccc8bb 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart
@@ -85,7 +85,7 @@
       fuchsiaArtifacts.kernelCompiler.path,
       ...flags,
     ];
-    final Process process = await runCommand(command);
+    final Process process = await processUtils.start(command);
     final Status status = logger.startProgress(
       'Building Fuchsia application...',
       timeout: null,
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
index 55907ff..03fca09 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
@@ -118,7 +118,7 @@
       '-l',
       '$host:$port',
     ];
-    final Process process = await runCommand(command);
+    final Process process = await processUtils.start(command);
     process.stdout
         .transform(utf8.decoder)
         .transform(const LineSplitter())
@@ -152,7 +152,7 @@
       throwToolExit('Fuchsia pm tool not found');
     }
     final List<String> command = <String>[fuchsiaArtifacts.pm.path] + args;
-    final RunResult result = await runAsync(command);
+    final RunResult result = await processUtils.run(command);
     return result.exitCode == 0;
   }
 }
diff --git a/packages/flutter_tools/lib/src/ios/code_signing.dart b/packages/flutter_tools/lib/src/ios/code_signing.dart
index eefe21d..b238435 100644
--- a/packages/flutter_tools/lib/src/ios/code_signing.dart
+++ b/packages/flutter_tools/lib/src/ios/code_signing.dart
@@ -96,8 +96,9 @@
   BuildableIOSApp iosApp,
 }) async {
   final Map<String, String> buildSettings = iosApp.project.buildSettings;
-  if (buildSettings == null)
+  if (buildSettings == null) {
     return null;
+  }
 
   // If the user already has it set in the project build settings itself,
   // continue with that.
@@ -114,16 +115,21 @@
 
   // If the user's environment is missing the tools needed to find and read
   // certificates, abandon. Tools should be pre-equipped on macOS.
-  if (!exitsHappy(const <String>['which', 'security']) || !exitsHappy(const <String>['which', 'openssl']))
+  if (!await processUtils.exitsHappy(const <String>['which', 'security']) ||
+      !await processUtils.exitsHappy(const <String>['which', 'openssl'])) {
     return null;
+  }
 
   const List<String> findIdentityCommand =
       <String>['security', 'find-identity', '-p', 'codesigning', '-v'];
 
   String findIdentityStdout;
   try {
-    findIdentityStdout = runCheckedSync(findIdentityCommand);
-  } catch (error) {
+    findIdentityStdout = (await processUtils.run(
+      findIdentityCommand,
+      throwOnError: true,
+    )).stdout.trim();
+  } on ProcessException catch (error) {
     printTrace('Unexpected failure from find-identity: $error.');
     return null;
   }
@@ -142,8 +148,9 @@
   final String signingIdentity = await _chooseSigningIdentity(validCodeSigningIdentities);
 
   // If none are chosen, return null.
-  if (signingIdentity == null)
+  if (signingIdentity == null) {
     return null;
+  }
 
   printStatus('Signing iOS app for device deployment using developer identity: "$signingIdentity"');
 
@@ -153,20 +160,23 @@
           ?.group(1);
 
   // If `security`'s output format changes, we'd have to update the above regex.
-  if (signingCertificateId == null)
+  if (signingCertificateId == null) {
     return null;
+  }
 
   String signingCertificateStdout;
   try {
-    signingCertificateStdout = runCheckedSync(
-      <String>['security', 'find-certificate', '-c', signingCertificateId, '-p']
-    );
-  } catch (error) {
+    signingCertificateStdout = (await processUtils.run(
+      <String>['security', 'find-certificate', '-c', signingCertificateId, '-p'],
+      throwOnError: true,
+    )).stdout.trim();
+  } on ProcessException catch (error) {
     printTrace('Couldn\'t find the certificate: $error.');
     return null;
   }
 
-  final Process opensslProcess = await runCommand(const <String>['openssl', 'x509', '-subject']);
+  final Process opensslProcess = await processUtils.start(
+    const <String>['openssl', 'x509', '-subject']);
   await (opensslProcess.stdin..write(signingCertificateStdout)).close();
 
   final String opensslOutput = await utf8.decodeStream(opensslProcess.stdout);
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index bc907ed..3d0f1f5 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -64,7 +64,7 @@
     iosDeployEnv['PATH'] = '/usr/bin:${iosDeployEnv['PATH']}';
     iosDeployEnv.addEntries(<MapEntry<String, String>>[cache.dyLdLibEntry]);
 
-    return await runCommandAndStreamOutput(
+    return await processUtils.stream(
       launchCommand,
       mapFunction: _monitorInstallationFailure,
       trace: true,
@@ -195,8 +195,9 @@
   Future<bool> isAppInstalled(ApplicationPackage app) async {
     RunResult apps;
     try {
-      apps = await runCheckedAsync(
+      apps = await processUtils.run(
         <String>[_installerPath, '--list-apps'],
+        throwOnError: true,
         environment: Map<String, String>.fromEntries(
           <MapEntry<String, String>>[cache.dyLdLibEntry],
         ),
@@ -220,8 +221,9 @@
     }
 
     try {
-      await runCheckedAsync(
+      await processUtils.run(
         <String>[_installerPath, '-i', iosApp.deviceBundlePath],
+        throwOnError: true,
         environment: Map<String, String>.fromEntries(
           <MapEntry<String, String>>[cache.dyLdLibEntry],
         ),
@@ -236,8 +238,9 @@
   @override
   Future<bool> uninstallApp(ApplicationPackage app) async {
     try {
-      await runCheckedAsync(
+      await processUtils.run(
         <String>[_installerPath, '-U', app.id],
+        throwOnError: true,
         environment: Map<String, String>.fromEntries(
           <MapEntry<String, String>>[cache.dyLdLibEntry],
         ),
@@ -610,7 +613,7 @@
     while (!connected) {
       printTrace('attempting to forward device port $devicePort to host port $hostPort');
       // Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
-      process = await runCommand(
+      process = await processUtils.start(
         <String>[
           device._iproxyPath,
           hostPort.toString(),
diff --git a/packages/flutter_tools/lib/src/ios/ios_emulators.dart b/packages/flutter_tools/lib/src/ios/ios_emulators.dart
index eec2249..a3bd885 100644
--- a/packages/flutter_tools/lib/src/ios/ios_emulators.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_emulators.dart
@@ -47,7 +47,7 @@
           .followedBy(<String>['-a', xcode.getSimulatorPath()])
           .toList();
 
-      final RunResult launchResult = await runAsync(args);
+      final RunResult launchResult = await processUtils.run(args);
       if (launchResult.exitCode != 0) {
         printError('$launchResult');
         return false;
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 9a491a6..8749ec8 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -107,7 +107,7 @@
   final String _idevicescreenshotPath;
 
   bool get isInstalled {
-    _isInstalled ??= exitsHappy(
+    _isInstalled ??= processUtils.exitsHappySync(
       <String>[
         _ideviceIdPath,
         '-h'
@@ -136,7 +136,7 @@
     final Map<String, String> executionEnv = Map<String, String>.fromEntries(
       <MapEntry<String, String>>[cache.dyLdLibEntry]
     );
-    final ProcessResult ideviceResult = (await runAsync(
+    final ProcessResult ideviceResult = (await processUtils.run(
       <String>[
         _ideviceinfoPath,
         '-u',
@@ -150,7 +150,7 @@
     }
 
     // If no device is attached, we're unable to detect any problems. Assume all is well.
-    final ProcessResult result = (await runAsync(
+    final ProcessResult result = (await processUtils.run(
       <String>[
         _ideviceIdPath,
         '-l',
@@ -161,7 +161,7 @@
       _isWorking = true;
     } else {
       // Check that we can look up the names of any attached devices.
-      _isWorking = await exitsHappyAsync(
+      _isWorking = await processUtils.exitsHappy(
         <String>[_idevicenamePath],
         environment: executionEnv,
       );
@@ -229,7 +229,7 @@
 
   /// Starts `idevicesyslog` and returns the running process.
   Future<Process> startLogger(String deviceID) {
-    return runCommand(
+    return processUtils.start(
       <String>[
         _idevicesyslogPath,
         '-u',
@@ -243,11 +243,12 @@
 
   /// Captures a screenshot to the specified outputFile.
   Future<void> takeScreenshot(File outputFile) {
-    return runCheckedAsync(
+    return processUtils.run(
       <String>[
         _idevicescreenshotPath,
         outputFile.path
       ],
+      throwOnError: true,
       environment: Map<String, String>.fromEntries(
         <MapEntry<String, String>>[cache.dyLdLibEntry]
       ),
@@ -318,8 +319,9 @@
   }
 
   Map<String, String> autoSigningConfigs;
-  if (codesign && buildForDevice)
+  if (codesign && buildForDevice) {
     autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
+  }
 
   // Before the build, all service definitions must be updated and the dylibs
   // copied over to a location that is suitable for Xcodebuild to find them.
@@ -440,7 +442,7 @@
 
   final Stopwatch sw = Stopwatch()..start();
   initialBuildStatus = logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.fastOperation);
-  final RunResult buildResult = await runAsync(
+  final RunResult buildResult = await processUtils.run(
     buildCommands,
     workingDirectory: app.project.hostAppRoot.path,
     allowReentrantFlutter: true,
@@ -476,8 +478,9 @@
   const Duration showBuildSettingsTimeout = Duration(minutes: 1);
   Map<String, String> buildSettings;
   try {
-    final RunResult showBuildSettingsResult = await runCheckedAsync(
+    final RunResult showBuildSettingsResult = await processUtils.run(
       showBuildSettingsCommand,
+      throwOnError: true,
       workingDirectory: app.project.hostAppRoot.path,
       timeout: showBuildSettingsTimeout,
       timeoutRetries: 1,
@@ -677,7 +680,10 @@
       continue;
     }
     // Shell out so permissions on the dylib are preserved.
-    await runCheckedAsync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
+    await processUtils.run(
+      <String>['/bin/cp', dylib.path, frameworksDirectory.path],
+      throwOnError: true,
+    );
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/plist_parser.dart b/packages/flutter_tools/lib/src/ios/plist_parser.dart
index 8e8beeb..f626fe6 100644
--- a/packages/flutter_tools/lib/src/ios/plist_parser.dart
+++ b/packages/flutter_tools/lib/src/ios/plist_parser.dart
@@ -4,6 +4,7 @@
 
 import '../base/context.dart';
 import '../base/file_system.dart';
+import '../base/io.dart';
 import '../base/process.dart';
 import '../convert.dart';
 import '../globals.dart';
@@ -38,9 +39,12 @@
       final List<String> args = <String>[
         executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
       ];
-      final String jsonContent = runCheckedSync(args);
+      final String jsonContent = processUtils.runSync(
+        args,
+        throwOnError: true,
+      ).stdout.trim();
       return json.decode(jsonContent);
-    } catch (error) {
+    } on ProcessException catch (error) {
       printTrace('$error');
       return const <String, dynamic>{};
     }
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index edf150f..a8f912c 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -130,7 +130,7 @@
   }
 
   Future<bool> isInstalled(String deviceId, String appId) {
-    return exitsHappyAsync(<String>[
+    return processUtils.exitsHappy(<String>[
       _xcrunPath,
       'simctl',
       'get_app_container',
@@ -142,7 +142,10 @@
   Future<RunResult> install(String deviceId, String appPath) {
     Future<RunResult> result;
     try {
-      result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
+      result = processUtils.run(
+        <String>[_xcrunPath, 'simctl', 'install', deviceId, appPath],
+        throwOnError: true,
+      );
     } on ProcessException catch (exception) {
       throwToolExit('Unable to install $appPath on $deviceId:\n$exception');
     }
@@ -152,7 +155,10 @@
   Future<RunResult> uninstall(String deviceId, String appId) {
     Future<RunResult> result;
     try {
-      result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
+      result = processUtils.run(
+        <String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId],
+        throwOnError: true,
+      );
     } on ProcessException catch (exception) {
       throwToolExit('Unable to uninstall $appId from $deviceId:\n$exception');
     }
@@ -162,14 +168,17 @@
   Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) {
     Future<RunResult> result;
     try {
-      result = runCheckedAsync(<String>[
-        _xcrunPath,
-        'simctl',
-        'launch',
-        deviceId,
-        appIdentifier,
-        ...?launchArgs,
-      ]);
+      result = processUtils.run(
+        <String>[
+          _xcrunPath,
+          'simctl',
+          'launch',
+          deviceId,
+          appIdentifier,
+          ...?launchArgs,
+        ],
+        throwOnError: true,
+      );
     } on ProcessException catch (exception) {
       throwToolExit('Unable to launch $appIdentifier on $deviceId:\n$exception');
     }
@@ -178,7 +187,10 @@
 
   Future<void> takeScreenshot(String deviceId, String outputPath) async {
     try {
-      await runCheckedAsync(<String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath]);
+      await processUtils.run(
+        <String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath],
+        throwOnError: true,
+      );
     } on ProcessException catch (exception) {
       throwToolExit('Unable to take screenshot of $deviceId:\n$exception');
     }
@@ -518,20 +530,22 @@
 /// Launches the device log reader process on the host.
 Future<Process> launchDeviceLogTool(IOSSimulator device) async {
   // Versions of iOS prior to iOS 11 log to the simulator syslog file.
-  if (await device.sdkMajorVersion < 11)
-    return runCommand(<String>['tail', '-n', '0', '-F', device.logFilePath]);
+  if (await device.sdkMajorVersion < 11) {
+    return processUtils.start(<String>['tail', '-n', '0', '-F', device.logFilePath]);
+  }
 
   // For iOS 11 and above, use /usr/bin/log to tail process logs.
   // Run in interactive mode (via script), otherwise /usr/bin/log buffers in 4k chunks. (radar: 34420207)
-  return runCommand(<String>[
+  return processUtils.start(<String>[
     'script', '/dev/null', '/usr/bin/log', 'stream', '--style', 'syslog', '--predicate', 'processImagePath CONTAINS "${device.id}"',
   ]);
 }
 
 Future<Process> launchSystemLogTool(IOSSimulator device) async {
   // Versions of iOS prior to 11 tail the simulator syslog file.
-  if (await device.sdkMajorVersion < 11)
-    return runCommand(<String>['tail', '-n', '0', '-F', '/private/var/log/system.log']);
+  if (await device.sdkMajorVersion < 11) {
+    return processUtils.start(<String>['tail', '-n', '0', '-F', '/private/var/log/system.log']);
+  }
 
   // For iOS 11 and later, all relevant detail is in the device log.
   return null;
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 7a52b55..181905f 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -14,7 +14,6 @@
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
-import '../base/process_manager.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
@@ -211,7 +210,9 @@
       return;
     }
     try {
-      final ProcessResult result = processManager.runSync(<String>[_executable, '-version']);
+      final RunResult result = processUtils.runSync(
+        <String>[_executable, '-version'],
+      );
       if (result.exitCode != 0) {
         return;
       }
@@ -255,16 +256,20 @@
   /// version below.
   Map<String, String> getBuildSettings(String projectPath, String target) {
     try {
-      final String out = runCheckedSync(<String>[
-        _executable,
-        '-project',
-        fs.path.absolute(projectPath),
-        '-target',
-        target,
-        '-showBuildSettings',
-      ], workingDirectory: projectPath);
+      final String out = processUtils.runSync(
+        <String>[
+          _executable,
+          '-project',
+          fs.path.absolute(projectPath),
+          '-target',
+          target,
+          '-showBuildSettings',
+        ],
+        throwOnError: true,
+        workingDirectory: projectPath,
+      ).stdout.trim();
       return parseXcodeBuildSettings(out);
-    } catch(error) {
+    } on ProcessException catch (error) {
       printTrace('Unexpected failure to get the build settings: $error.');
       return const <String, String>{};
     }
@@ -291,8 +296,9 @@
       // showBuildSettings is reported to ocassionally timeout. Here, we give it
       // a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
       // When there is a timeout, we retry once.
-      final RunResult result = await runCheckedAsync(
+      final RunResult result = await processUtils.run(
         showBuildSettingsCommand,
+        throwOnError: true,
         workingDirectory: projectPath,
         timeout: timeout,
         timeoutRetries: 1,
@@ -313,7 +319,7 @@
   }
 
   void cleanWorkspace(String workspacePath, String scheme) {
-    runSync(<String>[
+    processUtils.runSync(<String>[
       _executable,
       '-workspace',
       workspacePath,
@@ -325,11 +331,15 @@
   }
 
   Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
-    final RunResult result = await runCheckedAsync(<String>[
-      _executable,
-      '-list',
-      if (projectFilename != null) ...<String>['-project', projectFilename],
-    ], workingDirectory: projectPath);
+    final RunResult result = await processUtils.run(
+      <String>[
+        _executable,
+        '-list',
+        if (projectFilename != null) ...<String>['-project', projectFilename],
+      ],
+      throwOnError: true,
+      workingDirectory: projectPath,
+    );
     return XcodeProjectInfo.fromXcodeBuildOutput(result.toString());
   }
 }
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index fac60ca..08e2057 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -67,10 +67,11 @@
   String get cocoaPodsMinimumVersion => '1.6.0';
   String get cocoaPodsRecommendedVersion => '1.6.0';
 
-  Future<bool> get isInstalled => exitsHappyAsync(<String>['which', 'pod']);
+  Future<bool> get isInstalled =>
+      processUtils.exitsHappy(<String>['which', 'pod']);
 
   Future<String> get cocoaPodsVersionText {
-    _versionText ??= runAsync(<String>['pod', '--version']).then<String>((RunResult result) {
+    _versionText ??= processUtils.run(<String>['pod', '--version']).then<String>((RunResult result) {
       return result.exitCode == 0 ? result.stdout.trim() : null;
     }, onError: (dynamic _) => null);
     return _versionText;
diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart
index 8888f8d..5ab06a0 100644
--- a/packages/flutter_tools/lib/src/macos/xcode.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode.dart
@@ -10,7 +10,6 @@
 import '../base/io.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
-import '../base/process_manager.dart';
 import '../ios/xcodeproj.dart';
 
 const int kXcodeRequiredVersionMajor = 9;
@@ -25,7 +24,9 @@
   String get xcodeSelectPath {
     if (_xcodeSelectPath == null) {
       try {
-        _xcodeSelectPath = processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']).stdout.trim();
+        _xcodeSelectPath = processUtils.runSync(
+          <String>['/usr/bin/xcode-select', '--print-path'],
+        ).stdout.trim();
       } on ProcessException {
         // Ignored, return null below.
       } on ArgumentError {
@@ -52,13 +53,16 @@
   bool get eulaSigned {
     if (_eulaSigned == null) {
       try {
-        final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']);
-        if (result.stdout != null && result.stdout.contains('license'))
+        final RunResult result = processUtils.runSync(
+          <String>['/usr/bin/xcrun', 'clang'],
+        );
+        if (result.stdout != null && result.stdout.contains('license')) {
           _eulaSigned = false;
-        else if (result.stderr != null && result.stderr.contains('license'))
+        } else if (result.stderr != null && result.stderr.contains('license')) {
           _eulaSigned = false;
-        else
+        } else {
           _eulaSigned = true;
+        }
       } on ProcessException {
         _eulaSigned = false;
       }
@@ -74,7 +78,9 @@
       try {
         // This command will error if additional components need to be installed in
         // xcode 9.2 and above.
-        final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'simctl', 'list']);
+        final RunResult result = processUtils.runSync(
+          <String>['/usr/bin/xcrun', 'simctl', 'list'],
+        );
         _isSimctlInstalled = result.stderr == null || result.stderr == '';
       } on ProcessException {
         _isSimctlInstalled = false;
@@ -94,16 +100,23 @@
   }
 
   Future<RunResult> cc(List<String> args) {
-    return runCheckedAsync(<String>['xcrun', 'cc', ...args]);
+    return processUtils.run(
+      <String>['xcrun', 'cc', ...args],
+      throwOnError: true,
+    );
   }
 
   Future<RunResult> clang(List<String> args) {
-    return runCheckedAsync(<String>['xcrun', 'clang', ...args]);
+    return processUtils.run(
+      <String>['xcrun', 'clang', ...args],
+      throwOnError: true,
+    );
   }
 
   Future<String> iPhoneSdkLocation() async {
-    final RunResult runResult = await runCheckedAsync(
+    final RunResult runResult = await processUtils.run(
       <String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'],
+      throwOnError: true,
     );
     if (runResult.exitCode != 0) {
       throwToolExit('Could not find iPhone SDK location: ${runResult.stderr}');
diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart
index 4a2b444..1ce8a9d 100644
--- a/packages/flutter_tools/lib/src/test/coverage_collector.dart
+++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart
@@ -11,7 +11,7 @@
 import '../base/logger.dart';
 import '../base/os.dart';
 import '../base/platform.dart';
-import '../base/process_manager.dart';
+import '../base/process.dart';
 import '../dart/package_map.dart';
 import '../globals.dart';
 import '../vmservice.dart';
@@ -150,7 +150,7 @@
       final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_test_coverage.');
       try {
         final File sourceFile = coverageFile.copySync(fs.path.join(tempDir.path, 'lcov.source.info'));
-        final ProcessResult result = processManager.runSync(<String>[
+        final RunResult result = processUtils.runSync(<String>[
           'lcov',
           '--add-tracefile', baseCoverageData,
           '--add-tracefile', sourceFile.path,
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index e42cd6d..df4185e 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -538,7 +538,10 @@
 }
 
 String _runGit(String command) {
-  return runSync(command.split(' '), workingDirectory: Cache.flutterRoot);
+  return processUtils.runSync(
+    command.split(' '),
+    workingDirectory: Cache.flutterRoot,
+  ).stdout.trim();
 }
 
 /// Runs [command] in the root of the Flutter installation and returns the
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio.dart b/packages/flutter_tools/lib/src/windows/visual_studio.dart
index b342cf4..31fd027 100644
--- a/packages/flutter_tools/lib/src/windows/visual_studio.dart
+++ b/packages/flutter_tools/lib/src/windows/visual_studio.dart
@@ -6,7 +6,7 @@
 import '../base/file_system.dart';
 import '../base/io.dart';
 import '../base/platform.dart';
-import '../base/process_manager.dart';
+import '../base/process.dart';
 import '../convert.dart';
 
 VisualStudio get visualStudio => context.get<VisualStudio>();
@@ -167,7 +167,7 @@
         '-utf8',
         '-latest',
       ];
-      final ProcessResult whereResult = processManager.runSync(<String>[
+      final RunResult whereResult = processUtils.runSync(<String>[
         _vswherePath,
         ...defaultArguments,
         ...?additionalArguments,
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart
index 8a714a9..05944f9 100644
--- a/packages/flutter_tools/lib/src/windows/windows_device.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -8,7 +8,7 @@
 import '../base/io.dart';
 import '../base/os.dart';
 import '../base/platform.dart';
-import '../base/process_manager.dart';
+import '../base/process.dart';
 import '../build_info.dart';
 import '../desktop.dart';
 import '../device.dart';
@@ -88,7 +88,7 @@
       );
     }
     await stopApp(package);
-    final Process process = await processManager.start(<String>[
+    final Process process = await processUtils.start(<String>[
       package.executable(debuggingOptions?.buildInfo?.mode)
     ]);
     if (debuggingOptions?.buildInfo?.isRelease == true) {
@@ -114,7 +114,9 @@
     if (process == null) {
       return false;
     }
-    final ProcessResult result = await processManager.run(<String>['Taskkill', '/PID', process.first, '/F']);
+    final RunResult result = await processUtils.run(
+      <String>['Taskkill', '/PID', process.first, '/F'],
+    );
     return result.exitCode == 0;
   }
 
@@ -163,7 +165,9 @@
 @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"']);
+  final RunResult result = processUtils.runSync(
+    <String>['powershell', '-script="Get-CimInstance Win32_Process"'],
+  );
   if (result.exitCode != 0) {
     return null;
   }
diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart
index c2fbedb..a374142 100644
--- a/packages/flutter_tools/test/general.shard/application_package_test.dart
+++ b/packages/flutter_tools/test/general.shard/application_package_test.dart
@@ -87,7 +87,7 @@
           workingDirectory: anyNamed('workingDirectory'),
           environment: anyNamed('environment'),
         ),
-      ).thenReturn(ProcessResult(0, 0, _aaptDataWithDefaultEnabledAndMainLauncherActivity, null));
+      ).thenReturn(ProcessResult(0, 0, _aaptDataWithDefaultEnabledAndMainLauncherActivity, ''));
 
       final ApplicationPackage applicationPackage = await ApplicationPackageFactory.instance.getPackageForPlatform(
         TargetPlatform.android_arm,
diff --git a/packages/flutter_tools/test/general.shard/base/process_test.dart b/packages/flutter_tools/test/general.shard/base/process_test.dart
index 9c8981c..88e7dd2 100644
--- a/packages/flutter_tools/test/general.shard/base/process_test.dart
+++ b/packages/flutter_tools/test/general.shard/base/process_test.dart
@@ -26,10 +26,11 @@
       mockProcessManager = PlainMockProcessManager();
     });
 
-    testUsingContext('runCheckedAsync exceptions should be ProcessException objects', () async {
-      when(mockProcessManager.run(<String>['false']))
-          .thenAnswer((Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', '')));
-      expect(() async => await runCheckedAsync(<String>['false']), throwsA(isInstanceOf<ProcessException>()));
+    testUsingContext('runAsync throwOnError: exceptions should be ProcessException objects', () async {
+      when(mockProcessManager.run(<String>['false'])).thenAnswer(
+          (Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', '')));
+      expect(() async => await processUtils.run(<String>['false'], throwOnError: true),
+             throwsA(isInstanceOf<ProcessException>()));
     }, overrides: <Type, Generator>{ProcessManager: () => mockProcessManager});
   });
 
@@ -86,7 +87,7 @@
     testUsingContext('Command output is not wrapped.', () async {
       final List<String> testString = <String>['0123456789' * 10];
       mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString);
-      await runCommandAndStreamOutput(<String>['command']);
+      await processUtils.stream(<String>['command']);
       expect(mockLogger.statusText, equals('${testString[0]}\n'));
       expect(mockLogger.errorText, equals('${testString[0]}\n'));
     }, overrides: <Type, Generator>{
@@ -97,22 +98,82 @@
     });
   });
 
-  group('runAsync timeout and retry', () {
+  group('run', () {
     const Duration delay = Duration(seconds: 2);
     MockProcessManager flakyProcessManager;
+    ProcessManager mockProcessManager;
 
     setUp(() {
       // MockProcessManager has an implementation of start() that returns the
       // result of processFactory.
       flakyProcessManager = MockProcessManager();
+      mockProcessManager = MockProcessManager();
     });
 
-    testUsingContext('flaky process fails without retry', () async {
+    testUsingContext(' succeeds on success', () async {
+      when(mockProcessManager.run(<String>['whoohoo'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
+      });
+      expect((await processUtils.run(<String>['whoohoo'])).exitCode, 0);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' fails on failure', () async {
+      when(mockProcessManager.run(<String>['boohoo'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 1, '', ''));
+      });
+      expect((await processUtils.run(<String>['boohoo'])).exitCode, 1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' throws on failure with throwOnError', () async {
+      when(mockProcessManager.run(<String>['kaboom'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 1, '', ''));
+      });
+      expect(() => processUtils.run(<String>['kaboom'], throwOnError: true),
+             throwsA(isA<ProcessException>()));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' does not throw on failure with whitelist', () async {
+      when(mockProcessManager.run(<String>['kaboom'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 1, '', ''));
+      });
+      expect(
+        (await processUtils.run(
+          <String>['kaboom'],
+          throwOnError: true,
+          whiteListFailures: (int c) => c == 1,
+        )).exitCode,
+        1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' throws on failure when not in whitelist', () async {
+      when(mockProcessManager.run(<String>['kaboom'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 2, '', ''));
+      });
+      expect(
+        () => processUtils.run(
+          <String>['kaboom'],
+          throwOnError: true,
+          whiteListFailures: (int c) => c == 1,
+        ),
+        throwsA(isA<ProcessException>()));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' flaky process fails without retry', () async {
       flakyProcessManager.processFactory = flakyProcessFactory(
         flakes: 1,
         delay: delay,
       );
-      final RunResult result = await runAsync(
+      final RunResult result = await processUtils.run(
         <String>['dummy'],
         timeout: delay + const Duration(seconds: 1),
       );
@@ -121,12 +182,12 @@
       ProcessManager: () => flakyProcessManager,
     });
 
-    testUsingContext('flaky process succeeds with retry', () async {
+    testUsingContext(' flaky process succeeds with retry', () async {
       flakyProcessManager.processFactory = flakyProcessFactory(
         flakes: 1,
         delay: delay,
       );
-      final RunResult result = await runAsync(
+      final RunResult result = await processUtils.run(
         <String>['dummy'],
         timeout: delay - const Duration(milliseconds: 500),
         timeoutRetries: 1,
@@ -136,7 +197,7 @@
       ProcessManager: () => flakyProcessManager,
     });
 
-    testUsingContext('flaky process generates ProcessException on timeout', () async {
+    testUsingContext(' flaky process generates ProcessException on timeout', () async {
       final Completer<List<int>> flakyStderr = Completer<List<int>>();
       final Completer<List<int>> flakyStdout = Completer<List<int>>();
       flakyProcessManager.processFactory = flakyProcessFactory(
@@ -153,7 +214,7 @@
         flakyStdout.complete(<int>[]);
         return true;
       });
-      expect(() async => await runAsync(
+      expect(() => processUtils.run(
         <String>['dummy'],
         timeout: delay - const Duration(milliseconds: 500),
         timeoutRetries: 0,
@@ -162,6 +223,159 @@
       ProcessManager: () => flakyProcessManager,
     });
   });
+
+  group('runSync', () {
+    ProcessManager mockProcessManager;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+    });
+
+    testUsingContext(' succeeds on success', () async {
+      when(mockProcessManager.runSync(<String>['whoohoo'])).thenReturn(
+        ProcessResult(0, 0, '', '')
+      );
+      expect(processUtils.runSync(<String>['whoohoo']).exitCode, 0);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' fails on failure', () async {
+      when(mockProcessManager.runSync(<String>['boohoo'])).thenReturn(
+        ProcessResult(0, 1, '', '')
+      );
+      expect(processUtils.runSync(<String>['boohoo']).exitCode, 1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' throws on failure with throwOnError', () async {
+      when(mockProcessManager.runSync(<String>['kaboom'])).thenReturn(
+        ProcessResult(0, 1, '', '')
+      );
+      expect(() => processUtils.runSync(<String>['kaboom'], throwOnError: true),
+             throwsA(isA<ProcessException>()));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' does not throw on failure with whitelist', () async {
+      when(mockProcessManager.runSync(<String>['kaboom'])).thenReturn(
+        ProcessResult(0, 1, '', '')
+      );
+      expect(
+        processUtils.runSync(
+          <String>['kaboom'],
+          throwOnError: true,
+          whiteListFailures: (int c) => c == 1,
+        ).exitCode,
+        1);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' throws on failure when not in whitelist', () async {
+      when(mockProcessManager.runSync(<String>['kaboom'])).thenReturn(
+        ProcessResult(0, 2, '', '')
+      );
+      expect(
+        () => processUtils.runSync(
+          <String>['kaboom'],
+          throwOnError: true,
+          whiteListFailures: (int c) => c == 1,
+        ),
+        throwsA(isA<ProcessException>()));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' prints stdout and stderr to trace on success', () async {
+      when(mockProcessManager.runSync(<String>['whoohoo'])).thenReturn(
+        ProcessResult(0, 0, 'stdout', 'stderr')
+      );
+      expect(processUtils.runSync(<String>['whoohoo']).exitCode, 0);
+      expect(testLogger.traceText, contains('stdout'));
+      expect(testLogger.traceText, contains('stderr'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' prints stdout to status and stderr to error on failure with throwOnError', () async {
+      when(mockProcessManager.runSync(<String>['kaboom'])).thenReturn(
+        ProcessResult(0, 1, 'stdout', 'stderr')
+      );
+      expect(() => processUtils.runSync(<String>['kaboom'], throwOnError: true),
+             throwsA(isA<ProcessException>()));
+      expect(testLogger.statusText, contains('stdout'));
+      expect(testLogger.errorText, contains('stderr'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' does not print stdout with hideStdout', () async {
+      when(mockProcessManager.runSync(<String>['whoohoo'])).thenReturn(
+        ProcessResult(0, 0, 'stdout', 'stderr')
+      );
+      expect(processUtils.runSync(<String>['whoohoo'], hideStdout: true).exitCode, 0);
+      expect(testLogger.traceText.contains('stdout'), isFalse);
+      expect(testLogger.traceText, contains('stderr'));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+  });
+
+  group('exitsHappySync', () {
+    ProcessManager mockProcessManager;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+    });
+
+    testUsingContext(' succeeds on success', () async {
+      when(mockProcessManager.runSync(<String>['whoohoo'])).thenReturn(
+        ProcessResult(0, 0, '', '')
+      );
+      expect(processUtils.exitsHappySync(<String>['whoohoo']), isTrue);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' fails on failure', () async {
+      when(mockProcessManager.runSync(<String>['boohoo'])).thenReturn(
+        ProcessResult(0, 1, '', '')
+      );
+      expect(processUtils.exitsHappySync(<String>['boohoo']), isFalse);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+  });
+
+  group('exitsHappy', () {
+    ProcessManager mockProcessManager;
+
+    setUp(() {
+      mockProcessManager = MockProcessManager();
+    });
+
+    testUsingContext(' succeeds on success', () async {
+      when(mockProcessManager.run(<String>['whoohoo'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
+      });
+      expect(await processUtils.exitsHappy(<String>['whoohoo']), isTrue);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext(' fails on failure', () async {
+      when(mockProcessManager.run(<String>['boohoo'])).thenAnswer((_) {
+        return Future<ProcessResult>.value(ProcessResult(0, 1, '', ''));
+      });
+      expect(await processUtils.exitsHappy(<String>['boohoo']), isFalse);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+  });
+
 }
 
 class PlainMockProcessManager extends Mock implements ProcessManager {}
diff --git a/packages/flutter_tools/test/general.shard/ios/code_signing_test.dart b/packages/flutter_tools/test/general.shard/ios/code_signing_test.dart
index b709295..1d85a9a 100644
--- a/packages/flutter_tools/test/general.shard/ios/code_signing_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/code_signing_test.dart
@@ -59,8 +59,8 @@
     });
 
     testUsingContext('No auto-sign if security or openssl not available', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsFail);
+      when(mockProcessManager.run(<String>['which', 'security']))
+          .thenAnswer((_) => Future<ProcessResult>.value(exitsFail));
       final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
       expect(signingConfigs, isNull);
     },
@@ -69,15 +69,21 @@
     });
 
     testUsingContext('No valid code signing certificates shows instructions', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(exitsHappy);
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
 
 
       Map<String, String> signingConfigs;
@@ -95,39 +101,45 @@
     });
 
     testUsingContext('Test single identity and certificate organization works', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
 1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
     1 valid identities found''',
         '',
-      ));
-      when(mockProcessManager.runSync(
+      )));
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
       final MockProcess mockProcess = MockProcess();
       final MockStdIn mockStdIn = MockStdIn();
       final MockStream mockStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess));
@@ -156,39 +168,45 @@
 
 
     testUsingContext('Test single identity (Catalina format) and certificate organization works', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
 1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "Apple Development: Profile 1 (1111AAAA11)"
     1 valid identities found''',
         '',
-      ));
-      when(mockProcessManager.runSync(
+      )));
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
       final MockProcess mockProcess = MockProcess();
       final MockStdIn mockStdIn = MockStdIn();
       final MockStream mockStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess));
@@ -222,15 +240,21 @@
     });
 
     testUsingContext('Test multiple identity and certificate organization works', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
@@ -239,26 +263,26 @@
 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
     3 valid identities found''',
         '',
-      ));
+      )));
       mockTerminalStdInStream =
           Stream<String>.fromFuture(Future<String>.value('3'));
-      when(mockProcessManager.runSync(
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
       final MockProcess mockOpenSslProcess = MockProcess();
       final MockStdIn mockOpenSslStdIn = MockStdIn();
       final MockStream mockOpenSslStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
@@ -298,15 +322,21 @@
 
     testUsingContext('Test multiple identity in machine mode works', () async {
       testTerminal.usesTerminalUi = false;
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
@@ -315,26 +345,26 @@
 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
     3 valid identities found''',
           '',
-      ));
+      )));
       mockTerminalStdInStream =
         Stream<String>.fromFuture(Future<String>.error(Exception('Cannot read from StdIn')));
-      when(mockProcessManager.runSync(
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
       final MockProcess mockOpenSslProcess = MockProcess();
       final MockStdIn mockOpenSslStdIn = MockStdIn();
       final MockStream mockOpenSslStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
@@ -367,15 +397,21 @@
     });
 
     testUsingContext('Test saved certificate used', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
@@ -384,24 +420,24 @@
 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
     3 valid identities found''',
         '',
-      ));
-      when(mockProcessManager.runSync(
+      )));
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
       final MockProcess mockOpenSslProcess = MockProcess();
       final MockStdIn mockOpenSslStdIn = MockStdIn();
       final MockStream mockOpenSslStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
@@ -438,15 +474,21 @@
     });
 
     testUsingContext('Test invalid saved certificate shows error and prompts again', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-      argThat(contains('find-identity')),
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         '''
@@ -455,19 +497,19 @@
 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
     3 valid identities found''',
         '',
-      ));
+      )));
       mockTerminalStdInStream =
           Stream<String>.fromFuture(Future<String>.value('3'));
-      when(mockProcessManager.runSync(
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
         1, // pid
         0, // exitCode
         'This is a mock certificate',
         '',
-      ));
+      )));
 
 
       final MockProcess mockOpenSslProcess = MockProcess();
@@ -475,7 +517,7 @@
       final MockStream mockOpenSslStdErr = MockStream();
 
       when(mockProcessManager.start(
-      argThat(contains('openssl')),
+        argThat(contains('openssl')),
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
       )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
@@ -511,15 +553,23 @@
     });
 
     testUsingContext('find-identity failure', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-          argThat(contains('find-identity')),
-            environment: anyNamed('environment'),
-            workingDirectory: anyNamed('workingDirectory'),
-          )).thenReturn(ProcessResult(0, 1, '', ''));
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
+        environment: anyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(
+        ProcessResult(0, 1, '', '')
+      ));
 
       final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
       expect(signingConfigs, isNull);
@@ -531,15 +581,21 @@
     });
 
     testUsingContext('find-certificate failure', () async {
-      when(mockProcessManager.runSync(<String>['which', 'security']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(<String>['which', 'openssl']))
-          .thenReturn(exitsHappy);
-      when(mockProcessManager.runSync(
-          argThat(contains('find-identity')),
-            environment: anyNamed('environment'),
-            workingDirectory: anyNamed('workingDirectory'),
-          )).thenReturn(ProcessResult(
+      when(mockProcessManager.run(
+        <String>['which', 'security'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        <String>['which', 'openssl'],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
+      when(mockProcessManager.run(
+        argThat(contains('find-identity')),
+        environment: anyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
             1, // pid
             0, // exitCode
             '''
@@ -548,14 +604,16 @@
 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
     3 valid identities found''',
             '',
-          ));
+      )));
       mockTerminalStdInStream =
           Stream<String>.fromFuture(Future<String>.value('3'));
-      when(mockProcessManager.runSync(
+      when(mockProcessManager.run(
         <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
         environment: anyNamed('environment'),
         workingDirectory: anyNamed('workingDirectory'),
-      )).thenReturn(ProcessResult(1, 1, '', '' ));
+      )).thenAnswer((_) => Future<ProcessResult>.value(
+        ProcessResult(1, 1, '', '' ))
+      );
 
       final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
       expect(signingConfigs, isNull);
diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
index f2b38c0..a05f1d0 100644
--- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
@@ -142,10 +142,13 @@
         final MockDirectory directory = MockDirectory();
         when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
         when(directory.existsSync()).thenReturn(true);
-        when(mockProcessManager.run(installArgs, environment: env))
-            .thenAnswer(
-                (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
-            );
+        when(mockProcessManager.run(
+          installArgs,
+          workingDirectory: anyNamed('workingDirectory'),
+          environment: env
+        )).thenAnswer(
+          (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+        );
 
         when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture'))
             .thenAnswer((_) => Future<String>.value('arm64'));
@@ -259,6 +262,21 @@
             return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
           });
 
+          when(mockProcessManager.run(
+            argThat(contains('find-identity')),
+            environment: anyNamed('environment'),
+            workingDirectory: anyNamed('workingDirectory'),
+          )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
+                1, // pid
+                0, // exitCode
+                '''
+    1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
+    2) da4b9237bacccdf19c0760cab7aec4a8359010b0 "iPhone Developer: Profile 2 (2222BBBB22)"
+    3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
+        3 valid identities found''',
+                '',
+          )));
+
           // Deploy works.
           when(mockIosDeploy.runApp(
             deviceId: anyNamed('deviceId'),
diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
index cf74b33..6870c50 100644
--- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -146,7 +146,10 @@
     });
 
     testUsingOsxContext('build settings is empty when xcodebuild failed to get the build settings', () {
-      when(mockProcessManager.runSync(argThat(contains(xcodebuild))))
+      when(mockProcessManager.runSync(
+               argThat(contains(xcodebuild)),
+               workingDirectory: anyNamed('workingDirectory'),
+               environment: anyNamed('environment')))
           .thenReturn(ProcessResult(0, 1, '', ''));
       expect(xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{});
     });
diff --git a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart
index 26a591b..d6da8bf 100644
--- a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart
@@ -69,18 +69,23 @@
     final String finalResponse =
         json.encode(<Map<String, dynamic>>[response]);
     when<String>(result.stdout).thenReturn(finalResponse);
+    when<String>(result.stderr).thenReturn('');
     final List<String> requirementArguments = requiredComponents == null
         ? <String>[]
         : <String>['-requires', ...requiredComponents];
-    when(mockProcessManager.runSync(<String>[
-      vswherePath,
-      '-format',
-      'json',
-      '-utf8',
-      '-latest',
-      ...?additionalArguments,
-      ...?requirementArguments,
-    ])).thenAnswer((Invocation invocation) {
+    when(mockProcessManager.runSync(
+      <String>[
+        vswherePath,
+          '-format',
+          'json',
+          '-utf8',
+          '-latest',
+          ...?additionalArguments,
+          ...?requirementArguments,
+      ],
+      workingDirectory: anyNamed('workingDirectory'),
+      environment: anyNamed('environment'),
+    )).thenAnswer((Invocation invocation) {
       return result;
     });
   }
@@ -111,8 +116,11 @@
     });
 
     testUsingContext('isInstalled returns false when vswhere is missing', () {
-      when(mockProcessManager.runSync(any))
-          .thenThrow(const ProcessException('vswhere', <String>[]));
+      when(mockProcessManager.runSync(
+        any,
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenThrow(const ProcessException('vswhere', <String>[]));
 
       visualStudio = VisualStudio();
       expect(visualStudio.isInstalled, false);
@@ -123,8 +131,11 @@
     });
 
     testUsingContext('vcvarsPath returns null when vswhere is missing', () {
-      when(mockProcessManager.runSync(any))
-          .thenThrow(const ProcessException('vswhere', <String>[]));
+      when(mockProcessManager.runSync(
+        any,
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenThrow(const ProcessException('vswhere', <String>[]));
 
       visualStudio = VisualStudio();
       expect(visualStudio.vcvarsPath, isNull);
@@ -135,13 +146,24 @@
     });
 
     testUsingContext('isInstalled returns false when vswhere returns non-zero', () {
-      when(mockProcessManager.runSync(any))
-          .thenThrow(const ProcessException('vswhere', <String>[]));
+
+      when(mockProcessManager.runSync(
+        any,
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenThrow(const ProcessException('vswhere', <String>[]));
+
       final MockProcessResult result = MockProcessResult();
       when(result.exitCode).thenReturn(1);
-      when(mockProcessManager.runSync(any)).thenAnswer((Invocation invocation) {
+      when(mockProcessManager.runSync(
+        any,
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).thenAnswer((Invocation invocation) {
         return result;
       });
+      when<String>(result.stdout).thenReturn('');
+      when<String>(result.stderr).thenReturn('');
 
       visualStudio = VisualStudio();
       expect(visualStudio.isInstalled, false);
diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
index c1d650f..755d08f 100644
--- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
@@ -26,18 +26,23 @@
 
     when(notWindows.isWindows).thenReturn(false);
     when(notWindows.environment).thenReturn(const <String, String>{});
-    when(mockProcessManager.runSync(<String>[
-      'powershell', '-script="Get-CimInstance Win32_Process"'
-    ])).thenAnswer((Invocation invocation) {
+    when(mockProcessManager.runSync(
+      <String>['powershell', '-script="Get-CimInstance Win32_Process"'],
+      workingDirectory: anyNamed('workingDirectory'),
+      environment: anyNamed('environment'),
+    )).thenAnswer((Invocation invocation) {
       // The flutter tool process is returned as output to the powershell script
       final MockProcessResult result = MockProcessResult();
       when(result.exitCode).thenReturn(0);
       when<String>(result.stdout).thenReturn('$pid  $flutterToolBinary');
+      when<String>(result.stderr).thenReturn('');
       return result;
     });
-    when(mockProcessManager.run(<String>[
-      'Taskkill', '/PID', '$pid', '/F'
-    ])).thenThrow(Exception('Flutter tool process has been killed'));
+    when(mockProcessManager.run(
+      <String>['Taskkill', '/PID', '$pid', '/F'],
+      workingDirectory: anyNamed('workingDirectory'),
+      environment: anyNamed('environment'),
+    )).thenThrow(Exception('Flutter tool process has been killed'));
 
     testUsingContext('defaults', () async {
       final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');