blob: 84af6df064ec70a5055362df1c6a7ae602b52fb7 [file] [log] [blame]
// Copyright 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// ignore_for_file: close_sinks,cancel_subscriptions
import 'dart:async';
import 'dart:io' as io;
import 'package:meta/meta.dart';
import 'shared_stdin.dart';
/// Type definition for both [io.Process.start] and [ProcessManager.spawn].
///
/// Useful for taking different implementations of this base functionality.
typedef StartProcess = Future<io.Process> Function(
String executable,
List<String> arguments, {
String workingDirectory,
Map<String, String> environment,
bool includeParentEnvironment,
bool runInShell,
io.ProcessStartMode mode,
});
/// A high-level abstraction around using and managing processes on the system.
abstract class ProcessManager {
/// Terminates the global `stdin` listener, making future listens impossible.
///
/// This method should be invoked only at the _end_ of a program's execution.
static Future<void> terminateStdIn() async {
await sharedStdIn.terminate();
}
/// Create a new instance of [ProcessManager] for the current platform.
///
/// May manually specify whether the current platform [isWindows], otherwise
/// this is derived from the Dart runtime (i.e. [io.Platform.isWindows]).
factory ProcessManager({
Stream<List<int>>? stdin,
io.IOSink? stdout,
io.IOSink? stderr,
bool? isWindows,
}) {
stdin ??= sharedStdIn;
stdout ??= io.stdout;
stderr ??= io.stderr;
isWindows ??= io.Platform.isWindows;
if (isWindows) {
return _WindowsProcessManager(stdin, stdout, stderr);
}
return _UnixProcessManager(stdin, stdout, stderr);
}
final Stream<List<int>> _stdin;
final io.IOSink _stdout;
final io.IOSink _stderr;
const ProcessManager._(this._stdin, this._stdout, this._stderr);
/// Spawns a process by invoking [executable] with [arguments].
///
/// This is _similar_ to [io.Process.start], but all standard input and output
/// is forwarded/routed between the process and the host, similar to how a
/// shell script works.
///
/// Returns a future that completes with a handle to the spawned process.
Future<io.Process> spawn(
String executable,
Iterable<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) async {
final process = io.Process.start(
executable,
arguments.toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
mode: mode,
);
return _ForwardingSpawn(await process, _stdin, _stdout, _stderr);
}
/// Spawns a process by invoking [executable] with [arguments].
///
/// This is _similar_ to [io.Process.start], but `stdout` and `stderr` is
/// forwarded/routed between the process and host, similar to how a shell
/// script works.
///
/// Returns a future that completes with a handle to the spawned process.
Future<io.Process> spawnBackground(
String executable,
Iterable<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) async {
final process = io.Process.start(
executable,
arguments.toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
mode: mode,
);
return _ForwardingSpawn(
await process,
const Stream.empty(),
_stdout,
_stderr,
);
}
/// Spawns a process by invoking [executable] with [arguments].
///
/// This is _identical to [io.Process.start] (no forwarding of I/O).
///
/// Returns a future that completes with a handle to the spawned process.
Future<io.Process> spawnDetached(
String executable,
Iterable<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) async =>
io.Process.start(
executable,
arguments.toList(),
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
mode: mode,
);
}
/// A process instance created and managed through [ProcessManager].
///
/// Unlike one created directly by [io.Process.start] or [io.Process.run], a
/// spawned process works more like executing a command in a shell script.
class Spawn implements io.Process {
final io.Process _delegate;
Spawn._(this._delegate) {
_delegate.exitCode.then((_) => _onClosed());
}
@mustCallSuper
void _onClosed() {}
@override
bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) =>
_delegate.kill(signal);
@override
Future<int> get exitCode => _delegate.exitCode;
@override
int get pid => _delegate.pid;
@override
Stream<List<int>> get stderr => _delegate.stderr;
@override
io.IOSink get stdin => _delegate.stdin;
@override
Stream<List<int>> get stdout => _delegate.stdout;
}
/// Forwards `stdin`/`stdout`/`stderr` to/from the host.
class _ForwardingSpawn extends Spawn {
final StreamSubscription<List<int>> _stdInSub;
final StreamSubscription<List<int>> _stdOutSub;
final StreamSubscription<List<int>> _stdErrSub;
final StreamController<List<int>> _stdOut;
final StreamController<List<int>> _stdErr;
factory _ForwardingSpawn(
io.Process delegate,
Stream<List<int>> stdin,
io.IOSink stdout,
io.IOSink stderr,
) {
final stdoutSelf = StreamController<List<int>>();
final stderrSelf = StreamController<List<int>>();
final stdInSub = stdin.listen(delegate.stdin.add);
final stdOutSub = delegate.stdout.listen((event) {
stdout.add(event);
stdoutSelf.add(event);
});
final stdErrSub = delegate.stderr.listen((event) {
stderr.add(event);
stderrSelf.add(event);
});
return _ForwardingSpawn._delegate(
delegate,
stdInSub,
stdOutSub,
stdErrSub,
stdoutSelf,
stderrSelf,
);
}
_ForwardingSpawn._delegate(
io.Process delegate,
this._stdInSub,
this._stdOutSub,
this._stdErrSub,
this._stdOut,
this._stdErr,
) : super._(delegate);
@override
void _onClosed() {
_stdInSub.cancel();
_stdOutSub.cancel();
_stdErrSub.cancel();
super._onClosed();
}
@override
Stream<List<int>> get stdout => _stdOut.stream;
@override
Stream<List<int>> get stderr => _stdErr.stream;
}
class _UnixProcessManager extends ProcessManager {
const _UnixProcessManager(
Stream<List<int>> stdin,
io.IOSink stdout,
io.IOSink stderr,
) : super._(
stdin,
stdout,
stderr,
);
}
class _WindowsProcessManager extends ProcessManager {
const _WindowsProcessManager(
Stream<List<int>> stdin,
io.IOSink stdout,
io.IOSink stderr,
) : super._(
stdin,
stdout,
stderr,
);
}