Unverified Commit 73c10e8c authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] process.dart cleanup (#39899)

parent 3712ea63
...@@ -227,11 +227,14 @@ class AndroidDevice extends Device { ...@@ -227,11 +227,14 @@ class AndroidDevice extends Device {
String workingDirectory, String workingDirectory,
bool allowReentrantFlutter = false, bool allowReentrantFlutter = false,
Map<String, String> environment}) { Map<String, String> environment}) {
return runCheckedSync(adbCommandForDevice(params), workingDirectory: workingDirectory, return processUtils.runSync(
allowReentrantFlutter: allowReentrantFlutter, adbCommandForDevice(params),
environment: environment, throwOnError: true,
whiteListFailures: allowHeapCorruptionOnWindows workingDirectory: workingDirectory,
); allowReentrantFlutter: allowReentrantFlutter,
environment: environment,
whiteListFailures: allowHeapCorruptionOnWindows,
).stdout.trim();
} }
Future<RunResult> runAdbCheckedAsync( Future<RunResult> runAdbCheckedAsync(
...@@ -239,9 +242,13 @@ class AndroidDevice extends Device { ...@@ -239,9 +242,13 @@ class AndroidDevice extends Device {
String workingDirectory, String workingDirectory,
bool allowReentrantFlutter = false, bool allowReentrantFlutter = false,
}) async { }) async {
return runCheckedAsync(adbCommandForDevice(params), workingDirectory: workingDirectory, return processUtils.run(
allowReentrantFlutter: allowReentrantFlutter, adbCommandForDevice(params),
whiteListFailures: allowHeapCorruptionOnWindows); throwOnError: true,
workingDirectory: workingDirectory,
allowReentrantFlutter: allowReentrantFlutter,
whiteListFailures: allowHeapCorruptionOnWindows,
);
} }
bool _isValidAdbVersion(String adbVersion) { bool _isValidAdbVersion(String adbVersion) {
...@@ -268,13 +275,18 @@ class AndroidDevice extends Device { ...@@ -268,13 +275,18 @@ class AndroidDevice extends Device {
} }
Future<bool> _checkForSupportedAdbVersion() async { Future<bool> _checkForSupportedAdbVersion() async {
if (androidSdk == null) if (androidSdk == null) {
return false; return false;
}
try { try {
final RunResult adbVersion = await runCheckedAsync(<String>[getAdbPath(androidSdk), 'version']); final RunResult adbVersion = await processUtils.run(
if (_isValidAdbVersion(adbVersion.stdout)) <String>[getAdbPath(androidSdk), 'version'],
throwOnError: true,
);
if (_isValidAdbVersion(adbVersion.stdout)) {
return true; return true;
}
printError('The ADB at "${getAdbPath(androidSdk)}" is too old; please install version 1.0.39 or later.'); printError('The ADB at "${getAdbPath(androidSdk)}" is too old; please install version 1.0.39 or later.');
} catch (error, trace) { } catch (error, trace) {
printError('Error running ADB: $error', stackTrace: trace); printError('Error running ADB: $error', stackTrace: trace);
...@@ -289,7 +301,10 @@ class AndroidDevice extends Device { ...@@ -289,7 +301,10 @@ class AndroidDevice extends Device {
// output lines like this, which we want to ignore: // output lines like this, which we want to ignore:
// adb server is out of date. killing.. // adb server is out of date. killing..
// * daemon started successfully * // * daemon started successfully *
await runCheckedAsync(<String>[getAdbPath(androidSdk), 'start-server']); await processUtils.run(
<String>[getAdbPath(androidSdk), 'start-server'],
throwOnError: true,
);
// Sample output: '22' // Sample output: '22'
final String sdkVersion = await _getProperty('ro.build.version.sdk'); final String sdkVersion = await _getProperty('ro.build.version.sdk');
...@@ -320,7 +335,8 @@ class AndroidDevice extends Device { ...@@ -320,7 +335,8 @@ class AndroidDevice extends Device {
} }
Future<String> _getDeviceApkSha1(ApplicationPackage app) async { 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; return result.stdout;
} }
...@@ -363,7 +379,8 @@ class AndroidDevice extends Device { ...@@ -363,7 +379,8 @@ class AndroidDevice extends Device {
return false; return false;
final Status status = logger.startProgress('Installing ${fs.path.relative(apk.file.path)}...', timeout: timeoutConfiguration.slowOperation); 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(); status.stop();
// Some versions of adb exit with exit code 0 even on failure :( // Some versions of adb exit with exit code 0 even on failure :(
// Parsing the output to check for failures. // Parsing the output to check for failures.
...@@ -396,7 +413,11 @@ class AndroidDevice extends Device { ...@@ -396,7 +413,11 @@ class AndroidDevice extends Device {
String uninstallOut; String uninstallOut;
try { 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) { } catch (error) {
printError('adb uninstall failed: $error'); printError('adb uninstall failed: $error');
return false; return false;
...@@ -603,13 +624,13 @@ class AndroidDevice extends Device { ...@@ -603,13 +624,13 @@ class AndroidDevice extends Device {
@override @override
Future<bool> stopApp(ApplicationPackage app) { Future<bool> stopApp(ApplicationPackage app) {
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]); 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)); (int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode));
} }
@override @override
void clearLogs() { void clearLogs() {
runSync(adbCommandForDevice(<String>['logcat', '-c'])); processUtils.runSync(adbCommandForDevice(<String>['logcat', '-c']));
} }
@override @override
...@@ -650,7 +671,10 @@ class AndroidDevice extends Device { ...@@ -650,7 +671,10 @@ class AndroidDevice extends Device {
Future<void> takeScreenshot(File outputFile) async { Future<void> takeScreenshot(File outputFile) async {
const String remotePath = '/data/local/tmp/flutter_screenshot.png'; const String remotePath = '/data/local/tmp/flutter_screenshot.png';
await runAdbCheckedAsync(<String>['shell', 'screencap', '-p', remotePath]); 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]); await runAdbCheckedAsync(<String>['shell', 'rm', remotePath]);
} }
...@@ -675,7 +699,10 @@ List<AndroidDevice> getAdbDevices() { ...@@ -675,7 +699,10 @@ List<AndroidDevice> getAdbDevices() {
return <AndroidDevice>[]; return <AndroidDevice>[];
String text; String text;
try { try {
text = runSync(<String>[adbPath, 'devices', '-l']); text = processUtils.runSync(
<String>[adbPath, 'devices', '-l'],
throwOnError: true,
).stdout.trim();
} on ArgumentError catch (exception) { } on ArgumentError catch (exception) {
throwToolExit('Unable to find "adb", check your Android SDK installation and ' throwToolExit('Unable to find "adb", check your Android SDK installation and '
'ANDROID_HOME environment variable: ${exception.message}'); 'ANDROID_HOME environment variable: ${exception.message}');
...@@ -694,7 +721,7 @@ Future<List<String>> getAdbDeviceDiagnostics() async { ...@@ -694,7 +721,7 @@ Future<List<String>> getAdbDeviceDiagnostics() async {
if (adbPath == null) if (adbPath == null)
return <String>[]; 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) { if (result.exitCode != 0) {
return <String>[]; return <String>[];
} else { } else {
...@@ -816,11 +843,12 @@ class _AdbLogReader extends DeviceLogReader { ...@@ -816,11 +843,12 @@ class _AdbLogReader extends DeviceLogReader {
// Start the adb logcat process. // Start the adb logcat process.
final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time']; final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time'];
final String lastTimestamp = device.lastLogcatTimestamp; final String lastTimestamp = device.lastLogcatTimestamp;
if (lastTimestamp != null) if (lastTimestamp != null) {
_timeOrigin = _adbTimestampToDateTime(lastTimestamp); _timeOrigin = _adbTimestampToDateTime(lastTimestamp);
else } else {
_timeOrigin = null; _timeOrigin = null;
runCommand(device.adbCommandForDevice(args)).then<void>((Process process) { }
processUtils.start(device.adbCommandForDevice(args)).then<void>((Process process) {
_process = process; _process = process;
// We expect logcat streams to occasionally contain invalid utf-8, // We expect logcat streams to occasionally contain invalid utf-8,
// see: https://github.com/flutter/flutter/pull/8864. // see: https://github.com/flutter/flutter/pull/8864.
...@@ -959,33 +987,37 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -959,33 +987,37 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
String stdout; String stdout;
try { try {
stdout = runCheckedSync(device.adbCommandForDevice( stdout = processUtils.runSync(
<String>['forward', '--list'] device.adbCommandForDevice(<String>['forward', '--list']),
)); throwOnError: true,
} catch (error) { ).stdout.trim();
} on ProcessException catch (error) {
printError('Failed to list forwarded ports: $error.'); printError('Failed to list forwarded ports: $error.');
return ports; return ports;
} }
final List<String> lines = LineSplitter.split(stdout).toList(); final List<String> lines = LineSplitter.split(stdout).toList();
for (String line in lines) { for (String line in lines) {
if (line.startsWith(device.id)) { if (!line.startsWith(device.id)) {
final List<String> splitLine = line.split('tcp:'); continue;
}
// Sanity check splitLine. final List<String> splitLine = line.split('tcp:');
if (splitLine.length != 3)
continue;
// Attempt to extract ports. // Sanity check splitLine.
final int hostPort = _extractPort(splitLine[1]); if (splitLine.length != 3) {
final int devicePort = _extractPort(splitLine[2]); continue;
}
// Failed, skip. // Attempt to extract ports.
if (hostPort == null || devicePort == null) final int hostPort = _extractPort(splitLine[1]);
continue; final int devicePort = _extractPort(splitLine[2]);
ports.add(ForwardedPort(hostPort, devicePort)); // Failed, skip.
if (hostPort == null || devicePort == null) {
continue;
} }
ports.add(ForwardedPort(hostPort, devicePort));
} }
return ports; return ports;
...@@ -994,23 +1026,35 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -994,23 +1026,35 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
@override @override
Future<int> forward(int devicePort, { int hostPort }) async { Future<int> forward(int devicePort, { int hostPort }) async {
hostPort ??= 0; hostPort ??= 0;
final RunResult process = await runCheckedAsync(device.adbCommandForDevice( final List<String> forwardCommand = <String>[
<String>['forward', 'tcp:$hostPort', 'tcp:$devicePort'] '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}'); process.throwException('adb returned error:\n${process.stderr}');
}
if (process.exitCode != 0) { if (process.exitCode != 0) {
if (process.stdout.isNotEmpty) if (process.stdout.isNotEmpty) {
process.throwException('adb returned error:\n${process.stdout}'); process.throwException('adb returned error:\n${process.stdout}');
}
process.throwException('adb failed without a message'); process.throwException('adb failed without a message');
} }
if (hostPort == 0) { if (hostPort == 0) {
if (process.stdout.isEmpty) if (process.stdout.isEmpty) {
process.throwException('adb did not report forwarded port'); 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 { } else {
// stdout may be empty or the port we asked it to forward, though it's // stdout may be empty or the port we asked it to forward, though it's
// not documented (or obvious) what triggers each case. // not documented (or obvious) what triggers each case.
...@@ -1025,8 +1069,9 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -1025,8 +1069,9 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
// To cover all cases, we accept the output being either empty or exactly // 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 // the port number, but treat any other output as probably being an error
// message. // 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}'); process.throwException('adb returned error:\n${process.stdout}');
}
} }
return hostPort; return hostPort;
...@@ -1034,8 +1079,14 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -1034,8 +1079,14 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
@override @override
Future<void> unforward(ForwardedPort forwardedPort) async { Future<void> unforward(ForwardedPort forwardedPort) async {
await runCheckedAsync(device.adbCommandForDevice( final List<String> unforwardCommand = <String>[
<String>['forward', '--remove', 'tcp:${forwardedPort.hostPort}'] 'forward',
)); '--remove',
'tcp:${forwardedPort.hostPort}',
];
await processUtils.run(
device.adbCommandForDevice(unforwardCommand),
throwOnError: true,
);
} }
} }
...@@ -9,8 +9,7 @@ import 'package:meta/meta.dart'; ...@@ -9,8 +9,7 @@ import 'package:meta/meta.dart';
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../android/android_workflow.dart'; import '../android/android_workflow.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/process.dart';
import '../base/process_manager.dart';
import '../device.dart'; import '../device.dart';
import '../emulator.dart'; import '../emulator.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
...@@ -50,13 +49,10 @@ class AndroidEmulator extends Emulator { ...@@ -50,13 +49,10 @@ class AndroidEmulator extends Emulator {
@override @override
Future<void> launch() async { Future<void> launch() async {
final Future<void> launchResult = final Future<void> launchResult = processUtils.run(
processManager.run(<String>[getEmulatorPath(), '-avd', id]) <String>[getEmulatorPath(), '-avd', id],
.then((ProcessResult runResult) { throwOnError: true,
if (runResult.exitCode != 0) { );
throw '${runResult.stdout}\n${runResult.stderr}'.trimRight();
}
});
// The emulator continues running on a successful launch, so if it hasn't // 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 // 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 // means that on a slow machine, a failure that takes more than three
...@@ -75,7 +71,8 @@ List<AndroidEmulator> getEmulatorAvds() { ...@@ -75,7 +71,8 @@ List<AndroidEmulator> getEmulatorAvds() {
return <AndroidEmulator>[]; 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>[]; final List<AndroidEmulator> emulators = <AndroidEmulator>[];
if (listAvdsOutput != null) { if (listAvdsOutput != null) {
......
...@@ -7,7 +7,6 @@ import 'package:meta/meta.dart'; ...@@ -7,7 +7,6 @@ import 'package:meta/meta.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart' show ProcessResult;
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
...@@ -526,9 +525,9 @@ class AndroidSdk { ...@@ -526,9 +525,9 @@ class AndroidSdk {
/// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH. /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
static String findJavaBinary() { static String findJavaBinary() {
if (android_studio.javaPath != null) {
if (android_studio.javaPath != null)
return fs.path.join(android_studio.javaPath, 'bin', 'java'); return fs.path.join(android_studio.javaPath, 'bin', 'java');
}
final String javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable]; final String javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable];
if (javaHomeEnv != null) { if (javaHomeEnv != null) {
...@@ -540,7 +539,11 @@ class AndroidSdk { ...@@ -540,7 +539,11 @@ class AndroidSdk {
// See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac. // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
if (platform.isMacOS) { if (platform.isMacOS) {
try { 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) { if (javaHomeOutput != null) {
final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n'); final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) { if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
...@@ -575,7 +578,10 @@ class AndroidSdk { ...@@ -575,7 +578,10 @@ class AndroidSdk {
String get sdkManagerVersion { String get sdkManagerVersion {
if (!processManager.canRun(sdkManagerPath)) if (!processManager.canRun(sdkManagerPath))
throwToolExit('Android sdkmanager not found. Update to the latest Android SDK to resolve this.'); 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) { if (result.exitCode != 0) {
printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}'); printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
return null; return null;
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -290,7 +290,7 @@ class AndroidStudio implements Comparable<AndroidStudio> { ...@@ -290,7 +290,7 @@ class AndroidStudio implements Comparable<AndroidStudio> {
if (!processManager.canRun(javaExecutable)) { if (!processManager.canRun(javaExecutable)) {
_validationMessages.add('Unable to find bundled Java version.'); _validationMessages.add('Unable to find bundled Java version.');
} else { } else {
final ProcessResult result = processManager.runSync(<String>[javaExecutable, '-version']); final RunResult result = processUtils.runSync(<String>[javaExecutable, '-version']);
if (result.exitCode == 0) { if (result.exitCode == 0) {
final List<String> versionLines = result.stderr.split('\n'); final List<String> versionLines = result.stderr.split('\n');
final String javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; final String javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
......
...@@ -259,7 +259,7 @@ class AndroidLicenseValidator extends DoctorValidator { ...@@ -259,7 +259,7 @@ class AndroidLicenseValidator extends DoctorValidator {
} }
try { try {
final Process process = await runCommand( final Process process = await processUtils.start(
<String>[androidSdk.sdkManagerPath, '--licenses'], <String>[androidSdk.sdkManagerPath, '--licenses'],
environment: androidSdk.sdkManagerEnv, environment: androidSdk.sdkManagerEnv,
); );
...@@ -302,7 +302,7 @@ class AndroidLicenseValidator extends DoctorValidator { ...@@ -302,7 +302,7 @@ class AndroidLicenseValidator extends DoctorValidator {
} }
try { try {
final Process process = await runCommand( final Process process = await processUtils.start(
<String>[androidSdk.sdkManagerPath, '--licenses'], <String>[androidSdk.sdkManagerPath, '--licenses'],
environment: androidSdk.sdkManagerEnv, environment: androidSdk.sdkManagerEnv,
); );
......
...@@ -145,8 +145,9 @@ Future<void> checkGradleDependencies() async { ...@@ -145,8 +145,9 @@ Future<void> checkGradleDependencies() async {
final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation); final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
final String gradlew = await gradleUtils.getExecutable(flutterProject); final String gradlew = await gradleUtils.getExecutable(flutterProject);
await runCheckedAsync( await processUtils.run(
<String>[gradlew, 'dependencies'], <String>[gradlew, 'dependencies'],
throwOnError: true,
workingDirectory: flutterProject.android.hostAppGradleRoot.path, workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
...@@ -234,13 +235,15 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async { ...@@ -234,13 +235,15 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
// Get the properties and tasks from Gradle, so we can determinate the `buildDir`, // 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 // flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
try { try {
final RunResult propertiesRunResult = await runCheckedAsync( final RunResult propertiesRunResult = await processUtils.run(
<String>[gradlew, isLibrary ? 'properties' : 'app:properties'], <String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
throwOnError: true,
workingDirectory: hostAppGradleRoot.path, workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
final RunResult tasksRunResult = await runCheckedAsync( final RunResult tasksRunResult = await processUtils.run(
<String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'], <String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
throwOnError: true,
workingDirectory: hostAppGradleRoot.path, workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
...@@ -306,22 +309,29 @@ Future<String> _initializeGradle(FlutterProject project) async { ...@@ -306,22 +309,29 @@ Future<String> _initializeGradle(FlutterProject project) async {
injectGradleWrapperIfNeeded(android); injectGradleWrapperIfNeeded(android);
final String gradle = _locateGradlewExecutable(android); final String gradle = _locateGradlewExecutable(android);
if (gradle == null) if (gradle == null) {
status.stop();
throwToolExit('Unable to locate gradlew script'); throwToolExit('Unable to locate gradlew script');
}
printTrace('Using gradle from $gradle.'); printTrace('Using gradle from $gradle.');
// Validates the Gradle executable by asking for its version. // Validates the Gradle executable by asking for its version.
// Makes Gradle Wrapper download and install Gradle distribution, if needed. // Makes Gradle Wrapper download and install Gradle distribution, if needed.
try { try {
await runCheckedAsync(<String>[gradle, '-v'], environment: _gradleEnv); await processUtils.run(
} catch (e) { <String>[gradle, '-v'],
if (e is ProcessException && throwOnError: true,
e.toString().contains('java.io.FileNotFoundException: https://downloads.gradle.org') || environment: _gradleEnv,
e.toString().contains('java.io.IOException: Unable to tunnel through proxy')) { );
} 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'); throwToolExit('$gradle threw an error while trying to update itself.\n$e');
} }
rethrow; rethrow;
} finally {
status.stop();
} }
status.stop();
return gradle; return gradle;
} }
...@@ -595,7 +605,7 @@ Future<void> buildGradleAar({ ...@@ -595,7 +605,7 @@ Future<void> buildGradleAar({
int exitCode = 1; int exitCode = 1;
try { try {
exitCode = await runCommandAndStreamOutput( exitCode = await processUtils.stream(
command, command,
workingDirectory: project.android.hostAppGradleRoot.path, workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true, allowReentrantFlutter: true,
...@@ -633,7 +643,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project) async { ...@@ -633,7 +643,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project) async {
multilineOutput: true, multilineOutput: true,
); );
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final int exitCode = await runCommandAndStreamOutput( final int exitCode = await processUtils.stream(
<String>[fs.file(gradlew).absolute.path, 'build'], <String>[fs.file(gradlew).absolute.path, 'build'],
workingDirectory: project.android.hostAppGradleRoot.path, workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true, allowReentrantFlutter: true,
...@@ -756,7 +766,7 @@ Future<void> _buildGradleProjectV2( ...@@ -756,7 +766,7 @@ Future<void> _buildGradleProjectV2(
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
int exitCode = 1; int exitCode = 1;
try { try {
exitCode = await runCommandAndStreamOutput( exitCode = await processUtils.stream(
command, command,
workingDirectory: flutterProject.android.hostAppGradleRoot.path, workingDirectory: flutterProject.android.hostAppGradleRoot.path,
allowReentrantFlutter: true, allowReentrantFlutter: true,
......
...@@ -13,6 +13,7 @@ import 'android/gradle.dart'; ...@@ -13,6 +13,7 @@ import 'android/gradle.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/io.dart';
import 'base/os.dart' show os; import 'base/os.dart' show os;
import 'base/process.dart'; import 'base/process.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
...@@ -115,14 +116,17 @@ class AndroidApk extends ApplicationPackage { ...@@ -115,14 +116,17 @@ class AndroidApk extends ApplicationPackage {
String apptStdout; String apptStdout;
try { try {
apptStdout = runCheckedSync(<String>[ apptStdout = processUtils.runSync(
aaptPath, <String>[
'dump', aaptPath,
'xmltree', 'dump',
apk.path, 'xmltree',
'AndroidManifest.xml', apk.path,
]); 'AndroidManifest.xml',
} catch (error) { ],
throwOnError: true,
).stdout.trim();
} on ProcessException catch (error) {
printError('Failed to extract manifest from APK: $error.'); printError('Failed to extract manifest from APK: $error.');
return null; return null;
} }
......
...@@ -69,7 +69,10 @@ class GenSnapshot { ...@@ -69,7 +69,10 @@ class GenSnapshot {
outputFilter = (String line) => line != kStripWarning ? line : null; outputFilter = (String line) => line != kStripWarning ? line : null;
} }
return runCommandAndStreamOutput(<String>[snapshotterPath, ...args], mapFunction: outputFilter); return processUtils.stream(
<String>[snapshotterPath, ...args],
mapFunction: outputFilter,
);
} }
} }
......
...@@ -154,30 +154,45 @@ class _PosixUtils extends OperatingSystemUtils { ...@@ -154,30 +154,45 @@ class _PosixUtils extends OperatingSystemUtils {
@override @override
void zip(Directory data, File zipFile) { 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 // unzip -o -q zipfile -d dest
@override @override
void unzip(File file, Directory targetDirectory) { 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 @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 // tar -xzf tarball -C dest
@override @override
void unpack(File gzippedTarFile, Directory targetDirectory) { 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 @override
bool verifyGzip(File gzippedFile) => exitsHappy(<String>['gzip', '-t', gzippedFile.path]); bool verifyGzip(File gzippedFile) =>
processUtils.exitsHappySync(<String>['gzip', '-t', gzippedFile.path]);
@override @override
File makePipe(String path) { File makePipe(String path) {
runSync(<String>['mkfifo', path]); processUtils.runSync(
<String>['mkfifo', path],
throwOnError: true,
);
return fs.file(path); return fs.file(path);
} }
...@@ -187,12 +202,12 @@ class _PosixUtils extends OperatingSystemUtils { ...@@ -187,12 +202,12 @@ class _PosixUtils extends OperatingSystemUtils {
String get name { String get name {
if (_name == null) { if (_name == null) {
if (platform.isMacOS) { if (platform.isMacOS) {
final List<ProcessResult> results = <ProcessResult>[ final List<RunResult> results = <RunResult>[
processManager.runSync(<String>['sw_vers', '-productName']), processUtils.runSync(<String>['sw_vers', '-productName']),
processManager.runSync(<String>['sw_vers', '-productVersion']), processUtils.runSync(<String>['sw_vers', '-productVersion']),
processManager.runSync(<String>['sw_vers', '-buildVersion']), 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 _name = '${results[0].stdout.trim()} ${results[1].stdout
.trim()} ${results[2].stdout.trim()}'; .trim()} ${results[2].stdout.trim()}';
} }
......
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
import 'common.dart'; import 'context.dart';
import 'file_system.dart'; import 'file_system.dart';
import 'io.dart'; import 'io.dart';
import 'process_manager.dart'; import 'process_manager.dart';
...@@ -98,379 +98,6 @@ Future<void> runShutdownHooks() async { ...@@ -98,379 +98,6 @@ Future<void> runShutdownHooks() async {
printTrace('Shutdown hooks complete'); 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 { class ProcessExit implements Exception {
ProcessExit(this.exitCode, {this.immediate = false}); ProcessExit(this.exitCode, {this.immediate = false});
...@@ -516,3 +143,382 @@ class RunResult { ...@@ -516,3 +143,382 @@ class RunResult {
); );
} }
} }
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');
}
}
}
...@@ -9,6 +9,7 @@ import '../base/build.dart'; ...@@ -9,6 +9,7 @@ import '../base/build.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/version.dart'; import '../base/version.dart';
...@@ -142,14 +143,18 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen ...@@ -142,14 +143,18 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
// Merge arch-specific App.frameworks into a multi-arch App.framework. // Merge arch-specific App.frameworks into a multi-arch App.framework.
if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) { 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(); fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync();
await runCheckedAsync(<String>[ await processUtils.run(
'lipo', <String>[
...dylibs, 'lipo',
'-create', ...dylibs,
'-output', fs.path.join(outputPath, 'App.framework', 'App'), '-create',
]); '-output', fs.path.join(outputPath, 'App.framework', 'App'),
],
throwOnError: true,
);
} else { } else {
status?.cancel(); status?.cancel();
exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async { exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async {
...@@ -173,10 +178,10 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen ...@@ -173,10 +178,10 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode'); throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
} }
} }
} on String catch (error) { } on ProcessException catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below. // Catch the String exceptions thrown from the `runSync` methods below.
status?.cancel(); status?.cancel();
printError(error); printError(error.toString());
return null; return null;
} }
status?.stop(); status?.stop();
......
...@@ -61,7 +61,7 @@ class ChannelCommand extends FlutterCommand { ...@@ -61,7 +61,7 @@ class ChannelCommand extends FlutterCommand {
showAll = showAll || currentChannel != currentBranch; showAll = showAll || currentChannel != currentBranch;
printStatus('Flutter channels:'); printStatus('Flutter channels:');
final int result = await runCommandAndStreamOutput( final int result = await processUtils.stream(
<String>['git', 'branch', '-r'], <String>['git', 'branch', '-r'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
mapFunction: (String line) { mapFunction: (String line) {
...@@ -111,28 +111,28 @@ class ChannelCommand extends FlutterCommand { ...@@ -111,28 +111,28 @@ class ChannelCommand extends FlutterCommand {
static Future<void> _checkout(String branchName) async { static Future<void> _checkout(String branchName) async {
// Get latest refs from upstream. // Get latest refs from upstream.
int result = await runCommandAndStreamOutput( int result = await processUtils.stream(
<String>['git', 'fetch'], <String>['git', 'fetch'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
prefix: 'git: ', prefix: 'git: ',
); );
if (result == 0) { if (result == 0) {
result = await runCommandAndStreamOutput( result = await processUtils.stream(
<String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'], <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
prefix: 'git: ', prefix: 'git: ',
); );
if (result == 0) { if (result == 0) {
// branch already exists, try just switching to it // branch already exists, try just switching to it
result = await runCommandAndStreamOutput( result = await processUtils.stream(
<String>['git', 'checkout', branchName, '--'], <String>['git', 'checkout', branchName, '--'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
prefix: 'git: ', prefix: 'git: ',
); );
} else { } else {
// branch does not exist, we have to create it // branch does not exist, we have to create it
result = await runCommandAndStreamOutput( result = await processUtils.stream(
<String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'], <String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
prefix: 'git: ', prefix: 'git: ',
......
...@@ -292,7 +292,7 @@ Future<void> _runTests(List<String> testArgs, String observatoryUri) async { ...@@ -292,7 +292,7 @@ Future<void> _runTests(List<String> testArgs, String observatoryUri) async {
PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(PackageMap.globalPackagesPath)); PackageMap.globalPackagesPath = fs.path.normalize(fs.path.absolute(PackageMap.globalPackagesPath));
final String dartVmPath = fs.path.join(dartSdkPath, 'bin', 'dart'); final String dartVmPath = fs.path.join(dartSdkPath, 'bin', 'dart');
final int result = await runCommandAndStreamOutput( final int result = await processUtils.stream(
<String>[ <String>[
dartVmPath, dartVmPath,
...dartVmFlags, ...dartVmFlags,
......
...@@ -76,9 +76,10 @@ class FormatCommand extends FlutterCommand { ...@@ -76,9 +76,10 @@ class FormatCommand extends FlutterCommand {
...argResults.rest, ...argResults.rest,
]; ];
final int result = await runCommandAndStreamOutput(command); final int result = await processUtils.stream(command);
if (result != 0) if (result != 0) {
throwToolExit('Formatting failed: $result', exitCode: result); throwToolExit('Formatting failed: $result', exitCode: result);
}
return null; return null;
} }
......
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/process.dart';
import '../base/process_manager.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -284,7 +283,7 @@ class ArtifactUnpacker { ...@@ -284,7 +283,7 @@ class ArtifactUnpacker {
_deleteFrameworkIfPresent( _deleteFrameworkIfPresent(
fs.path.join(targetDirectory, fs.path.basename(frameworkPath))); fs.path.join(targetDirectory, fs.path.basename(frameworkPath)));
final ProcessResult result = processManager final RunResult result = processUtils
.runSync(<String>['cp', '-R', frameworkPath, targetDirectory]); .runSync(<String>['cp', '-R', frameworkPath, targetDirectory]);
if (result.exitCode != 0) { if (result.exitCode != 0) {
throw Exception( throw Exception(
......
...@@ -121,7 +121,7 @@ class UpgradeCommandRunner { ...@@ -121,7 +121,7 @@ class UpgradeCommandRunner {
} }
Future<void> flutterUpgradeContinue() async { Future<void> flutterUpgradeContinue() async {
final int code = await runCommandAndStreamOutput( final int code = await processUtils.stream(
<String>[ <String>[
fs.path.join('bin', 'flutter'), fs.path.join('bin', 'flutter'),
'upgrade', 'upgrade',
...@@ -146,9 +146,11 @@ class UpgradeCommandRunner { ...@@ -146,9 +146,11 @@ class UpgradeCommandRunner {
Future<bool> hasUncomittedChanges() async { Future<bool> hasUncomittedChanges() async {
try { try {
final RunResult result = await runCheckedAsync(<String>[ final RunResult result = await processUtils.run(
'git', 'status', '-s' <String>['git', 'status', '-s'],
], workingDirectory: Cache.flutterRoot); throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
return result.stdout.trim().isNotEmpty; return result.stdout.trim().isNotEmpty;
} on ProcessException catch (error) { } on ProcessException catch (error) {
throwToolExit( throwToolExit(
...@@ -167,9 +169,11 @@ class UpgradeCommandRunner { ...@@ -167,9 +169,11 @@ class UpgradeCommandRunner {
/// Exits tool if there is no upstream. /// Exits tool if there is no upstream.
Future<void> verifyUpstreamConfigured() async { Future<void> verifyUpstreamConfigured() async {
try { try {
await runCheckedAsync(<String>[ await processUtils.run(
'git', 'rev-parse', '@{u}', <String>[ 'git', 'rev-parse', '@{u}'],
], workingDirectory: Cache.flutterRoot); throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
} catch (e) { } catch (e) {
throwToolExit( throwToolExit(
'Unable to upgrade Flutter: no origin repository configured. ' 'Unable to upgrade Flutter: no origin repository configured. '
...@@ -191,9 +195,11 @@ class UpgradeCommandRunner { ...@@ -191,9 +195,11 @@ class UpgradeCommandRunner {
tag = 'v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}'; tag = 'v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}';
} }
try { try {
await runCheckedAsync(<String>[ await processUtils.run(
'git', 'reset', '--hard', tag, <String>['git', 'reset', '--hard', tag],
], workingDirectory: Cache.flutterRoot); throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
} on ProcessException catch (error) { } on ProcessException catch (error) {
throwToolExit( throwToolExit(
'Unable to upgrade Flutter: The tool could not update to the version $tag. ' 'Unable to upgrade Flutter: The tool could not update to the version $tag. '
...@@ -218,7 +224,7 @@ class UpgradeCommandRunner { ...@@ -218,7 +224,7 @@ class UpgradeCommandRunner {
/// If there haven't been any hot fixes or local changes, this is equivalent /// If there haven't been any hot fixes or local changes, this is equivalent
/// to a fast-forward. /// to a fast-forward.
Future<void> attemptFastForward() async { Future<void> attemptFastForward() async {
final int code = await runCommandAndStreamOutput( final int code = await processUtils.stream(
<String>['git', 'pull', '--ff'], <String>['git', 'pull', '--ff'],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
mapFunction: (String line) => matchesGitLine(line) ? null : line, mapFunction: (String line) => matchesGitLine(line) ? null : line,
...@@ -236,7 +242,7 @@ class UpgradeCommandRunner { ...@@ -236,7 +242,7 @@ class UpgradeCommandRunner {
Future<void> precacheArtifacts() async { Future<void> precacheArtifacts() async {
printStatus(''); printStatus('');
printStatus('Upgrading engine...'); printStatus('Upgrading engine...');
final int code = await runCommandAndStreamOutput( final int code = await processUtils.stream(
<String>[ <String>[
fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache', fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache',
], ],
...@@ -263,7 +269,7 @@ class UpgradeCommandRunner { ...@@ -263,7 +269,7 @@ class UpgradeCommandRunner {
Future<void> runDoctor() async { Future<void> runDoctor() async {
printStatus(''); printStatus('');
printStatus('Running flutter doctor...'); printStatus('Running flutter doctor...');
await runCommandAndStreamOutput( await processUtils.stream(
<String>[ <String>[
fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor', fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor',
], ],
......
...@@ -37,8 +37,9 @@ class VersionCommand extends FlutterCommand { ...@@ -37,8 +37,9 @@ class VersionCommand extends FlutterCommand {
Future<List<String>> getTags() async { Future<List<String>> getTags() async {
RunResult runResult; RunResult runResult;
try { try {
runResult = await runCheckedAsync( runResult = await processUtils.run(
<String>['git', 'tag', '-l', 'v*', '--sort=-creatordate'], <String>['git', 'tag', '-l', 'v*', '--sort=-creatordate'],
throwOnError: true,
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
); );
} on ProcessException catch (error) { } on ProcessException catch (error) {
...@@ -83,8 +84,9 @@ class VersionCommand extends FlutterCommand { ...@@ -83,8 +84,9 @@ class VersionCommand extends FlutterCommand {
} }
try { try {
await runCheckedAsync( await processUtils.run(
<String>['git', 'checkout', 'v$version'], <String>['git', 'checkout', 'v$version'],
throwOnError: true,
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
); );
} catch (e) { } catch (e) {
...@@ -101,7 +103,7 @@ class VersionCommand extends FlutterCommand { ...@@ -101,7 +103,7 @@ class VersionCommand extends FlutterCommand {
// if necessary. // if necessary.
printStatus(''); printStatus('');
printStatus('Downloading engine...'); printStatus('Downloading engine...');
int code = await runCommandAndStreamOutput(<String>[ int code = await processUtils.stream(<String>[
fs.path.join('bin', 'flutter'), fs.path.join('bin', 'flutter'),
'--no-color', '--no-color',
'precache', 'precache',
...@@ -128,7 +130,7 @@ class VersionCommand extends FlutterCommand { ...@@ -128,7 +130,7 @@ class VersionCommand extends FlutterCommand {
// Run a doctor check in case system requirements have changed. // Run a doctor check in case system requirements have changed.
printStatus(''); printStatus('');
printStatus('Running flutter doctor...'); printStatus('Running flutter doctor...');
code = await runCommandAndStreamOutput( code = await processUtils.stream(
<String>[ <String>[
fs.path.join('bin', 'flutter'), fs.path.join('bin', 'flutter'),
'doctor', 'doctor',
......
...@@ -19,6 +19,7 @@ import 'base/io.dart'; ...@@ -19,6 +19,7 @@ import 'base/io.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'base/platform.dart'; import 'base/platform.dart';
import 'base/process.dart';
import 'base/time.dart'; import 'base/time.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
import 'base/utils.dart'; import 'base/utils.dart';
...@@ -101,6 +102,7 @@ Future<T> runInContext<T>( ...@@ -101,6 +102,7 @@ Future<T> runInContext<T>(
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(), Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
MacOSWorkflow: () => const MacOSWorkflow(), MacOSWorkflow: () => const MacOSWorkflow(),
OperatingSystemUtils: () => OperatingSystemUtils(), OperatingSystemUtils: () => OperatingSystemUtils(),
ProcessUtils: () => ProcessUtils(),
SimControl: () => SimControl(), SimControl: () => SimControl(),
Stdio: () => const Stdio(), Stdio: () => const Stdio(),
SystemClock: () => const SystemClock(), SystemClock: () => const SystemClock(),
......
...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; ...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart' as io;
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
...@@ -156,22 +157,25 @@ Future<void> pub( ...@@ -156,22 +157,25 @@ Future<void> pub(
int code; int code;
while (true) { while (true) {
attempts += 1; attempts += 1;
code = await runCommandAndStreamOutput( code = await processUtils.stream(
_pubCommand(arguments), _pubCommand(arguments),
workingDirectory: directory, workingDirectory: directory,
mapFunction: filter, mapFunction: filter,
environment: _createPubEnvironment(context), 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; break;
}
printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...'); printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
await Future<void>.delayed(Duration(seconds: duration)); await Future<void>.delayed(Duration(seconds: duration));
if (duration < 64) if (duration < 64) {
duration *= 2; duration *= 2;
}
} }
assert(code != null); assert(code != null);
if (code != 0) if (code != 0) {
throwToolExit('$failureMessage ($code)', exitCode: code); throwToolExit('$failureMessage ($code)', exitCode: code);
}
} }
/// Runs pub in 'interactive' mode, directly piping the stdin stream of this /// Runs pub in 'interactive' mode, directly piping the stdin stream of this
...@@ -182,13 +186,26 @@ Future<void> pubInteractively( ...@@ -182,13 +186,26 @@ Future<void> pubInteractively(
String directory, String directory,
}) async { }) async {
Cache.releaseLockEarly(); Cache.releaseLockEarly();
final int code = await runInteractively( final io.Process process = await processUtils.start(
_pubCommand(arguments), _pubCommand(arguments),
workingDirectory: directory, workingDirectory: directory,
environment: _createPubEnvironment(PubContext.interactive), 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); throwToolExit('pub finished with exit code $code', exitCode: code);
}
} }
/// The command used for running pub. /// The command used for running pub.
......
...@@ -14,7 +14,7 @@ import 'base/file_system.dart'; ...@@ -14,7 +14,7 @@ import 'base/file_system.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'base/platform.dart'; import 'base/platform.dart';
import 'base/process_manager.dart'; import 'base/process.dart';
import 'base/terminal.dart'; import 'base/terminal.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
import 'base/utils.dart'; import 'base/utils.dart';
...@@ -607,7 +607,7 @@ class FlutterValidator extends DoctorValidator { ...@@ -607,7 +607,7 @@ class FlutterValidator extends DoctorValidator {
bool _genSnapshotRuns(String genSnapshotPath) { bool _genSnapshotRuns(String genSnapshotPath) {
const int kExpectedExitCode = 255; const int kExpectedExitCode = 255;
try { try {
return processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode; return processUtils.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
} catch (error) { } catch (error) {
return false; return false;
} }
......
...@@ -8,8 +8,7 @@ import 'dart:math' as math; ...@@ -8,8 +8,7 @@ import 'dart:math' as math;
import 'android/android_emulator.dart'; import 'android/android_emulator.dart';
import 'android/android_sdk.dart'; import 'android/android_sdk.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/io.dart' show ProcessResult; import 'base/process.dart';
import 'base/process_manager.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'ios/ios_emulators.dart'; import 'ios/ios_emulators.dart';
...@@ -118,7 +117,7 @@ class EmulatorManager { ...@@ -118,7 +117,7 @@ class EmulatorManager {
'-k', sdkId, '-k', sdkId,
'-d', device, '-d', device,
]; ];
final ProcessResult runResult = processManager.runSync(args, final RunResult runResult = processUtils.runSync(args,
environment: androidSdk?.sdkManagerEnv); environment: androidSdk?.sdkManagerEnv);
return CreateEmulatorResult( return CreateEmulatorResult(
name, name,
...@@ -139,10 +138,11 @@ class EmulatorManager { ...@@ -139,10 +138,11 @@ class EmulatorManager {
'device', 'device',
'-c', '-c',
]; ];
final ProcessResult runResult = processManager.runSync(args, final RunResult runResult = processUtils.runSync(args,
environment: androidSdk?.sdkManagerEnv); environment: androidSdk?.sdkManagerEnv);
if (runResult.exitCode != 0) if (runResult.exitCode != 0) {
return null; return null;
}
final List<String> availableDevices = runResult.stdout final List<String> availableDevices = runResult.stdout
.split('\n') .split('\n')
...@@ -165,7 +165,7 @@ class EmulatorManager { ...@@ -165,7 +165,7 @@ class EmulatorManager {
'avd', 'avd',
'-n', 'temp', '-n', 'temp',
]; ];
final ProcessResult runResult = processManager.runSync(args, final RunResult runResult = processUtils.runSync(args,
environment: androidSdk?.sdkManagerEnv); environment: androidSdk?.sdkManagerEnv);
// Get the list of IDs that match our criteria // Get the list of IDs that match our criteria
......
...@@ -32,7 +32,7 @@ class FuchsiaDevFinder { ...@@ -32,7 +32,7 @@ class FuchsiaDevFinder {
'list', 'list',
'-full' '-full'
]; ];
final RunResult result = await runAsync(command); final RunResult result = await processUtils.run(command);
if (result.exitCode != 0) { if (result.exitCode != 0) {
printError('dev_finder failed: ${result.stderr}'); printError('dev_finder failed: ${result.stderr}');
return null; return null;
...@@ -57,7 +57,7 @@ class FuchsiaDevFinder { ...@@ -57,7 +57,7 @@ class FuchsiaDevFinder {
'-device-limit', '1', '-device-limit', '1',
deviceName deviceName
]; ];
final RunResult result = await runAsync(command); final RunResult result = await processUtils.run(command);
if (result.exitCode != 0) { if (result.exitCode != 0) {
printError('dev_finder failed: ${result.stderr}'); printError('dev_finder failed: ${result.stderr}');
return null; return null;
......
...@@ -454,7 +454,7 @@ class FuchsiaDevice extends Device { ...@@ -454,7 +454,7 @@ class FuchsiaDevice extends Device {
throwToolExit('Cannot interact with device. No ssh config.\n' throwToolExit('Cannot interact with device. No ssh config.\n'
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.'); 'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
} }
return await runAsync(<String>[ return await processUtils.run(<String>[
'ssh', 'ssh',
'-F', '-F',
fuchsiaArtifacts.sshConfig.absolute.path, fuchsiaArtifacts.sshConfig.absolute.path,
......
...@@ -85,7 +85,7 @@ class FuchsiaKernelCompiler { ...@@ -85,7 +85,7 @@ class FuchsiaKernelCompiler {
fuchsiaArtifacts.kernelCompiler.path, fuchsiaArtifacts.kernelCompiler.path,
...flags, ...flags,
]; ];
final Process process = await runCommand(command); final Process process = await processUtils.start(command);
final Status status = logger.startProgress( final Status status = logger.startProgress(
'Building Fuchsia application...', 'Building Fuchsia application...',
timeout: null, timeout: null,
......
...@@ -118,7 +118,7 @@ class FuchsiaPM { ...@@ -118,7 +118,7 @@ class FuchsiaPM {
'-l', '-l',
'$host:$port', '$host:$port',
]; ];
final Process process = await runCommand(command); final Process process = await processUtils.start(command);
process.stdout process.stdout
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
...@@ -152,7 +152,7 @@ class FuchsiaPM { ...@@ -152,7 +152,7 @@ class FuchsiaPM {
throwToolExit('Fuchsia pm tool not found'); throwToolExit('Fuchsia pm tool not found');
} }
final List<String> command = <String>[fuchsiaArtifacts.pm.path] + args; 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; return result.exitCode == 0;
} }
} }
......
...@@ -96,8 +96,9 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({ ...@@ -96,8 +96,9 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
BuildableIOSApp iosApp, BuildableIOSApp iosApp,
}) async { }) async {
final Map<String, String> buildSettings = iosApp.project.buildSettings; final Map<String, String> buildSettings = iosApp.project.buildSettings;
if (buildSettings == null) if (buildSettings == null) {
return null; return null;
}
// If the user already has it set in the project build settings itself, // If the user already has it set in the project build settings itself,
// continue with that. // continue with that.
...@@ -114,16 +115,21 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({ ...@@ -114,16 +115,21 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
// If the user's environment is missing the tools needed to find and read // If the user's environment is missing the tools needed to find and read
// certificates, abandon. Tools should be pre-equipped on macOS. // 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; return null;
}
const List<String> findIdentityCommand = const List<String> findIdentityCommand =
<String>['security', 'find-identity', '-p', 'codesigning', '-v']; <String>['security', 'find-identity', '-p', 'codesigning', '-v'];
String findIdentityStdout; String findIdentityStdout;
try { try {
findIdentityStdout = runCheckedSync(findIdentityCommand); findIdentityStdout = (await processUtils.run(
} catch (error) { findIdentityCommand,
throwOnError: true,
)).stdout.trim();
} on ProcessException catch (error) {
printTrace('Unexpected failure from find-identity: $error.'); printTrace('Unexpected failure from find-identity: $error.');
return null; return null;
} }
...@@ -142,8 +148,9 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({ ...@@ -142,8 +148,9 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
final String signingIdentity = await _chooseSigningIdentity(validCodeSigningIdentities); final String signingIdentity = await _chooseSigningIdentity(validCodeSigningIdentities);
// If none are chosen, return null. // If none are chosen, return null.
if (signingIdentity == null) if (signingIdentity == null) {
return null; return null;
}
printStatus('Signing iOS app for device deployment using developer identity: "$signingIdentity"'); printStatus('Signing iOS app for device deployment using developer identity: "$signingIdentity"');
...@@ -153,20 +160,23 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({ ...@@ -153,20 +160,23 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
?.group(1); ?.group(1);
// If `security`'s output format changes, we'd have to update the above regex. // If `security`'s output format changes, we'd have to update the above regex.
if (signingCertificateId == null) if (signingCertificateId == null) {
return null; return null;
}
String signingCertificateStdout; String signingCertificateStdout;
try { try {
signingCertificateStdout = runCheckedSync( signingCertificateStdout = (await processUtils.run(
<String>['security', 'find-certificate', '-c', signingCertificateId, '-p'] <String>['security', 'find-certificate', '-c', signingCertificateId, '-p'],
); throwOnError: true,
} catch (error) { )).stdout.trim();
} on ProcessException catch (error) {
printTrace('Couldn\'t find the certificate: $error.'); printTrace('Couldn\'t find the certificate: $error.');
return null; 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(); await (opensslProcess.stdin..write(signingCertificateStdout)).close();
final String opensslOutput = await utf8.decodeStream(opensslProcess.stdout); final String opensslOutput = await utf8.decodeStream(opensslProcess.stdout);
......
...@@ -64,7 +64,7 @@ class IOSDeploy { ...@@ -64,7 +64,7 @@ class IOSDeploy {
iosDeployEnv['PATH'] = '/usr/bin:${iosDeployEnv['PATH']}'; iosDeployEnv['PATH'] = '/usr/bin:${iosDeployEnv['PATH']}';
iosDeployEnv.addEntries(<MapEntry<String, String>>[cache.dyLdLibEntry]); iosDeployEnv.addEntries(<MapEntry<String, String>>[cache.dyLdLibEntry]);
return await runCommandAndStreamOutput( return await processUtils.stream(
launchCommand, launchCommand,
mapFunction: _monitorInstallationFailure, mapFunction: _monitorInstallationFailure,
trace: true, trace: true,
...@@ -195,8 +195,9 @@ class IOSDevice extends Device { ...@@ -195,8 +195,9 @@ class IOSDevice extends Device {
Future<bool> isAppInstalled(ApplicationPackage app) async { Future<bool> isAppInstalled(ApplicationPackage app) async {
RunResult apps; RunResult apps;
try { try {
apps = await runCheckedAsync( apps = await processUtils.run(
<String>[_installerPath, '--list-apps'], <String>[_installerPath, '--list-apps'],
throwOnError: true,
environment: Map<String, String>.fromEntries( environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[cache.dyLdLibEntry], <MapEntry<String, String>>[cache.dyLdLibEntry],
), ),
...@@ -220,8 +221,9 @@ class IOSDevice extends Device { ...@@ -220,8 +221,9 @@ class IOSDevice extends Device {
} }
try { try {
await runCheckedAsync( await processUtils.run(
<String>[_installerPath, '-i', iosApp.deviceBundlePath], <String>[_installerPath, '-i', iosApp.deviceBundlePath],
throwOnError: true,
environment: Map<String, String>.fromEntries( environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[cache.dyLdLibEntry], <MapEntry<String, String>>[cache.dyLdLibEntry],
), ),
...@@ -236,8 +238,9 @@ class IOSDevice extends Device { ...@@ -236,8 +238,9 @@ class IOSDevice extends Device {
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async { Future<bool> uninstallApp(ApplicationPackage app) async {
try { try {
await runCheckedAsync( await processUtils.run(
<String>[_installerPath, '-U', app.id], <String>[_installerPath, '-U', app.id],
throwOnError: true,
environment: Map<String, String>.fromEntries( environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[cache.dyLdLibEntry], <MapEntry<String, String>>[cache.dyLdLibEntry],
), ),
...@@ -610,7 +613,7 @@ class _IOSDevicePortForwarder extends DevicePortForwarder { ...@@ -610,7 +613,7 @@ class _IOSDevicePortForwarder extends DevicePortForwarder {
while (!connected) { while (!connected) {
printTrace('attempting to forward device port $devicePort to host port $hostPort'); printTrace('attempting to forward device port $devicePort to host port $hostPort');
// Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID // Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
process = await runCommand( process = await processUtils.start(
<String>[ <String>[
device._iproxyPath, device._iproxyPath,
hostPort.toString(), hostPort.toString(),
......
...@@ -47,7 +47,7 @@ class IOSEmulator extends Emulator { ...@@ -47,7 +47,7 @@ class IOSEmulator extends Emulator {
.followedBy(<String>['-a', xcode.getSimulatorPath()]) .followedBy(<String>['-a', xcode.getSimulatorPath()])
.toList(); .toList();
final RunResult launchResult = await runAsync(args); final RunResult launchResult = await processUtils.run(args);
if (launchResult.exitCode != 0) { if (launchResult.exitCode != 0) {
printError('$launchResult'); printError('$launchResult');
return false; return false;
......
...@@ -107,7 +107,7 @@ class IMobileDevice { ...@@ -107,7 +107,7 @@ class IMobileDevice {
final String _idevicescreenshotPath; final String _idevicescreenshotPath;
bool get isInstalled { bool get isInstalled {
_isInstalled ??= exitsHappy( _isInstalled ??= processUtils.exitsHappySync(
<String>[ <String>[
_ideviceIdPath, _ideviceIdPath,
'-h' '-h'
...@@ -136,7 +136,7 @@ class IMobileDevice { ...@@ -136,7 +136,7 @@ class IMobileDevice {
final Map<String, String> executionEnv = Map<String, String>.fromEntries( final Map<String, String> executionEnv = Map<String, String>.fromEntries(
<MapEntry<String, String>>[cache.dyLdLibEntry] <MapEntry<String, String>>[cache.dyLdLibEntry]
); );
final ProcessResult ideviceResult = (await runAsync( final ProcessResult ideviceResult = (await processUtils.run(
<String>[ <String>[
_ideviceinfoPath, _ideviceinfoPath,
'-u', '-u',
...@@ -150,7 +150,7 @@ class IMobileDevice { ...@@ -150,7 +150,7 @@ class IMobileDevice {
} }
// If no device is attached, we're unable to detect any problems. Assume all is well. // 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>[ <String>[
_ideviceIdPath, _ideviceIdPath,
'-l', '-l',
...@@ -161,7 +161,7 @@ class IMobileDevice { ...@@ -161,7 +161,7 @@ class IMobileDevice {
_isWorking = true; _isWorking = true;
} else { } else {
// Check that we can look up the names of any attached devices. // Check that we can look up the names of any attached devices.
_isWorking = await exitsHappyAsync( _isWorking = await processUtils.exitsHappy(
<String>[_idevicenamePath], <String>[_idevicenamePath],
environment: executionEnv, environment: executionEnv,
); );
...@@ -229,7 +229,7 @@ class IMobileDevice { ...@@ -229,7 +229,7 @@ class IMobileDevice {
/// Starts `idevicesyslog` and returns the running process. /// Starts `idevicesyslog` and returns the running process.
Future<Process> startLogger(String deviceID) { Future<Process> startLogger(String deviceID) {
return runCommand( return processUtils.start(
<String>[ <String>[
_idevicesyslogPath, _idevicesyslogPath,
'-u', '-u',
...@@ -243,11 +243,12 @@ class IMobileDevice { ...@@ -243,11 +243,12 @@ class IMobileDevice {
/// Captures a screenshot to the specified outputFile. /// Captures a screenshot to the specified outputFile.
Future<void> takeScreenshot(File outputFile) { Future<void> takeScreenshot(File outputFile) {
return runCheckedAsync( return processUtils.run(
<String>[ <String>[
_idevicescreenshotPath, _idevicescreenshotPath,
outputFile.path outputFile.path
], ],
throwOnError: true,
environment: Map<String, String>.fromEntries( environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[cache.dyLdLibEntry] <MapEntry<String, String>>[cache.dyLdLibEntry]
), ),
...@@ -318,8 +319,9 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -318,8 +319,9 @@ Future<XcodeBuildResult> buildXcodeProject({
} }
Map<String, String> autoSigningConfigs; Map<String, String> autoSigningConfigs;
if (codesign && buildForDevice) if (codesign && buildForDevice) {
autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app); autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
}
// Before the build, all service definitions must be updated and the dylibs // 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. // copied over to a location that is suitable for Xcodebuild to find them.
...@@ -440,7 +442,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -440,7 +442,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.fastOperation); initialBuildStatus = logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.fastOperation);
final RunResult buildResult = await runAsync( final RunResult buildResult = await processUtils.run(
buildCommands, buildCommands,
workingDirectory: app.project.hostAppRoot.path, workingDirectory: app.project.hostAppRoot.path,
allowReentrantFlutter: true, allowReentrantFlutter: true,
...@@ -476,8 +478,9 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -476,8 +478,9 @@ Future<XcodeBuildResult> buildXcodeProject({
const Duration showBuildSettingsTimeout = Duration(minutes: 1); const Duration showBuildSettingsTimeout = Duration(minutes: 1);
Map<String, String> buildSettings; Map<String, String> buildSettings;
try { try {
final RunResult showBuildSettingsResult = await runCheckedAsync( final RunResult showBuildSettingsResult = await processUtils.run(
showBuildSettingsCommand, showBuildSettingsCommand,
throwOnError: true,
workingDirectory: app.project.hostAppRoot.path, workingDirectory: app.project.hostAppRoot.path,
timeout: showBuildSettingsTimeout, timeout: showBuildSettingsTimeout,
timeoutRetries: 1, timeoutRetries: 1,
...@@ -677,7 +680,10 @@ Future<void> _copyServiceFrameworks(List<Map<String, String>> services, Director ...@@ -677,7 +680,10 @@ Future<void> _copyServiceFrameworks(List<Map<String, String>> services, Director
continue; continue;
} }
// Shell out so permissions on the dylib are preserved. // 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,
);
} }
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -38,9 +39,12 @@ class PlistParser { ...@@ -38,9 +39,12 @@ class PlistParser {
final List<String> args = <String>[ final List<String> args = <String>[
executable, '-convert', 'json', '-o', '-', normalizedPlistPath, executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
]; ];
final String jsonContent = runCheckedSync(args); final String jsonContent = processUtils.runSync(
args,
throwOnError: true,
).stdout.trim();
return json.decode(jsonContent); return json.decode(jsonContent);
} catch (error) { } on ProcessException catch (error) {
printTrace('$error'); printTrace('$error');
return const <String, dynamic>{}; return const <String, dynamic>{};
} }
......
...@@ -130,7 +130,7 @@ class SimControl { ...@@ -130,7 +130,7 @@ class SimControl {
} }
Future<bool> isInstalled(String deviceId, String appId) { Future<bool> isInstalled(String deviceId, String appId) {
return exitsHappyAsync(<String>[ return processUtils.exitsHappy(<String>[
_xcrunPath, _xcrunPath,
'simctl', 'simctl',
'get_app_container', 'get_app_container',
...@@ -142,7 +142,10 @@ class SimControl { ...@@ -142,7 +142,10 @@ class SimControl {
Future<RunResult> install(String deviceId, String appPath) { Future<RunResult> install(String deviceId, String appPath) {
Future<RunResult> result; Future<RunResult> result;
try { try {
result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]); result = processUtils.run(
<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath],
throwOnError: true,
);
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to install $appPath on $deviceId:\n$exception'); throwToolExit('Unable to install $appPath on $deviceId:\n$exception');
} }
...@@ -152,7 +155,10 @@ class SimControl { ...@@ -152,7 +155,10 @@ class SimControl {
Future<RunResult> uninstall(String deviceId, String appId) { Future<RunResult> uninstall(String deviceId, String appId) {
Future<RunResult> result; Future<RunResult> result;
try { try {
result = runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]); result = processUtils.run(
<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId],
throwOnError: true,
);
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to uninstall $appId from $deviceId:\n$exception'); throwToolExit('Unable to uninstall $appId from $deviceId:\n$exception');
} }
...@@ -162,14 +168,17 @@ class SimControl { ...@@ -162,14 +168,17 @@ class SimControl {
Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) { Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) {
Future<RunResult> result; Future<RunResult> result;
try { try {
result = runCheckedAsync(<String>[ result = processUtils.run(
_xcrunPath, <String>[
'simctl', _xcrunPath,
'launch', 'simctl',
deviceId, 'launch',
appIdentifier, deviceId,
...?launchArgs, appIdentifier,
]); ...?launchArgs,
],
throwOnError: true,
);
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to launch $appIdentifier on $deviceId:\n$exception'); throwToolExit('Unable to launch $appIdentifier on $deviceId:\n$exception');
} }
...@@ -178,7 +187,10 @@ class SimControl { ...@@ -178,7 +187,10 @@ class SimControl {
Future<void> takeScreenshot(String deviceId, String outputPath) async { Future<void> takeScreenshot(String deviceId, String outputPath) async {
try { 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) { } on ProcessException catch (exception) {
throwToolExit('Unable to take screenshot of $deviceId:\n$exception'); throwToolExit('Unable to take screenshot of $deviceId:\n$exception');
} }
...@@ -518,20 +530,22 @@ class IOSSimulator extends Device { ...@@ -518,20 +530,22 @@ class IOSSimulator extends Device {
/// Launches the device log reader process on the host. /// Launches the device log reader process on the host.
Future<Process> launchDeviceLogTool(IOSSimulator device) async { Future<Process> launchDeviceLogTool(IOSSimulator device) async {
// Versions of iOS prior to iOS 11 log to the simulator syslog file. // Versions of iOS prior to iOS 11 log to the simulator syslog file.
if (await device.sdkMajorVersion < 11) if (await device.sdkMajorVersion < 11) {
return runCommand(<String>['tail', '-n', '0', '-F', device.logFilePath]); return processUtils.start(<String>['tail', '-n', '0', '-F', device.logFilePath]);
}
// For iOS 11 and above, use /usr/bin/log to tail process logs. // 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) // 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}"', 'script', '/dev/null', '/usr/bin/log', 'stream', '--style', 'syslog', '--predicate', 'processImagePath CONTAINS "${device.id}"',
]); ]);
} }
Future<Process> launchSystemLogTool(IOSSimulator device) async { Future<Process> launchSystemLogTool(IOSSimulator device) async {
// Versions of iOS prior to 11 tail the simulator syslog file. // Versions of iOS prior to 11 tail the simulator syslog file.
if (await device.sdkMajorVersion < 11) if (await device.sdkMajorVersion < 11) {
return runCommand(<String>['tail', '-n', '0', '-F', '/private/var/log/system.log']); 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. // For iOS 11 and later, all relevant detail is in the device log.
return null; return null;
......
...@@ -14,7 +14,6 @@ import '../base/logger.dart'; ...@@ -14,7 +14,6 @@ import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -211,7 +210,9 @@ class XcodeProjectInterpreter { ...@@ -211,7 +210,9 @@ class XcodeProjectInterpreter {
return; return;
} }
try { try {
final ProcessResult result = processManager.runSync(<String>[_executable, '-version']); final RunResult result = processUtils.runSync(
<String>[_executable, '-version'],
);
if (result.exitCode != 0) { if (result.exitCode != 0) {
return; return;
} }
...@@ -255,16 +256,20 @@ class XcodeProjectInterpreter { ...@@ -255,16 +256,20 @@ class XcodeProjectInterpreter {
/// version below. /// version below.
Map<String, String> getBuildSettings(String projectPath, String target) { Map<String, String> getBuildSettings(String projectPath, String target) {
try { try {
final String out = runCheckedSync(<String>[ final String out = processUtils.runSync(
_executable, <String>[
'-project', _executable,
fs.path.absolute(projectPath), '-project',
'-target', fs.path.absolute(projectPath),
target, '-target',
'-showBuildSettings', target,
], workingDirectory: projectPath); '-showBuildSettings',
],
throwOnError: true,
workingDirectory: projectPath,
).stdout.trim();
return parseXcodeBuildSettings(out); return parseXcodeBuildSettings(out);
} catch(error) { } on ProcessException catch (error) {
printTrace('Unexpected failure to get the build settings: $error.'); printTrace('Unexpected failure to get the build settings: $error.');
return const <String, String>{}; return const <String, String>{};
} }
...@@ -291,8 +296,9 @@ class XcodeProjectInterpreter { ...@@ -291,8 +296,9 @@ class XcodeProjectInterpreter {
// showBuildSettings is reported to ocassionally timeout. Here, we give it // showBuildSettings is reported to ocassionally timeout. Here, we give it
// a lot of wiggle room (locally on Flutter Gallery, this takes ~1s). // a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
// When there is a timeout, we retry once. // When there is a timeout, we retry once.
final RunResult result = await runCheckedAsync( final RunResult result = await processUtils.run(
showBuildSettingsCommand, showBuildSettingsCommand,
throwOnError: true,
workingDirectory: projectPath, workingDirectory: projectPath,
timeout: timeout, timeout: timeout,
timeoutRetries: 1, timeoutRetries: 1,
...@@ -313,7 +319,7 @@ class XcodeProjectInterpreter { ...@@ -313,7 +319,7 @@ class XcodeProjectInterpreter {
} }
void cleanWorkspace(String workspacePath, String scheme) { void cleanWorkspace(String workspacePath, String scheme) {
runSync(<String>[ processUtils.runSync(<String>[
_executable, _executable,
'-workspace', '-workspace',
workspacePath, workspacePath,
...@@ -325,11 +331,15 @@ class XcodeProjectInterpreter { ...@@ -325,11 +331,15 @@ class XcodeProjectInterpreter {
} }
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async { Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
final RunResult result = await runCheckedAsync(<String>[ final RunResult result = await processUtils.run(
_executable, <String>[
'-list', _executable,
if (projectFilename != null) ...<String>['-project', projectFilename], '-list',
], workingDirectory: projectPath); if (projectFilename != null) ...<String>['-project', projectFilename],
],
throwOnError: true,
workingDirectory: projectPath,
);
return XcodeProjectInfo.fromXcodeBuildOutput(result.toString()); return XcodeProjectInfo.fromXcodeBuildOutput(result.toString());
} }
} }
......
...@@ -67,10 +67,11 @@ class CocoaPods { ...@@ -67,10 +67,11 @@ class CocoaPods {
String get cocoaPodsMinimumVersion => '1.6.0'; String get cocoaPodsMinimumVersion => '1.6.0';
String get cocoaPodsRecommendedVersion => '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 { 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; return result.exitCode == 0 ? result.stdout.trim() : null;
}, onError: (dynamic _) => null); }, onError: (dynamic _) => null);
return _versionText; return _versionText;
......
...@@ -10,7 +10,6 @@ import '../base/file_system.dart'; ...@@ -10,7 +10,6 @@ import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart';
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
const int kXcodeRequiredVersionMajor = 9; const int kXcodeRequiredVersionMajor = 9;
...@@ -25,7 +24,9 @@ class Xcode { ...@@ -25,7 +24,9 @@ class Xcode {
String get xcodeSelectPath { String get xcodeSelectPath {
if (_xcodeSelectPath == null) { if (_xcodeSelectPath == null) {
try { 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 { } on ProcessException {
// Ignored, return null below. // Ignored, return null below.
} on ArgumentError { } on ArgumentError {
...@@ -52,13 +53,16 @@ class Xcode { ...@@ -52,13 +53,16 @@ class Xcode {
bool get eulaSigned { bool get eulaSigned {
if (_eulaSigned == null) { if (_eulaSigned == null) {
try { try {
final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']); final RunResult result = processUtils.runSync(
if (result.stdout != null && result.stdout.contains('license')) <String>['/usr/bin/xcrun', 'clang'],
);
if (result.stdout != null && result.stdout.contains('license')) {
_eulaSigned = false; _eulaSigned = false;
else if (result.stderr != null && result.stderr.contains('license')) } else if (result.stderr != null && result.stderr.contains('license')) {
_eulaSigned = false; _eulaSigned = false;
else } else {
_eulaSigned = true; _eulaSigned = true;
}
} on ProcessException { } on ProcessException {
_eulaSigned = false; _eulaSigned = false;
} }
...@@ -74,7 +78,9 @@ class Xcode { ...@@ -74,7 +78,9 @@ class Xcode {
try { try {
// This command will error if additional components need to be installed in // This command will error if additional components need to be installed in
// xcode 9.2 and above. // 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 == ''; _isSimctlInstalled = result.stderr == null || result.stderr == '';
} on ProcessException { } on ProcessException {
_isSimctlInstalled = false; _isSimctlInstalled = false;
...@@ -94,16 +100,23 @@ class Xcode { ...@@ -94,16 +100,23 @@ class Xcode {
} }
Future<RunResult> cc(List<String> args) { 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) { 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 { Future<String> iPhoneSdkLocation() async {
final RunResult runResult = await runCheckedAsync( final RunResult runResult = await processUtils.run(
<String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'], <String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'],
throwOnError: true,
); );
if (runResult.exitCode != 0) { if (runResult.exitCode != 0) {
throwToolExit('Could not find iPhone SDK location: ${runResult.stderr}'); throwToolExit('Could not find iPhone SDK location: ${runResult.stderr}');
......
...@@ -11,7 +11,7 @@ import '../base/io.dart'; ...@@ -11,7 +11,7 @@ import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process_manager.dart'; import '../base/process.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../globals.dart'; import '../globals.dart';
import '../vmservice.dart'; import '../vmservice.dart';
...@@ -150,7 +150,7 @@ class CoverageCollector extends TestWatcher { ...@@ -150,7 +150,7 @@ class CoverageCollector extends TestWatcher {
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_test_coverage.'); final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_test_coverage.');
try { try {
final File sourceFile = coverageFile.copySync(fs.path.join(tempDir.path, 'lcov.source.info')); 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', 'lcov',
'--add-tracefile', baseCoverageData, '--add-tracefile', baseCoverageData,
'--add-tracefile', sourceFile.path, '--add-tracefile', sourceFile.path,
......
...@@ -538,7 +538,10 @@ String _runSync(List<String> command, { bool lenient = true }) { ...@@ -538,7 +538,10 @@ String _runSync(List<String> command, { bool lenient = true }) {
} }
String _runGit(String command) { 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 /// Runs [command] in the root of the Flutter installation and returns the
......
...@@ -6,7 +6,7 @@ import '../base/context.dart'; ...@@ -6,7 +6,7 @@ import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process_manager.dart'; import '../base/process.dart';
import '../convert.dart'; import '../convert.dart';
VisualStudio get visualStudio => context.get<VisualStudio>(); VisualStudio get visualStudio => context.get<VisualStudio>();
...@@ -167,7 +167,7 @@ class VisualStudio { ...@@ -167,7 +167,7 @@ class VisualStudio {
'-utf8', '-utf8',
'-latest', '-latest',
]; ];
final ProcessResult whereResult = processManager.runSync(<String>[ final RunResult whereResult = processUtils.runSync(<String>[
_vswherePath, _vswherePath,
...defaultArguments, ...defaultArguments,
...?additionalArguments, ...?additionalArguments,
......
...@@ -8,7 +8,7 @@ import '../application_package.dart'; ...@@ -8,7 +8,7 @@ import '../application_package.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process_manager.dart'; import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../desktop.dart'; import '../desktop.dart';
import '../device.dart'; import '../device.dart';
...@@ -88,7 +88,7 @@ class WindowsDevice extends Device { ...@@ -88,7 +88,7 @@ class WindowsDevice extends Device {
); );
} }
await stopApp(package); await stopApp(package);
final Process process = await processManager.start(<String>[ final Process process = await processUtils.start(<String>[
package.executable(debuggingOptions?.buildInfo?.mode) package.executable(debuggingOptions?.buildInfo?.mode)
]); ]);
if (debuggingOptions?.buildInfo?.isRelease == true) { if (debuggingOptions?.buildInfo?.isRelease == true) {
...@@ -114,7 +114,9 @@ class WindowsDevice extends Device { ...@@ -114,7 +114,9 @@ class WindowsDevice extends Device {
if (process == null) { if (process == null) {
return false; 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; return result.exitCode == 0;
} }
...@@ -163,7 +165,9 @@ final RegExp _whitespace = RegExp(r'\s+'); ...@@ -163,7 +165,9 @@ final RegExp _whitespace = RegExp(r'\s+');
@visibleForTesting @visibleForTesting
List<String> runningProcess(String processName) { List<String> runningProcess(String processName) {
// TODO(jonahwilliams): find a way to do this without powershell. // 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) { if (result.exitCode != 0) {
return null; return null;
} }
......
...@@ -87,7 +87,7 @@ void main() { ...@@ -87,7 +87,7 @@ void main() {
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'), environment: anyNamed('environment'),
), ),
).thenReturn(ProcessResult(0, 0, _aaptDataWithDefaultEnabledAndMainLauncherActivity, null)); ).thenReturn(ProcessResult(0, 0, _aaptDataWithDefaultEnabledAndMainLauncherActivity, ''));
final ApplicationPackage applicationPackage = await ApplicationPackageFactory.instance.getPackageForPlatform( final ApplicationPackage applicationPackage = await ApplicationPackageFactory.instance.getPackageForPlatform(
TargetPlatform.android_arm, TargetPlatform.android_arm,
......
...@@ -26,10 +26,11 @@ void main() { ...@@ -26,10 +26,11 @@ void main() {
mockProcessManager = PlainMockProcessManager(); mockProcessManager = PlainMockProcessManager();
}); });
testUsingContext('runCheckedAsync exceptions should be ProcessException objects', () async { testUsingContext('runAsync throwOnError: exceptions should be ProcessException objects', () async {
when(mockProcessManager.run(<String>['false'])) when(mockProcessManager.run(<String>['false'])).thenAnswer(
.thenAnswer((Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', ''))); (Invocation invocation) => Future<ProcessResult>.value(ProcessResult(0, 1, '', '')));
expect(() async => await runCheckedAsync(<String>['false']), throwsA(isInstanceOf<ProcessException>())); expect(() async => await processUtils.run(<String>['false'], throwOnError: true),
throwsA(isInstanceOf<ProcessException>()));
}, overrides: <Type, Generator>{ProcessManager: () => mockProcessManager}); }, overrides: <Type, Generator>{ProcessManager: () => mockProcessManager});
}); });
...@@ -86,7 +87,7 @@ void main() { ...@@ -86,7 +87,7 @@ void main() {
testUsingContext('Command output is not wrapped.', () async { testUsingContext('Command output is not wrapped.', () async {
final List<String> testString = <String>['0123456789' * 10]; final List<String> testString = <String>['0123456789' * 10];
mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString); mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString);
await runCommandAndStreamOutput(<String>['command']); await processUtils.stream(<String>['command']);
expect(mockLogger.statusText, equals('${testString[0]}\n')); expect(mockLogger.statusText, equals('${testString[0]}\n'));
expect(mockLogger.errorText, equals('${testString[0]}\n')); expect(mockLogger.errorText, equals('${testString[0]}\n'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -97,22 +98,82 @@ void main() { ...@@ -97,22 +98,82 @@ void main() {
}); });
}); });
group('runAsync timeout and retry', () { group('run', () {
const Duration delay = Duration(seconds: 2); const Duration delay = Duration(seconds: 2);
MockProcessManager flakyProcessManager; MockProcessManager flakyProcessManager;
ProcessManager mockProcessManager;
setUp(() { setUp(() {
// MockProcessManager has an implementation of start() that returns the // MockProcessManager has an implementation of start() that returns the
// result of processFactory. // result of processFactory.
flakyProcessManager = MockProcessManager(); flakyProcessManager = MockProcessManager();
mockProcessManager = MockProcessManager();
});
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('flaky process fails without retry', () async { 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( flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1, flakes: 1,
delay: delay, delay: delay,
); );
final RunResult result = await runAsync( final RunResult result = await processUtils.run(
<String>['dummy'], <String>['dummy'],
timeout: delay + const Duration(seconds: 1), timeout: delay + const Duration(seconds: 1),
); );
...@@ -121,12 +182,12 @@ void main() { ...@@ -121,12 +182,12 @@ void main() {
ProcessManager: () => flakyProcessManager, ProcessManager: () => flakyProcessManager,
}); });
testUsingContext('flaky process succeeds with retry', () async { testUsingContext(' flaky process succeeds with retry', () async {
flakyProcessManager.processFactory = flakyProcessFactory( flakyProcessManager.processFactory = flakyProcessFactory(
flakes: 1, flakes: 1,
delay: delay, delay: delay,
); );
final RunResult result = await runAsync( final RunResult result = await processUtils.run(
<String>['dummy'], <String>['dummy'],
timeout: delay - const Duration(milliseconds: 500), timeout: delay - const Duration(milliseconds: 500),
timeoutRetries: 1, timeoutRetries: 1,
...@@ -136,7 +197,7 @@ void main() { ...@@ -136,7 +197,7 @@ void main() {
ProcessManager: () => flakyProcessManager, 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>> flakyStderr = Completer<List<int>>();
final Completer<List<int>> flakyStdout = Completer<List<int>>(); final Completer<List<int>> flakyStdout = Completer<List<int>>();
flakyProcessManager.processFactory = flakyProcessFactory( flakyProcessManager.processFactory = flakyProcessFactory(
...@@ -153,7 +214,7 @@ void main() { ...@@ -153,7 +214,7 @@ void main() {
flakyStdout.complete(<int>[]); flakyStdout.complete(<int>[]);
return true; return true;
}); });
expect(() async => await runAsync( expect(() => processUtils.run(
<String>['dummy'], <String>['dummy'],
timeout: delay - const Duration(milliseconds: 500), timeout: delay - const Duration(milliseconds: 500),
timeoutRetries: 0, timeoutRetries: 0,
...@@ -162,6 +223,159 @@ void main() { ...@@ -162,6 +223,159 @@ void main() {
ProcessManager: () => flakyProcessManager, 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 {} class PlainMockProcessManager extends Mock implements ProcessManager {}
...@@ -59,8 +59,8 @@ void main() { ...@@ -59,8 +59,8 @@ void main() {
}); });
testUsingContext('No auto-sign if security or openssl not available', () async { testUsingContext('No auto-sign if security or openssl not available', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(<String>['which', 'security']))
.thenReturn(exitsFail); .thenAnswer((_) => Future<ProcessResult>.value(exitsFail));
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app); final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
expect(signingConfigs, isNull); expect(signingConfigs, isNull);
}, },
...@@ -69,15 +69,21 @@ void main() { ...@@ -69,15 +69,21 @@ void main() {
}); });
testUsingContext('No valid code signing certificates shows instructions', () async { testUsingContext('No valid code signing certificates shows instructions', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), 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'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(exitsHappy); )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
Map<String, String> signingConfigs; Map<String, String> signingConfigs;
...@@ -95,39 +101,45 @@ void main() { ...@@ -95,39 +101,45 @@ void main() {
}); });
testUsingContext('Test single identity and certificate organization works', () async { testUsingContext('Test single identity and certificate organization works', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), 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'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
''' '''
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)" 1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
1 valid identities found''', 1 valid identities found''',
'', '',
)); )));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'], <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockProcess = MockProcess(); final MockProcess mockProcess = MockProcess();
final MockStdIn mockStdIn = MockStdIn(); final MockStdIn mockStdIn = MockStdIn();
final MockStream mockStdErr = MockStream(); final MockStream mockStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess));
...@@ -156,39 +168,45 @@ void main() { ...@@ -156,39 +168,45 @@ void main() {
testUsingContext('Test single identity (Catalina format) and certificate organization works', () async { testUsingContext('Test single identity (Catalina format) and certificate organization works', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), 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'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
''' '''
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "Apple Development: Profile 1 (1111AAAA11)" 1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "Apple Development: Profile 1 (1111AAAA11)"
1 valid identities found''', 1 valid identities found''',
'', '',
)); )));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'], <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockProcess = MockProcess(); final MockProcess mockProcess = MockProcess();
final MockStdIn mockStdIn = MockStdIn(); final MockStdIn mockStdIn = MockStdIn();
final MockStream mockStdErr = MockStream(); final MockStream mockStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockProcess));
...@@ -222,15 +240,21 @@ void main() { ...@@ -222,15 +240,21 @@ void main() {
}); });
testUsingContext('Test multiple identity and certificate organization works', () async { testUsingContext('Test multiple identity and certificate organization works', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy);
when(mockProcessManager.runSync(
argThat(contains('find-identity')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
when(mockProcessManager.run(
<String>['which', 'openssl'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( 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 1, // pid
0, // exitCode 0, // exitCode
''' '''
...@@ -239,26 +263,26 @@ void main() { ...@@ -239,26 +263,26 @@ void main() {
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
3 valid identities found''', 3 valid identities found''',
'', '',
)); )));
mockTerminalStdInStream = mockTerminalStdInStream =
Stream<String>.fromFuture(Future<String>.value('3')); Stream<String>.fromFuture(Future<String>.value('3'));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'], <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockOpenSslProcess = MockProcess(); final MockProcess mockOpenSslProcess = MockProcess();
final MockStdIn mockOpenSslStdIn = MockStdIn(); final MockStdIn mockOpenSslStdIn = MockStdIn();
final MockStream mockOpenSslStdErr = MockStream(); final MockStream mockOpenSslStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
...@@ -298,15 +322,21 @@ void main() { ...@@ -298,15 +322,21 @@ void main() {
testUsingContext('Test multiple identity in machine mode works', () async { testUsingContext('Test multiple identity in machine mode works', () async {
testTerminal.usesTerminalUi = false; testTerminal.usesTerminalUi = false;
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy);
when(mockProcessManager.runSync(
argThat(contains('find-identity')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
when(mockProcessManager.run(
<String>['which', 'openssl'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( 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 1, // pid
0, // exitCode 0, // exitCode
''' '''
...@@ -315,26 +345,26 @@ void main() { ...@@ -315,26 +345,26 @@ void main() {
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
3 valid identities found''', 3 valid identities found''',
'', '',
)); )));
mockTerminalStdInStream = mockTerminalStdInStream =
Stream<String>.fromFuture(Future<String>.error(Exception('Cannot read from StdIn'))); Stream<String>.fromFuture(Future<String>.error(Exception('Cannot read from StdIn')));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'], <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockOpenSslProcess = MockProcess(); final MockProcess mockOpenSslProcess = MockProcess();
final MockStdIn mockOpenSslStdIn = MockStdIn(); final MockStdIn mockOpenSslStdIn = MockStdIn();
final MockStream mockOpenSslStdErr = MockStream(); final MockStream mockOpenSslStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
...@@ -367,15 +397,21 @@ void main() { ...@@ -367,15 +397,21 @@ void main() {
}); });
testUsingContext('Test saved certificate used', () async { testUsingContext('Test saved certificate used', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), 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'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
''' '''
...@@ -384,24 +420,24 @@ void main() { ...@@ -384,24 +420,24 @@ void main() {
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
3 valid identities found''', 3 valid identities found''',
'', '',
)); )));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'], <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockOpenSslProcess = MockProcess(); final MockProcess mockOpenSslProcess = MockProcess();
final MockStdIn mockOpenSslStdIn = MockStdIn(); final MockStdIn mockOpenSslStdIn = MockStdIn();
final MockStream mockOpenSslStdErr = MockStream(); final MockStream mockOpenSslStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
...@@ -438,15 +474,21 @@ void main() { ...@@ -438,15 +474,21 @@ void main() {
}); });
testUsingContext('Test invalid saved certificate shows error and prompts again', () async { testUsingContext('Test invalid saved certificate shows error and prompts again', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy);
when(mockProcessManager.runSync(
argThat(contains('find-identity')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
when(mockProcessManager.run(
<String>['which', 'openssl'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( 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 1, // pid
0, // exitCode 0, // exitCode
''' '''
...@@ -455,19 +497,19 @@ void main() { ...@@ -455,19 +497,19 @@ void main() {
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
3 valid identities found''', 3 valid identities found''',
'', '',
)); )));
mockTerminalStdInStream = mockTerminalStdInStream =
Stream<String>.fromFuture(Future<String>.value('3')); Stream<String>.fromFuture(Future<String>.value('3'));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'], <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
1, // pid 1, // pid
0, // exitCode 0, // exitCode
'This is a mock certificate', 'This is a mock certificate',
'', '',
)); )));
final MockProcess mockOpenSslProcess = MockProcess(); final MockProcess mockOpenSslProcess = MockProcess();
...@@ -475,7 +517,7 @@ void main() { ...@@ -475,7 +517,7 @@ void main() {
final MockStream mockOpenSslStdErr = MockStream(); final MockStream mockOpenSslStdErr = MockStream();
when(mockProcessManager.start( when(mockProcessManager.start(
argThat(contains('openssl')), argThat(contains('openssl')),
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess)); )).thenAnswer((Invocation invocation) => Future<Process>.value(mockOpenSslProcess));
...@@ -511,15 +553,23 @@ void main() { ...@@ -511,15 +553,23 @@ void main() {
}); });
testUsingContext('find-identity failure', () async { testUsingContext('find-identity failure', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), when(mockProcessManager.run(
environment: anyNamed('environment'), <String>['which', 'openssl'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult(0, 1, '', '')); 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); final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
expect(signingConfigs, isNull); expect(signingConfigs, isNull);
...@@ -531,15 +581,21 @@ void main() { ...@@ -531,15 +581,21 @@ void main() {
}); });
testUsingContext('find-certificate failure', () async { testUsingContext('find-certificate failure', () async {
when(mockProcessManager.runSync(<String>['which', 'security'])) when(mockProcessManager.run(
.thenReturn(exitsHappy); <String>['which', 'security'],
when(mockProcessManager.runSync(<String>['which', 'openssl'])) workingDirectory: anyNamed('workingDirectory'),
.thenReturn(exitsHappy); environment: anyNamed('environment'),
when(mockProcessManager.runSync( )).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
argThat(contains('find-identity')), when(mockProcessManager.run(
environment: anyNamed('environment'), <String>['which', 'openssl'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult( 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 1, // pid
0, // exitCode 0, // exitCode
''' '''
...@@ -548,14 +604,16 @@ void main() { ...@@ -548,14 +604,16 @@ void main() {
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
3 valid identities found''', 3 valid identities found''',
'', '',
)); )));
mockTerminalStdInStream = mockTerminalStdInStream =
Stream<String>.fromFuture(Future<String>.value('3')); Stream<String>.fromFuture(Future<String>.value('3'));
when(mockProcessManager.runSync( when(mockProcessManager.run(
<String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'], <String>['security', 'find-certificate', '-c', '3333CCCC33', '-p'],
environment: anyNamed('environment'), environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
)).thenReturn(ProcessResult(1, 1, '', '' )); )).thenAnswer((_) => Future<ProcessResult>.value(
ProcessResult(1, 1, '', '' ))
);
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app); final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
expect(signingConfigs, isNull); expect(signingConfigs, isNull);
......
...@@ -142,10 +142,13 @@ void main() { ...@@ -142,10 +142,13 @@ void main() {
final MockDirectory directory = MockDirectory(); final MockDirectory directory = MockDirectory();
when(mockFileSystem.directory(bundlePath)).thenReturn(directory); when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
when(directory.existsSync()).thenReturn(true); when(directory.existsSync()).thenReturn(true);
when(mockProcessManager.run(installArgs, environment: env)) when(mockProcessManager.run(
.thenAnswer( installArgs,
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')) workingDirectory: anyNamed('workingDirectory'),
); environment: env
)).thenAnswer(
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
);
when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture')) when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture'))
.thenAnswer((_) => Future<String>.value('arm64')); .thenAnswer((_) => Future<String>.value('arm64'));
...@@ -259,6 +262,21 @@ void main() { ...@@ -259,6 +262,21 @@ void main() {
return Future<ProcessResult>.value(ProcessResult(0, 0, '', '')); 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. // Deploy works.
when(mockIosDeploy.runApp( when(mockIosDeploy.runApp(
deviceId: anyNamed('deviceId'), deviceId: anyNamed('deviceId'),
......
...@@ -146,7 +146,10 @@ void main() { ...@@ -146,7 +146,10 @@ void main() {
}); });
testUsingOsxContext('build settings is empty when xcodebuild failed to get the build settings', () { 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, '', '')); .thenReturn(ProcessResult(0, 1, '', ''));
expect(xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{}); expect(xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{});
}); });
......
...@@ -69,18 +69,23 @@ void main() { ...@@ -69,18 +69,23 @@ void main() {
final String finalResponse = final String finalResponse =
json.encode(<Map<String, dynamic>>[response]); json.encode(<Map<String, dynamic>>[response]);
when<String>(result.stdout).thenReturn(finalResponse); when<String>(result.stdout).thenReturn(finalResponse);
when<String>(result.stderr).thenReturn('');
final List<String> requirementArguments = requiredComponents == null final List<String> requirementArguments = requiredComponents == null
? <String>[] ? <String>[]
: <String>['-requires', ...requiredComponents]; : <String>['-requires', ...requiredComponents];
when(mockProcessManager.runSync(<String>[ when(mockProcessManager.runSync(
vswherePath, <String>[
'-format', vswherePath,
'json', '-format',
'-utf8', 'json',
'-latest', '-utf8',
...?additionalArguments, '-latest',
...?requirementArguments, ...?additionalArguments,
])).thenAnswer((Invocation invocation) { ...?requirementArguments,
],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) {
return result; return result;
}); });
} }
...@@ -111,8 +116,11 @@ void main() { ...@@ -111,8 +116,11 @@ void main() {
}); });
testUsingContext('isInstalled returns false when vswhere is missing', () { testUsingContext('isInstalled returns false when vswhere is missing', () {
when(mockProcessManager.runSync(any)) when(mockProcessManager.runSync(
.thenThrow(const ProcessException('vswhere', <String>[])); any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(const ProcessException('vswhere', <String>[]));
visualStudio = VisualStudio(); visualStudio = VisualStudio();
expect(visualStudio.isInstalled, false); expect(visualStudio.isInstalled, false);
...@@ -123,8 +131,11 @@ void main() { ...@@ -123,8 +131,11 @@ void main() {
}); });
testUsingContext('vcvarsPath returns null when vswhere is missing', () { testUsingContext('vcvarsPath returns null when vswhere is missing', () {
when(mockProcessManager.runSync(any)) when(mockProcessManager.runSync(
.thenThrow(const ProcessException('vswhere', <String>[])); any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(const ProcessException('vswhere', <String>[]));
visualStudio = VisualStudio(); visualStudio = VisualStudio();
expect(visualStudio.vcvarsPath, isNull); expect(visualStudio.vcvarsPath, isNull);
...@@ -135,13 +146,24 @@ void main() { ...@@ -135,13 +146,24 @@ void main() {
}); });
testUsingContext('isInstalled returns false when vswhere returns non-zero', () { 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(); final MockProcessResult result = MockProcessResult();
when(result.exitCode).thenReturn(1); 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; return result;
}); });
when<String>(result.stdout).thenReturn('');
when<String>(result.stderr).thenReturn('');
visualStudio = VisualStudio(); visualStudio = VisualStudio();
expect(visualStudio.isInstalled, false); expect(visualStudio.isInstalled, false);
......
...@@ -26,18 +26,23 @@ void main() { ...@@ -26,18 +26,23 @@ void main() {
when(notWindows.isWindows).thenReturn(false); when(notWindows.isWindows).thenReturn(false);
when(notWindows.environment).thenReturn(const <String, String>{}); when(notWindows.environment).thenReturn(const <String, String>{});
when(mockProcessManager.runSync(<String>[ when(mockProcessManager.runSync(
'powershell', '-script="Get-CimInstance Win32_Process"' <String>['powershell', '-script="Get-CimInstance Win32_Process"'],
])).thenAnswer((Invocation invocation) { workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) {
// The flutter tool process is returned as output to the powershell script // The flutter tool process is returned as output to the powershell script
final MockProcessResult result = MockProcessResult(); final MockProcessResult result = MockProcessResult();
when(result.exitCode).thenReturn(0); when(result.exitCode).thenReturn(0);
when<String>(result.stdout).thenReturn('$pid $flutterToolBinary'); when<String>(result.stdout).thenReturn('$pid $flutterToolBinary');
when<String>(result.stderr).thenReturn('');
return result; return result;
}); });
when(mockProcessManager.run(<String>[ when(mockProcessManager.run(
'Taskkill', '/PID', '$pid', '/F' <String>['Taskkill', '/PID', '$pid', '/F'],
])).thenThrow(Exception('Flutter tool process has been killed')); workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(Exception('Flutter tool process has been killed'));
testUsingContext('defaults', () async { testUsingContext('defaults', () async {
final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo'); final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment