| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| /// This file serves as the single point of entry into the `dart:io` APIs | |
| /// within Flutter tools. | |
| /// | |
| /// In order to make Flutter tools more testable, we use the `FileSystem` APIs | |
| /// in `package:file` rather than using the `dart:io` file APIs directly (see | |
| /// `file_system.dart`). Doing so allows us to swap out local file system | |
| /// access with mockable (or in-memory) file systems, making our tests hermetic | |
| /// vis-a-vis file system access. | |
| /// | |
| /// We also use `package:platform` to provide an abstraction away from the | |
| /// static methods in the `dart:io` `Platform` class (see `platform.dart`). As | |
| /// such, do not export Platform from this file! | |
| /// | |
| /// To ensure that all file system and platform API access within Flutter tools | |
| /// goes through the proper APIs, we forbid direct imports of `dart:io` (via a | |
| /// test), forcing all callers to instead import this file, which exports the | |
| /// blessed subset of `dart:io` that is legal to use in Flutter tools. | |
| /// | |
| /// Because of the nature of this file, it is important that **platform and file | |
| /// APIs not be exported from `dart:io` in this file**! Moreover, be careful | |
| /// about any additional exports that you add to this file, as doing so will | |
| /// increase the API surface that we have to test in Flutter tools, and the APIs | |
| /// in `dart:io` can sometimes be hard to use in tests. | |
| // We allow `print()` in this file as a fallback for writing to the terminal via | |
| // regular stdout/stderr/stdio paths. Everything else in the flutter_tools | |
| // library should route terminal I/O through the [Stdio] class defined below. | |
| // ignore_for_file: avoid_print | |
| import 'dart:async'; | |
| import 'dart:io' as io | |
| show | |
| IOSink, | |
| InternetAddress, | |
| InternetAddressType, | |
| NetworkInterface, | |
| Process, | |
| ProcessInfo, | |
| ProcessSignal, | |
| Stdin, | |
| StdinException, | |
| Stdout, | |
| StdoutException, | |
| exit, | |
| pid, | |
| stderr, | |
| stdin, | |
| stdout; | |
| import 'package:file/file.dart'; | |
| import 'package:meta/meta.dart'; | |
| import 'async_guard.dart'; | |
| import 'platform.dart'; | |
| import 'process.dart'; | |
| export 'dart:io' | |
| show | |
| BytesBuilder, | |
| CompressionOptions, | |
| // Directory, NO! Use `file_system.dart` | |
| // File, NO! Use `file_system.dart` | |
| // FileSystemEntity, NO! Use `file_system.dart` | |
| GZipCodec, | |
| HandshakeException, | |
| HttpClient, | |
| HttpClientRequest, | |
| HttpClientResponse, | |
| HttpClientResponseCompressionState, | |
| HttpException, | |
| HttpHeaders, | |
| HttpRequest, | |
| HttpResponse, | |
| HttpServer, | |
| HttpStatus, | |
| IOException, | |
| IOSink, | |
| InternetAddress, | |
| InternetAddressType, | |
| // Link NO! Use `file_system.dart` | |
| // NetworkInterface NO! Use `io.dart` | |
| OSError, | |
| // Platform NO! use `platform.dart` | |
| Process, | |
| ProcessException, | |
| // ProcessInfo, NO! use `io.dart` | |
| ProcessResult, | |
| // ProcessSignal NO! Use [ProcessSignal] below. | |
| ProcessStartMode, | |
| // RandomAccessFile NO! Use `file_system.dart` | |
| ServerSocket, | |
| SignalException, | |
| Socket, | |
| SocketException, | |
| Stdin, | |
| StdinException, | |
| Stdout, | |
| WebSocket, | |
| WebSocketException, | |
| WebSocketTransformer, | |
| ZLibEncoder, | |
| exitCode, | |
| gzip, | |
| pid, | |
| // stderr, NO! Use `io.dart` | |
| // stdin, NO! Use `io.dart` | |
| // stdout, NO! Use `io.dart` | |
| systemEncoding; | |
| /// Exits the process with the given [exitCode]. | |
| typedef ExitFunction = void Function(int exitCode); | |
| const ExitFunction _defaultExitFunction = io.exit; | |
| ExitFunction _exitFunction = _defaultExitFunction; | |
| /// Exits the process. | |
| /// | |
| /// Throws [AssertionError] if assertions are enabled and the dart:io exit | |
| /// is still active when called. This may indicate exit was called in | |
| /// a test without being configured correctly. | |
| /// | |
| /// This is analogous to the `exit` function in `dart:io`, except that this | |
| /// function may be set to a testing-friendly value by calling | |
| /// [setExitFunctionForTests] (and then restored to its default implementation | |
| /// with [restoreExitFunction]). The default implementation delegates to | |
| /// `dart:io`. | |
| ExitFunction get exit { | |
| assert( | |
| _exitFunction != io.exit || !_inUnitTest(), | |
| 'io.exit was called with assertions active in a unit test', | |
| ); | |
| return _exitFunction; | |
| } | |
| // Whether the tool is executing in a unit test. | |
| bool _inUnitTest() { | |
| return Zone.current[#test.declarer] != null; | |
| } | |
| /// Sets the [exit] function to a function that throws an exception rather | |
| /// than exiting the process; this is intended for testing purposes. | |
| @visibleForTesting | |
| void setExitFunctionForTests([ ExitFunction? exitFunction ]) { | |
| _exitFunction = exitFunction ?? (int exitCode) { | |
| throw ProcessExit(exitCode, immediate: true); | |
| }; | |
| } | |
| /// Restores the [exit] function to the `dart:io` implementation. | |
| @visibleForTesting | |
| void restoreExitFunction() { | |
| _exitFunction = _defaultExitFunction; | |
| } | |
| /// A portable version of [io.ProcessSignal]. | |
| /// | |
| /// Listening on signals that don't exist on the current platform is just a | |
| /// no-op. This is in contrast to [io.ProcessSignal], where listening to | |
| /// non-existent signals throws an exception. | |
| /// | |
| /// This class does NOT implement io.ProcessSignal, because that class uses | |
| /// private fields. This means it cannot be used with, e.g., [Process.killPid]. | |
| /// Alternative implementations of the relevant methods that take | |
| /// [ProcessSignal] instances are available on this class (e.g. "send"). | |
| class ProcessSignal { | |
| @visibleForTesting | |
| const ProcessSignal(this._delegate, {@visibleForTesting Platform platform = const LocalPlatform()}) | |
| : _platform = platform; | |
| static const ProcessSignal sigwinch = PosixProcessSignal(io.ProcessSignal.sigwinch); | |
| static const ProcessSignal sigterm = PosixProcessSignal(io.ProcessSignal.sigterm); | |
| static const ProcessSignal sigusr1 = PosixProcessSignal(io.ProcessSignal.sigusr1); | |
| static const ProcessSignal sigusr2 = PosixProcessSignal(io.ProcessSignal.sigusr2); | |
| static const ProcessSignal sigint = ProcessSignal(io.ProcessSignal.sigint); | |
| static const ProcessSignal sigkill = ProcessSignal(io.ProcessSignal.sigkill); | |
| final io.ProcessSignal _delegate; | |
| final Platform _platform; | |
| Stream<ProcessSignal> watch() { | |
| return _delegate.watch().map<ProcessSignal>((io.ProcessSignal signal) => this); | |
| } | |
| /// Sends the signal to the given process (identified by pid). | |
| /// | |
| /// Returns true if the signal was delivered, false otherwise. | |
| /// | |
| /// On Windows, this can only be used with [ProcessSignal.sigterm], which | |
| /// terminates the process. | |
| /// | |
| /// This is implemented by sending the signal using [Process.killPid]. | |
| bool send(int pid) { | |
| assert(!_platform.isWindows || this == ProcessSignal.sigterm); | |
| return io.Process.killPid(pid, _delegate); | |
| } | |
| @override | |
| String toString() => _delegate.toString(); | |
| } | |
| /// A [ProcessSignal] that is only available on Posix platforms. | |
| /// | |
| /// Listening to a [_PosixProcessSignal] is a no-op on Windows. | |
| @visibleForTesting | |
| class PosixProcessSignal extends ProcessSignal { | |
| const PosixProcessSignal(super.wrappedSignal, {@visibleForTesting super.platform}); | |
| @override | |
| Stream<ProcessSignal> watch() { | |
| // This uses the real platform since it invokes dart:io functionality directly. | |
| if (_platform.isWindows) { | |
| return const Stream<ProcessSignal>.empty(); | |
| } | |
| return super.watch(); | |
| } | |
| } | |
| /// A class that wraps stdout, stderr, and stdin, and exposes the allowed | |
| /// operations. | |
| /// | |
| /// In particular, there are three ways that writing to stdout and stderr | |
| /// can fail. A call to stdout.write() can fail: | |
| /// * by throwing a regular synchronous exception, | |
| /// * by throwing an exception asynchronously, and | |
| /// * by completing the Future stdout.done with an error. | |
| /// | |
| /// This class enapsulates all three so that we don't have to worry about it | |
| /// anywhere else. | |
| class Stdio { | |
| Stdio(); | |
| /// Tests can provide overrides to use instead of the stdout and stderr from | |
| /// dart:io. | |
| @visibleForTesting | |
| Stdio.test({ | |
| required io.Stdout stdout, | |
| required io.IOSink stderr, | |
| }) : _stdoutOverride = stdout, _stderrOverride = stderr; | |
| io.Stdout? _stdoutOverride; | |
| io.IOSink? _stderrOverride; | |
| // These flags exist to remember when the done Futures on stdout and stderr | |
| // complete to avoid trying to write to a closed stream sink, which would | |
| // generate a [StateError]. | |
| bool _stdoutDone = false; | |
| bool _stderrDone = false; | |
| Stream<List<int>> get stdin => io.stdin; | |
| io.Stdout get stdout { | |
| if (_stdout != null) { | |
| return _stdout!; | |
| } | |
| _stdout = _stdoutOverride ?? io.stdout; | |
| _stdout!.done.then( | |
| (void _) { _stdoutDone = true; }, | |
| onError: (Object err, StackTrace st) { _stdoutDone = true; }, | |
| ); | |
| return _stdout!; | |
| } | |
| io.Stdout? _stdout; | |
| @visibleForTesting | |
| io.IOSink get stderr { | |
| if (_stderr != null) { | |
| return _stderr!; | |
| } | |
| _stderr = _stderrOverride ?? io.stderr; | |
| _stderr!.done.then( | |
| (void _) { _stderrDone = true; }, | |
| onError: (Object err, StackTrace st) { _stderrDone = true; }, | |
| ); | |
| return _stderr!; | |
| } | |
| io.IOSink? _stderr; | |
| bool get hasTerminal => io.stdout.hasTerminal; | |
| static bool? _stdinHasTerminal; | |
| /// Determines whether there is a terminal attached. | |
| /// | |
| /// [io.Stdin.hasTerminal] only covers a subset of cases. In this check the | |
| /// echoMode is toggled on and off to catch cases where the tool running in | |
| /// a docker container thinks there is an attached terminal. This can cause | |
| /// runtime errors such as "inappropriate ioctl for device" if not handled. | |
| bool get stdinHasTerminal { | |
| if (_stdinHasTerminal != null) { | |
| return _stdinHasTerminal!; | |
| } | |
| if (stdin is! io.Stdin) { | |
| return _stdinHasTerminal = false; | |
| } | |
| final io.Stdin ioStdin = stdin as io.Stdin; | |
| if (!ioStdin.hasTerminal) { | |
| return _stdinHasTerminal = false; | |
| } | |
| try { | |
| final bool currentEchoMode = ioStdin.echoMode; | |
| ioStdin.echoMode = !currentEchoMode; | |
| ioStdin.echoMode = currentEchoMode; | |
| } on io.StdinException { | |
| return _stdinHasTerminal = false; | |
| } | |
| return _stdinHasTerminal = true; | |
| } | |
| int? get terminalColumns => hasTerminal ? stdout.terminalColumns : null; | |
| int? get terminalLines => hasTerminal ? stdout.terminalLines : null; | |
| bool get supportsAnsiEscapes => hasTerminal && stdout.supportsAnsiEscapes; | |
| /// Writes [message] to [stderr], falling back on [fallback] if the write | |
| /// throws any exception. The default fallback calls [print] on [message]. | |
| void stderrWrite( | |
| String message, { | |
| void Function(String, dynamic, StackTrace)? fallback, | |
| }) { | |
| if (!_stderrDone) { | |
| _stdioWrite(stderr, message, fallback: fallback); | |
| return; | |
| } | |
| fallback == null ? print(message) : fallback( | |
| message, | |
| const io.StdoutException('stderr is done'), | |
| StackTrace.current, | |
| ); | |
| } | |
| /// Writes [message] to [stdout], falling back on [fallback] if the write | |
| /// throws any exception. The default fallback calls [print] on [message]. | |
| void stdoutWrite( | |
| String message, { | |
| void Function(String, dynamic, StackTrace)? fallback, | |
| }) { | |
| if (!_stdoutDone) { | |
| _stdioWrite(stdout, message, fallback: fallback); | |
| return; | |
| } | |
| fallback == null ? print(message) : fallback( | |
| message, | |
| const io.StdoutException('stdout is done'), | |
| StackTrace.current, | |
| ); | |
| } | |
| // Helper for [stderrWrite] and [stdoutWrite]. | |
| void _stdioWrite(io.IOSink sink, String message, { | |
| void Function(String, dynamic, StackTrace)? fallback, | |
| }) { | |
| asyncGuard<void>(() async { | |
| sink.write(message); | |
| }, onError: (Object error, StackTrace stackTrace) { | |
| if (fallback == null) { | |
| print(message); | |
| } else { | |
| fallback(message, error, stackTrace); | |
| } | |
| }); | |
| } | |
| /// Adds [stream] to [stdout]. | |
| Future<void> addStdoutStream(Stream<List<int>> stream) => stdout.addStream(stream); | |
| /// Adds [stream] to [stderr]. | |
| Future<void> addStderrStream(Stream<List<int>> stream) => stderr.addStream(stream); | |
| } | |
| /// An overridable version of io.ProcessInfo. | |
| abstract class ProcessInfo { | |
| factory ProcessInfo(FileSystem fs) => _DefaultProcessInfo(fs); | |
| factory ProcessInfo.test(FileSystem fs) => _TestProcessInfo(fs); | |
| int get currentRss; | |
| int get maxRss; | |
| File writePidFile(String pidFile); | |
| } | |
| /// The default implementation of [ProcessInfo], which uses [io.ProcessInfo]. | |
| class _DefaultProcessInfo implements ProcessInfo { | |
| _DefaultProcessInfo(this._fileSystem); | |
| final FileSystem _fileSystem; | |
| @override | |
| int get currentRss => io.ProcessInfo.currentRss; | |
| @override | |
| int get maxRss => io.ProcessInfo.maxRss; | |
| @override | |
| File writePidFile(String pidFile) { | |
| assert(pidFile != null); | |
| return _fileSystem.file(pidFile) | |
| ..writeAsStringSync(io.pid.toString()); | |
| } | |
| } | |
| /// The test version of [ProcessInfo]. | |
| class _TestProcessInfo implements ProcessInfo { | |
| _TestProcessInfo(this._fileSystem); | |
| final FileSystem _fileSystem; | |
| @override | |
| int currentRss = 1000; | |
| @override | |
| int maxRss = 2000; | |
| @override | |
| File writePidFile(String pidFile) { | |
| assert(pidFile != null); | |
| return _fileSystem.file(pidFile) | |
| ..writeAsStringSync('12345'); | |
| } | |
| } | |
| /// The return type for [listNetworkInterfaces]. | |
| class NetworkInterface implements io.NetworkInterface { | |
| NetworkInterface(this._delegate); | |
| final io.NetworkInterface _delegate; | |
| @override | |
| List<io.InternetAddress> get addresses => _delegate.addresses; | |
| @override | |
| int get index => _delegate.index; | |
| @override | |
| String get name => _delegate.name; | |
| @override | |
| String toString() => "NetworkInterface('$name', $addresses)"; | |
| } | |
| typedef NetworkInterfaceLister = Future<List<NetworkInterface>> Function({ | |
| bool includeLoopback, | |
| bool includeLinkLocal, | |
| io.InternetAddressType type, | |
| }); | |
| NetworkInterfaceLister? _networkInterfaceListerOverride; | |
| // Tests can set up a non-default network interface lister. | |
| @visibleForTesting | |
| void setNetworkInterfaceLister(NetworkInterfaceLister lister) { | |
| _networkInterfaceListerOverride = lister; | |
| } | |
| @visibleForTesting | |
| void resetNetworkInterfaceLister() { | |
| _networkInterfaceListerOverride = null; | |
| } | |
| /// This calls [NetworkInterface.list] from `dart:io` unless it is overridden by | |
| /// [setNetworkInterfaceLister] for a test. If it is overridden for a test, | |
| /// it should be reset with [resetNetworkInterfaceLister]. | |
| Future<List<NetworkInterface>> listNetworkInterfaces({ | |
| bool includeLoopback = false, | |
| bool includeLinkLocal = false, | |
| io.InternetAddressType type = io.InternetAddressType.any, | |
| }) async { | |
| if (_networkInterfaceListerOverride != null) { | |
| return _networkInterfaceListerOverride!.call( | |
| includeLoopback: includeLoopback, | |
| includeLinkLocal: includeLinkLocal, | |
| type: type, | |
| ); | |
| } | |
| final List<io.NetworkInterface> interfaces = await io.NetworkInterface.list( | |
| includeLoopback: includeLoopback, | |
| includeLinkLocal: includeLinkLocal, | |
| type: type, | |
| ); | |
| return interfaces.map( | |
| (io.NetworkInterface interface) => NetworkInterface(interface), | |
| ).toList(); | |
| } |