| // Copyright 2013 The Flutter Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:convert'; | |
| import 'dart:io' as io; | |
| import 'package:file/file.dart'; | |
| import 'core.dart'; | |
| /// A class used to run processes. | |
| /// | |
| /// We use this instead of directly running the process so it can be overridden | |
| /// in tests. | |
| class ProcessRunner { | |
| /// Creates a new process runner. | |
| const ProcessRunner(); | |
| /// Run the [executable] with [args] and stream output to stderr and stdout. | |
| /// | |
| /// The current working directory of [executable] can be overridden by | |
| /// passing [workingDir]. | |
| /// | |
| /// If [exitOnError] is set to `true`, then this will throw an error if | |
| /// the [executable] terminates with a non-zero exit code. | |
| /// | |
| /// Returns the exit code of the [executable]. | |
| Future<int> runAndStream( | |
| String executable, | |
| List<String> args, { | |
| Directory? workingDir, | |
| Map<String, String>? environment, | |
| bool exitOnError = false, | |
| }) async { | |
| print( | |
| 'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}', | |
| ); | |
| final io.Process process = await io.Process.start( | |
| executable, | |
| args, | |
| workingDirectory: workingDir?.path, | |
| environment: environment, | |
| mode: io.ProcessStartMode.inheritStdio, | |
| ); | |
| if (exitOnError && await process.exitCode != 0) { | |
| final String error = _getErrorString(executable, args, workingDir: workingDir); | |
| print('$error See above for details.'); | |
| throw ToolExit(await process.exitCode); | |
| } | |
| return process.exitCode; | |
| } | |
| /// Run the [executable] with [args]. | |
| /// | |
| /// The current working directory of [executable] can be overridden by | |
| /// passing [workingDir]. | |
| /// | |
| /// If [exitOnError] is set to `true`, then this will throw an error if | |
| /// the [executable] terminates with a non-zero exit code. | |
| /// Defaults to `false`. | |
| /// | |
| /// If [logOnError] is set to `true`, it will print a formatted message about the error. | |
| /// Defaults to `false` | |
| /// | |
| /// Returns the [io.ProcessResult] of the [executable]. | |
| Future<io.ProcessResult> run( | |
| String executable, | |
| List<String> args, { | |
| Directory? workingDir, | |
| Map<String, String>? environment, | |
| bool exitOnError = false, | |
| bool logOnError = false, | |
| Encoding stdoutEncoding = io.systemEncoding, | |
| Encoding stderrEncoding = io.systemEncoding, | |
| }) async { | |
| final io.ProcessResult result = await io.Process.run( | |
| executable, | |
| args, | |
| workingDirectory: workingDir?.path, | |
| environment: environment, | |
| stdoutEncoding: stdoutEncoding, | |
| stderrEncoding: stderrEncoding, | |
| ); | |
| if (result.exitCode != 0) { | |
| if (logOnError) { | |
| final String error = _getErrorString(executable, args, workingDir: workingDir); | |
| print('$error Stderr:\n${result.stdout}'); | |
| } | |
| if (exitOnError) { | |
| throw ToolExit(result.exitCode); | |
| } | |
| } | |
| return result; | |
| } | |
| /// Starts the [executable] with [args]. | |
| /// | |
| /// The current working directory of [executable] can be overridden by | |
| /// passing [workingDir]. | |
| /// | |
| /// Returns the started [io.Process]. | |
| Future<io.Process> start( | |
| String executable, | |
| List<String> args, { | |
| Directory? workingDirectory, | |
| }) async { | |
| final io.Process process = await io.Process.start( | |
| executable, | |
| args, | |
| workingDirectory: workingDirectory?.path, | |
| ); | |
| return process; | |
| } | |
| String _getErrorString(String executable, List<String> args, {Directory? workingDir}) { | |
| final workdir = workingDir == null ? '' : ' in ${workingDir.path}'; | |
| return 'ERROR: Unable to execute "$executable ${args.join(' ')}"$workdir.'; | |
| } | |
| } |