| // 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, |
| ); |
| } |