1// Copyright 2017 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/// This file serves as the single point of entry into the `dart:io` APIs 6/// within Flutter tools. 7/// 8/// In order to make Flutter tools more testable, we use the `FileSystem` APIs 9/// in `package:file` rather than using the `dart:io` file APIs directly (see 10/// `file_system.dart`). Doing so allows us to swap out local file system 11/// access with mockable (or in-memory) file systems, making our tests hermetic 12/// vis-a-vis file system access. 13/// 14/// We also use `package:platform` to provide an abstraction away from the 15/// static methods in the `dart:io` `Platform` class (see `platform.dart`). As 16/// such, do not export Platform from this file! 17/// 18/// To ensure that all file system and platform API access within Flutter tools 19/// goes through the proper APIs, we forbid direct imports of `dart:io` (via a 20/// test), forcing all callers to instead import this file, which exports the 21/// blessed subset of `dart:io` that is legal to use in Flutter tools. 22/// 23/// Because of the nature of this file, it is important that **platform and file 24/// APIs not be exported from `dart:io` in this file**! Moreover, be careful 25/// about any additional exports that you add to this file, as doing so will 26/// increase the API surface that we have to test in Flutter tools, and the APIs 27/// in `dart:io` can sometimes be hard to use in tests. 28import 'dart:async'; 29import 'dart:io' as io show exit, IOSink, Process, ProcessSignal, stderr, stdin, Stdout, stdout; 30 31import 'package:meta/meta.dart'; 32 33import 'context.dart'; 34import 'platform.dart'; 35import 'process.dart'; 36 37export 'dart:io' 38 show 39 BytesBuilder, 40 CompressionOptions, 41 // Directory NO! Use `file_system.dart` 42 exitCode, 43 // File NO! Use `file_system.dart` 44 // FileSystemEntity NO! Use `file_system.dart` 45 gzip, 46 HandshakeException, 47 HttpClient, 48 HttpClientRequest, 49 HttpClientResponse, 50 HttpClientResponseCompressionState, 51 HttpException, 52 HttpHeaders, 53 HttpRequest, 54 HttpServer, 55 HttpStatus, 56 InternetAddress, 57 InternetAddressType, 58 IOException, 59 IOSink, 60 // Link NO! Use `file_system.dart` 61 pid, 62 // Platform NO! use `platform.dart` 63 Process, 64 ProcessException, 65 ProcessResult, 66 // ProcessSignal NO! Use [ProcessSignal] below. 67 ProcessStartMode, 68 // RandomAccessFile NO! Use `file_system.dart` 69 ServerSocket, 70 // stderr, NO! Use `io.dart` 71 // stdin, NO! Use `io.dart` 72 Stdin, 73 StdinException, 74 // stdout, NO! Use `io.dart` 75 Stdout, 76 Socket, 77 SocketException, 78 systemEncoding, 79 WebSocket, 80 WebSocketException, 81 WebSocketTransformer; 82 83/// Exits the process with the given [exitCode]. 84typedef ExitFunction = void Function(int exitCode); 85 86const ExitFunction _defaultExitFunction = io.exit; 87 88ExitFunction _exitFunction = _defaultExitFunction; 89 90/// Exits the process. 91/// 92/// This is analogous to the `exit` function in `dart:io`, except that this 93/// function may be set to a testing-friendly value by calling 94/// [setExitFunctionForTests] (and then restored to its default implementation 95/// with [restoreExitFunction]). The default implementation delegates to 96/// `dart:io`. 97ExitFunction get exit => _exitFunction; 98 99/// Sets the [exit] function to a function that throws an exception rather 100/// than exiting the process; this is intended for testing purposes. 101@visibleForTesting 102void setExitFunctionForTests([ ExitFunction exitFunction ]) { 103 _exitFunction = exitFunction ?? (int exitCode) { 104 throw ProcessExit(exitCode, immediate: true); 105 }; 106} 107 108/// Restores the [exit] function to the `dart:io` implementation. 109@visibleForTesting 110void restoreExitFunction() { 111 _exitFunction = _defaultExitFunction; 112} 113 114/// A portable version of [io.ProcessSignal]. 115/// 116/// Listening on signals that don't exist on the current platform is just a 117/// no-op. This is in contrast to [io.ProcessSignal], where listening to 118/// non-existent signals throws an exception. 119/// 120/// This class does NOT implement io.ProcessSignal, because that class uses 121/// private fields. This means it cannot be used with, e.g., [Process.killPid]. 122/// Alternative implementations of the relevant methods that take 123/// [ProcessSignal] instances are available on this class (e.g. "send"). 124class ProcessSignal { 125 @visibleForTesting 126 const ProcessSignal(this._delegate); 127 128 static const ProcessSignal SIGWINCH = _PosixProcessSignal._(io.ProcessSignal.sigwinch); 129 static const ProcessSignal SIGTERM = _PosixProcessSignal._(io.ProcessSignal.sigterm); 130 static const ProcessSignal SIGUSR1 = _PosixProcessSignal._(io.ProcessSignal.sigusr1); 131 static const ProcessSignal SIGUSR2 = _PosixProcessSignal._(io.ProcessSignal.sigusr2); 132 static const ProcessSignal SIGINT = ProcessSignal(io.ProcessSignal.sigint); 133 static const ProcessSignal SIGKILL = ProcessSignal(io.ProcessSignal.sigkill); 134 135 final io.ProcessSignal _delegate; 136 137 Stream<ProcessSignal> watch() { 138 return _delegate.watch().map<ProcessSignal>((io.ProcessSignal signal) => this); 139 } 140 141 /// Sends the signal to the given process (identified by pid). 142 /// 143 /// Returns true if the signal was delivered, false otherwise. 144 /// 145 /// On Windows, this can only be used with [ProcessSignal.SIGTERM], which 146 /// terminates the process. 147 /// 148 /// This is implemented by sending the signal using [Process.killPid]. 149 bool send(int pid) { 150 assert(!platform.isWindows || this == ProcessSignal.SIGTERM); 151 return io.Process.killPid(pid, _delegate); 152 } 153 154 @override 155 String toString() => _delegate.toString(); 156} 157 158/// A [ProcessSignal] that is only available on Posix platforms. 159/// 160/// Listening to a [_PosixProcessSignal] is a no-op on Windows. 161class _PosixProcessSignal extends ProcessSignal { 162 163 const _PosixProcessSignal._(io.ProcessSignal wrappedSignal) : super(wrappedSignal); 164 165 @override 166 Stream<ProcessSignal> watch() { 167 if (platform.isWindows) 168 return const Stream<ProcessSignal>.empty(); 169 return super.watch(); 170 } 171} 172 173class Stdio { 174 const Stdio(); 175 176 Stream<List<int>> get stdin => io.stdin; 177 io.Stdout get stdout => io.stdout; 178 io.IOSink get stderr => io.stderr; 179 180 bool get hasTerminal => io.stdout.hasTerminal; 181 int get terminalColumns => hasTerminal ? io.stdout.terminalColumns : null; 182 int get terminalLines => hasTerminal ? io.stdout.terminalLines : null; 183 bool get supportsAnsiEscapes => hasTerminal && io.stdout.supportsAnsiEscapes; 184} 185 186Stdio get stdio => context.get<Stdio>() ?? const Stdio(); 187io.Stdout get stdout => stdio.stdout; 188Stream<List<int>> get stdin => stdio.stdin; 189io.IOSink get stderr => stdio.stderr; 190