1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
// 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();
}