4
4
5
5
import 'dart:async' ;
6
6
7
+ import 'package:meta/meta.dart' ;
7
8
import 'package:package_config/package_config.dart' ;
8
9
import 'package:process/process.dart' ;
9
10
@@ -31,10 +32,6 @@ const String _kPubEnvironmentKey = 'PUB_ENVIRONMENT';
31
32
/// The console environment key used by the pub tool to find the cache directory.
32
33
const String _kPubCacheEnvironmentKey = 'PUB_CACHE' ;
33
34
34
- /// The UNAVAILABLE exit code returned by the pub tool.
35
- /// (see https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart)
36
- const int _kPubExitCodeUnavailable = 69 ;
37
-
38
35
typedef MessageFilter = String ? Function (String message);
39
36
40
37
/// globalCachePath is the directory in which the content of the localCachePath will be moved in
@@ -150,9 +147,20 @@ abstract class Pub {
150
147
required Platform platform,
151
148
required BotDetector botDetector,
152
149
required Usage usage,
153
- required Stdio stdio,
154
150
}) = _DefaultPub ;
155
151
152
+ /// Create a [Pub] instance with a mocked [stdio] .
153
+ @visibleForTesting
154
+ factory Pub .test ({
155
+ required FileSystem fileSystem,
156
+ required Logger logger,
157
+ required ProcessManager processManager,
158
+ required Platform platform,
159
+ required BotDetector botDetector,
160
+ required Usage usage,
161
+ required Stdio stdio,
162
+ }) = _DefaultPub .test;
163
+
156
164
/// Runs `pub get` or `pub upgrade` for [project] .
157
165
///
158
166
/// [context] provides extra information to package server requests to
@@ -214,6 +222,26 @@ class _DefaultPub implements Pub {
214
222
required Platform platform,
215
223
required BotDetector botDetector,
216
224
required Usage usage,
225
+ }) : _fileSystem = fileSystem,
226
+ _logger = logger,
227
+ _platform = platform,
228
+ _botDetector = botDetector,
229
+ _usage = usage,
230
+ _processUtils = ProcessUtils (
231
+ logger: logger,
232
+ processManager: processManager,
233
+ ),
234
+ _processManager = processManager,
235
+ _stdio = null ;
236
+
237
+ @visibleForTesting
238
+ _DefaultPub .test ({
239
+ required FileSystem fileSystem,
240
+ required Logger logger,
241
+ required ProcessManager processManager,
242
+ required Platform platform,
243
+ required BotDetector botDetector,
244
+ required Usage usage,
217
245
required Stdio stdio,
218
246
}) : _fileSystem = fileSystem,
219
247
_logger = logger,
@@ -234,7 +262,7 @@ class _DefaultPub implements Pub {
234
262
final BotDetector _botDetector;
235
263
final Usage _usage;
236
264
final ProcessManager _processManager;
237
- final Stdio _stdio;
265
+ final Stdio ? _stdio;
238
266
239
267
@override
240
268
Future <void > get ({
@@ -315,7 +343,7 @@ class _DefaultPub implements Pub {
315
343
'--offline' ,
316
344
'--example' ,
317
345
];
318
- await _runWithRetries (
346
+ await _runWithStdioInherited (
319
347
args,
320
348
command: command,
321
349
context: context,
@@ -346,15 +374,15 @@ class _DefaultPub implements Pub {
346
374
}
347
375
}
348
376
349
- /// Runs pub with [arguments] .
377
+ /// Runs pub with [arguments] and [ProcessStartMode.inheritStdio] mode .
350
378
///
351
- /// Retries the command as long as the exit code is
352
- /// `_kPubExitCodeUnavailable` .
379
+ /// Uses [ProcessStartMode.normal] and [Pub._stdio] if [Pub.test] constructor
380
+ /// was used .
353
381
///
354
- /// Prints the stderr and stdout of the last run.
382
+ /// Prints the stdout and stderr of the whole run.
355
383
///
356
- /// Sends an analytics event
357
- Future <void > _runWithRetries (
384
+ /// Sends an analytics event.
385
+ Future <void > _runWithStdioInherited (
358
386
List <String > arguments, {
359
387
required String command,
360
388
required bool printProgress,
@@ -365,57 +393,47 @@ class _DefaultPub implements Pub {
365
393
String ? flutterRootOverride,
366
394
}) async {
367
395
int exitCode;
368
- int attempts = 0 ;
369
- int duration = 1 ;
370
-
371
- List <_OutputLine >? output;
372
- StreamSubscription <String > recordLines (Stream <List <int >> stream, _OutputStream streamName) {
373
- return stream
374
- .transform <String >(utf8.decoder)
375
- .transform <String >(const LineSplitter ())
376
- .listen ((String line) => output! .add (_OutputLine (line, streamName)));
377
- }
378
396
379
- final Status ? status = printProgress
380
- ? _logger.startProgress ('Running "flutter pub $command " in ${_fileSystem .path .basename (directory )}...' ,)
381
- : null ;
397
+ _logger.printStatus ('Running "flutter pub $command " in ${_fileSystem .path .basename (directory )}...' );
382
398
final List <String > pubCommand = _pubCommand (arguments);
383
399
final Map <String , String > pubEnvironment = await _createPubEnvironment (context, flutterRootOverride);
384
400
try {
385
- do {
386
- output = < _OutputLine > [];
387
- attempts += 1 ;
388
- final io.Process process = await _processUtils.start (
401
+ final io.Process process;
402
+ final io.Stdio ? stdio = _stdio;
403
+
404
+ if (stdio != null ) {
405
+ // Omit mode parameter and direct pub output to [Pub._stdio] for tests.
406
+ process = await _processUtils.start (
389
407
pubCommand,
390
408
workingDirectory: _fileSystem.path.current,
391
409
environment: pubEnvironment,
392
410
);
393
- final StreamSubscription <String > stdoutSubscription =
394
- recordLines (process.stdout, _OutputStream .stdout);
395
- final StreamSubscription <String > stderrSubscription =
396
- recordLines (process.stderr, _OutputStream .stderr);
397
411
398
- exitCode = await process.exitCode;
412
+ final StreamSubscription <List <int >> stdoutSubscription =
413
+ process.stdout.listen (stdio.stdout.add);
414
+ final StreamSubscription <List <int >> stderrSubscription =
415
+ process.stderr.listen (stdio.stderr.add);
416
+
417
+ await Future .wait <void >(< Future <void >> [
418
+ stdoutSubscription.asFuture <void >(),
419
+ stderrSubscription.asFuture <void >(),
420
+ ]);
421
+
399
422
unawaited (stdoutSubscription.cancel ());
400
423
unawaited (stderrSubscription.cancel ());
424
+ } else {
425
+ // Let pub inherit stdio for normal operation.
426
+ process = await _processUtils.start (
427
+ pubCommand,
428
+ workingDirectory: _fileSystem.path.current,
429
+ environment: pubEnvironment,
430
+ mode: ProcessStartMode .inheritStdio,
431
+ );
432
+ }
401
433
402
- if (retry && exitCode == _kPubExitCodeUnavailable) {
403
- _logger.printStatus (
404
- '$failureMessage (server unavailable) -- attempting retry $attempts in $duration '
405
- 'second${ duration == 1 ? "" : "s" }...' ,
406
- );
407
- await Future <void >.delayed (Duration (seconds: duration));
408
- if (duration < 64 ) {
409
- duration *= 2 ;
410
- }
411
- // This will cause a retry.
412
- output = null ;
413
- }
414
- } while (output == null );
415
- status? .stop ();
434
+ exitCode = await process.exitCode;
416
435
// The exception is rethrown, so don't catch only Exceptions.
417
436
} catch (exception) { // ignore: avoid_catches_without_on_clauses
418
- status? .cancel ();
419
437
if (exception is io.ProcessException ) {
420
438
final StringBuffer buffer = StringBuffer ('${exception .message }\n ' );
421
439
final String directoryExistsMessage = _fileSystem.directory (directory).existsSync ()
@@ -434,40 +452,19 @@ class _DefaultPub implements Pub {
434
452
rethrow ;
435
453
}
436
454
437
- if (printProgress) {
438
- // Show the output of the last run.
439
- for (final _OutputLine line in output) {
440
- switch (line.stream) {
441
- case _OutputStream .stdout:
442
- _stdio.stdoutWrite ('${line .line }\n ' );
443
- break ;
444
- case _OutputStream .stderr:
445
- _stdio.stderrWrite ('${line .line }\n ' );
446
- break ;
447
- }
448
- }
449
- }
450
-
451
455
final int code = exitCode;
452
- String result = 'success' ;
453
- if (output.any ((_OutputLine line) => line.line.contains ('version solving failed' ))) {
454
- result = 'version-solving-failed' ;
455
- } else if (code != 0 ) {
456
- result = 'failure' ;
457
- }
456
+ final String result = code == 0 ? 'success' : 'failure' ;
458
457
PubResultEvent (
459
458
context: context.toAnalyticsString (),
460
459
result: result,
461
460
usage: _usage,
462
461
).send ();
463
- final String lastPubMessage = output.isEmpty ? 'no message' : output.last.line;
464
462
465
463
if (code != 0 ) {
466
464
final StringBuffer buffer = StringBuffer ('$failureMessage \n ' );
467
465
buffer.writeln ('command: "${pubCommand .join (' ' )}"' );
468
466
buffer.write (_stringifyPubEnv (pubEnvironment));
469
467
buffer.writeln ('exit code: $code ' );
470
- buffer.writeln ('last line of pub output: "${lastPubMessage .trim ()}"' );
471
468
throwToolExit (
472
469
buffer.toString (),
473
470
exitCode: code,
@@ -813,14 +810,3 @@ class _DefaultPub implements Pub {
813
810
return buffer.toString ();
814
811
}
815
812
}
816
-
817
- class _OutputLine {
818
- _OutputLine (this .line, this .stream);
819
- final String line;
820
- final _OutputStream stream;
821
- }
822
-
823
- enum _OutputStream {
824
- stdout,
825
- stderr,
826
- }
0 commit comments