| // 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<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( | |
| 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<List<int>> get stdout => _stdOut.stream; | |
| @override | |
| Stream<List<int>> 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._(); | |
| } |