runProcess function

Future<RunProcessResult> runProcess({
  1. required Uri executable,
  2. List<String> arguments = const [],
  3. Uri? workingDirectory,
  4. Map<String, String>? environment,
  5. bool includeParentEnvironment = true,
  6. required Logger? logger,
  7. bool captureOutput = true,
  8. int expectedExitCode = 0,
  9. bool throwOnUnexpectedExitCode = false,
})

Runs a Process.

If logger is provided, stream stdout and stderr to it.

If captureOutput, captures stdout and stderr.

Implementation

Future<RunProcessResult> runProcess({
  required Uri executable,
  List<String> arguments = const [],
  Uri? workingDirectory,
  Map<String, String>? environment,
  bool includeParentEnvironment = true,
  required Logger? logger,
  bool captureOutput = true,
  int expectedExitCode = 0,
  bool throwOnUnexpectedExitCode = false,
}) async {
  if (Platform.isWindows && !includeParentEnvironment) {
    const winEnvKeys = [
      'SYSTEMROOT',
      'TEMP',
      'TMP',
    ];
    environment = {
      for (final winEnvKey in winEnvKeys)
        winEnvKey: Platform.environment[winEnvKey]!,
      ...?environment,
    };
  }

  final printWorkingDir =
      workingDirectory != null && workingDirectory != Directory.current.uri;
  final commandString = [
    if (printWorkingDir) '(cd ${workingDirectory.toFilePath()};',
    ...?environment?.entries.map((entry) => '${entry.key}=${entry.value}'),
    executable.toFilePath(),
    ...arguments.map((a) => a.contains(' ') ? "'$a'" : a),
    if (printWorkingDir) ')',
  ].join(' ');
  logger?.info('Running `$commandString`.');

  final stdoutBuffer = StringBuffer();
  final stderrBuffer = StringBuffer();
  final process = await Process.start(
    executable.toFilePath(),
    arguments,
    workingDirectory: workingDirectory?.toFilePath(),
    environment: environment,
    includeParentEnvironment: includeParentEnvironment,
    runInShell: Platform.isWindows && !includeParentEnvironment,
  );

  final stdoutSub = process.stdout
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .listen(captureOutput
          ? (s) {
              logger?.fine(s);
              stdoutBuffer.writeln(s);
            }
          : logger?.fine);
  final stderrSub = process.stderr
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .listen(captureOutput
          ? (s) {
              logger?.severe(s);
              stderrBuffer.writeln(s);
            }
          : logger?.severe);

  final (exitCode, _, _) = await (
    process.exitCode,
    stdoutSub.asFuture<void>(),
    stderrSub.asFuture<void>()
  ).wait;
  final result = RunProcessResult(
    pid: process.pid,
    command: commandString,
    exitCode: exitCode,
    stdout: stdoutBuffer.toString(),
    stderr: stderrBuffer.toString(),
  );
  if (throwOnUnexpectedExitCode && expectedExitCode != exitCode) {
    throw ProcessException(
      executable.toFilePath(),
      arguments,
      "Full command string: '$commandString'.\n"
      "Exit code: '$exitCode'.\n"
      'For the output of the process check the logger output.',
    );
  }
  return result;
}