runProcess method

Future<ProcessInfo> runProcess(
  1. String commandName,
  2. List<String> args, {
  3. bool resolveCommandPath = true,
  4. String? workingDirectory,
  5. bool handleSignals = false,
  6. bool redirectOutput = false,
  7. bool catchOutput = false,
  8. bool drainOutput = false,
  9. String stdoutFilter(
    1. String o
    )?,
  10. String stderrFilter(
    1. String o
    )?,
  11. void onSignal(
    1. ProcessSignal signal
    )?,
})

Runs a Process command and returns it.

  • commandName is the command to be executed to start the Process.
  • args is the arguments to pass to the Process.
  • If resolveCommandPath is true resolves commandName to the local path of the command binary, using executablePath.
  • workingDirectory is the Process working directory. If null will use the current working directory.
  • If handleSignals is true kills the Process if SIGINT or SIGTERM is triggered in the host/current process.
    • onSignal is the hook for when a SIGINT or SIGTERM is redirected.
  • If redirectOutput is true redirects the Process outputs to the host/current stdout and stderr.
  • If catchOutput is true catches the Process outputs to ProcessInfo.outputBuffer and ProcessInfo.errorOutputBuffer.
  • stdoutFilter is a filter for the Process stdout. Useful to remove sensitive data.
  • stderrFilter is a filter for the Process stderr. Useful to remove sensitive data.
  • If drainOutput is true the Process outputs are consumed and discarded (when not already consumed by redirectOutput or catchOutput).

If none of redirectOutput, catchOutput and drainOutput is true, the Process stdout and stderr are not consumed (so the caller can read ProcessInfo.process stdout/stderr itself): the caller must drain them, otherwise a process that writes more than the OS pipe buffer blocks and never exits. Pass drainOutput: true when the output is not needed.

Implementation

Future<ProcessInfo> runProcess(
  String commandName,
  List<String> args, {
  bool resolveCommandPath = true,
  String? workingDirectory,
  bool handleSignals = false,
  bool redirectOutput = false,
  bool catchOutput = false,
  bool drainOutput = false,
  String Function(String o)? stdoutFilter,
  String Function(String o)? stderrFilter,
  void Function(ProcessSignal signal)? onSignal,
}) async {
  String? binPath;
  if (resolveCommandPath) {
    binPath = await executablePath(commandName);
    if (binPath == null) {
      throw StateError('Error resolving `$commandName` binary!');
    }
  } else {
    binPath = commandName;
  }

  log(
    'INFO',
    'Process.start> $binPath $args > workingDirectory: $workingDirectory',
  );

  var process = await Process.start(
    binPath,
    args,
    workingDirectory: workingDirectory,
  );

  StreamSubscription<ProcessSignal>? listenSigInt;
  StreamSubscription<ProcessSignal>? listenSigTerm;

  if (handleSignals) {
    listenSigInt = ProcessSignal.sigint.watch().listen((s) {
      if (onSignal != null) onSignal(s);
      process.kill(ProcessSignal.sigint);
    });

    listenSigTerm = ProcessSignal.sigterm.watch().listen((s) {
      if (onSignal != null) onSignal(s);
      process.kill(ProcessSignal.sigterm);
    });
  }

  var processInfo = ProcessInfo(process, binPath, args, workingDirectory);

  var outputDecoder = systemEncoding.decoder;
  final stdoutFilterF = stdoutFilter ?? (o) => o;
  final stderrFilterF = stderrFilter ?? (o) => o;

  if (catchOutput || redirectOutput) {
    // `ProcessInfo.exitCode` waits for these, so the output is complete
    // when it completes.
    processInfo._outputDone.add(
      process.stdout.transform(outputDecoder).forEach((o) {
        o = stdoutFilterF(o);
        if (redirectOutput) stdout.write(o);
        if (catchOutput) processInfo.outputBuffer.add(o);
      }),
    );
    processInfo._outputDone.add(
      process.stderr.transform(outputDecoder).forEach((o) {
        o = stderrFilterF(o);
        if (redirectOutput) stderr.write(o);
        if (catchOutput) processInfo.errorOutputBuffer.add(o);
      }),
    );
  } else if (drainOutput) {
    processInfo._outputDone.add(process.stdout.drain<void>());
    processInfo._outputDone.add(process.stderr.drain<void>());
  }

  process.exitCode.then((_) {
    // ignore: unawaited_futures
    listenSigInt?.cancel();
    // ignore: unawaited_futures
    listenSigTerm?.cancel();
  });

  return processInfo;
}