run function

Future<ProcessResult> run(
  1. String executable,
  2. List<String> arguments,
  3. {String workingDirectory,
  4. Map<String, String> environment,
  5. bool includeParentEnvironment = true,
  6. bool runInShell,
  7. Encoding stdoutEncoding = systemEncoding,
  8. Encoding stderrEncoding = systemEncoding,
  9. Stream<List<int>> stdin,
  10. StreamSink<List<int>> stdout,
  11. StreamSink<List<int>> stderr,
  12. bool verbose,
  13. bool commandVerbose}
)

if commmandVerbose or verbose is true, display the command. if verbose is true, stream stdout & stdin

Implementation

Future<ProcessResult> run(String executable, List<String> arguments,
    {String workingDirectory,
    Map<String, String> environment,
    bool includeParentEnvironment = true,
    bool runInShell,
    Encoding stdoutEncoding = systemEncoding,
    Encoding stderrEncoding = systemEncoding,
    Stream<List<int>> stdin,
    StreamSink<List<int>> stdout,
    StreamSink<List<int>> stderr,
    bool verbose,
    bool commandVerbose}) async {
  // enforce default
  includeParentEnvironment ??= true;
  //stdoutEncoding ??= SYSTEM_ENCODING;
  //stderrEncoding ??= SYSTEM_ENCODING;

  if (verbose == true) {
    commandVerbose = true;
    stdout ??= io.stdout;
    stderr ??= io.stderr;
  }

  if (commandVerbose == true) {
    utils.streamSinkWriteln(stdout ?? io.stdout,
        '\$ ${executableArgumentsToString(executable, arguments)}');
  }

  // Filter out environment
  // to remove vm_services
  if (includeParentEnvironment != false) {
    environment ??= userEnvironment;

    includeParentEnvironment = false;
  }

  // Default is the full command
  var executableShortName = executable;

  // Find executable if needed, i.e. if it is only a name
  if (basename(executable) == executable) {
    // Try to find it in path or use it as is
    executable = utils.findExecutableSync(executable, userPaths) ?? executable;
  }

  // Fix runInShell on windows (force run in shell for non-.exe)
  runInShell = utils.fixRunInShell(runInShell, executable);

  Process process;
  try {
    process = await Process.start(executable, arguments,
        workingDirectory: workingDirectory,
        environment: environment,
        includeParentEnvironment: includeParentEnvironment,
        runInShell: runInShell);
  } catch (e) {
    if (verbose == true) {
      io.stderr.writeln(e);
      io.stderr.writeln(
          '\$ ${executableArgumentsToString(executableShortName, arguments)}');
      io.stderr.writeln(
          'workingDirectory: ${workingDirectory ?? Directory.current?.path}');
    }
    rethrow;
  }

  final outCtlr = StreamController<List<int>>(sync: true);
  final errCtlr = StreamController<List<int>>(sync: true);

  // Connected stdin
  // Buggy!
  StreamSubscription stdinSubscription;
  if (stdin != null) {
    //stdin.pipe(process.stdin); // this closes the stream...
    stdinSubscription = stdin.listen((List<int> data) {
      process.stdin.add(data);
    })
      ..onDone(() {
        process.stdin.close();
      });
    // OLD 2: process.stdin.addStream(stdin);
  } else {
    // Close the input sync, we want this not interractive
    //process.stdin.close();
  }

  Future<dynamic> streamToResult(
      Stream<List<int>> stream, Encoding encoding) async {
    final list = <int>[];
    await for (final data in stream) {
      //devPrint('s: ${data}');
      list.addAll(data);
    }
    if (encoding != null) {
      return encoding.decode(list);
    }
    return list;
  }

  var out = streamToResult(outCtlr.stream, stdoutEncoding);
  var err = streamToResult(errCtlr.stream, stderrEncoding);

  process.stdout.listen((List<int> d) {
    if (stdout != null) {
      stdout.add(d);
    }
    outCtlr.add(d);
  }, onDone: () {
    outCtlr.close();
  });

  process.stderr.listen((List<int> d) async {
    if (stderr != null) {
      stderr.add(d);
    }
    errCtlr.add(d);
  }, onDone: () {
    errCtlr.close();
  });

  final exitCode = await process.exitCode;

  /// Cancel input sink
  if (stdinSubscription != null) {
    await stdinSubscription.cancel();
  }

  // Notice that exitCode can complete before all of the lines of output have been
  // processed. Also note that we do not explicitly close the process. In order
  // to not leak resources we have to drain both the stderr and the stdout streams.
  // To do that we set a listener (using await for) to drain the stderr stream.
  //await process.stdout.drain();
  //await process.stderr.drain();

  final result = ProcessResult(process.pid, exitCode, await out, await err);

  if (stdin != null) {
    //process.stdin.close();
  }

  // flush for consistency
  if (stdout == io.stdout) {
    await io.stdout.flush();
  }
  if (stderr == io.stderr) {
    await io.stderr.flush();
  }

  return result;
}