// Copyright (c) 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 Function( String executable, List arguments, { String workingDirectory, Map 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 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>? 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> _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 spawn( String executable, Iterable arguments, { String? workingDirectory, Map? 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 spawnBackground( String executable, Iterable arguments, { String? workingDirectory, Map? 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 spawnDetached( String executable, Iterable arguments, { String? workingDirectory, Map? 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 get exitCode => _delegate.exitCode; @override int get pid => _delegate.pid; @override Stream> get stderr => _delegate.stderr; @override io.IOSink get stdin => _delegate.stdin; @override Stream> get stdout => _delegate.stdout; } /// Forwards `stdin`/`stdout`/`stderr` to/from the host. class _ForwardingSpawn extends Spawn { final StreamSubscription> _stdInSub; final StreamSubscription> _stdOutSub; final StreamSubscription> _stdErrSub; final StreamController> _stdOut; final StreamController> _stdErr; factory _ForwardingSpawn( io.Process delegate, Stream> stdin, io.IOSink stdout, io.IOSink stderr, ) { final stdoutSelf = StreamController>(); final stderrSelf = StreamController>(); 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( super.delegate, this._stdInSub, this._stdOutSub, this._stdErrSub, this._stdOut, this._stdErr, ) : super._(); @override void _onClosed() { _stdInSub.cancel(); _stdOutSub.cancel(); _stdErrSub.cancel(); super._onClosed(); } @override Stream> get stdout => _stdOut.stream; @override Stream> get stderr => _stdErr.stream; } class _UnixProcessManager extends ProcessManager { const _UnixProcessManager( super.stdin, super.stdout, super.stderr, ) : super._(); } class _WindowsProcessManager extends ProcessManager { const _WindowsProcessManager( super.stdin, super.stdout, super.stderr, ) : super._(); }