Skip to content

Commit a9e94d9

Browse files
authored
Do not silently fail pub get even if output-mode is "none" (#153596)
I am making an assumption `OutputMode.none` should _really_ mean `OutputMode.failuresOnly`, that is, if we ever get a non-zero exit code, we still want to know why. If I've somehow misunderstood that, LMK and I'm happy to revert this PR or make adjustments. This fixes the bug where if you were to do: ```sh git clone https://github.com/myuser/fork-of-flutter cd fork-of-flutter ./bin/flutter update-packages ``` You now get: 1. An actual error message, versus no output at all. 2. A warning that a common reason is not tracking a remote, with instructions to fix it. Closes #148569.
1 parent 544ce7c commit a9e94d9

File tree

6 files changed

+100
-19
lines changed

6 files changed

+100
-19
lines changed

packages/flutter_tools/lib/src/commands/update_packages.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,15 +524,13 @@ class UpdatePackagesCommand extends FlutterCommand {
524524

525525
// Run "pub get" on it in order to force the download of any
526526
// needed packages to the pub cache, upgrading if requested.
527-
// TODO(ianh): If this fails, the tool exits silently.
528-
// It can fail, e.g., if --cherry-pick-version is invalid.
529527
await pub.get(
530528
context: PubContext.updatePackages,
531529
project: FlutterProject.fromDirectory(syntheticPackageDir),
532530
upgrade: doUpgrade,
533531
offline: boolArg('offline'),
534532
flutterRootOverride: temporaryFlutterSdk?.path,
535-
outputMode: PubOutputMode.none,
533+
outputMode: PubOutputMode.failuresOnly,
536534
);
537535

538536
if (reportDependenciesToTree) {
@@ -615,7 +613,7 @@ class UpdatePackagesCommand extends FlutterCommand {
615613
// All dependencies should already have been downloaded by the fake
616614
// package, so the concurrent checks can all happen offline.
617615
offline: true,
618-
outputMode: PubOutputMode.none,
616+
outputMode: PubOutputMode.failuresOnly,
619617
);
620618
stopwatch.stop();
621619
final double seconds = stopwatch.elapsedMilliseconds / 1000.0;

packages/flutter_tools/lib/src/dart/pub.dart

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '../convert.dart';
2222
import '../dart/package_map.dart';
2323
import '../project.dart';
2424
import '../reporting/reporting.dart';
25+
import '../version.dart';
2526

2627
/// The [Pub] instance.
2728
Pub get pub => context.get<Pub>()!;
@@ -91,13 +92,16 @@ class PubContext {
9192
}
9293

9394
/// Describes the amount of output that should get printed from a `pub` command.
94-
/// [PubOutputMode.all] indicates that the complete output is printed. This is
95-
/// typically the default.
96-
/// [PubOutputMode.none] indicates that no output should be printed.
97-
/// [PubOutputMode.summaryOnly] indicates that only summary information should be printed.
9895
enum PubOutputMode {
99-
none,
96+
/// No normal output should be printed.
97+
///
98+
/// If the command were to fail, failures are still printed.
99+
failuresOnly,
100+
101+
/// The complete output should be printed; this is typically the default.
100102
all,
103+
104+
/// Only summary information should be printed.
101105
summaryOnly,
102106
}
103107

@@ -392,8 +396,9 @@ class _DefaultPub implements Pub {
392396
final List<String> pubCommand = <String>[..._pubCommand, ...arguments];
393397
final Map<String, String> pubEnvironment = await _createPubEnvironment(context: context, flutterRootOverride: flutterRootOverride, summaryOnly: outputMode == PubOutputMode.summaryOnly);
394398

399+
String? pubStderr;
395400
try {
396-
if (outputMode != PubOutputMode.none) {
401+
if (outputMode != PubOutputMode.failuresOnly) {
397402
final io.Stdio? stdio = _stdio;
398403
if (stdio == null) {
399404
// Let pub inherit stdio and output directly to the tool's stdout and
@@ -447,6 +452,7 @@ class _DefaultPub implements Pub {
447452
);
448453

449454
exitCode = result.exitCode;
455+
pubStderr = result.stderr;
450456
}
451457
} on io.ProcessException catch (exception) {
452458
final StringBuffer buffer = StringBuffer('${exception.message}\n');
@@ -477,7 +483,27 @@ class _DefaultPub implements Pub {
477483
buffer.write(_stringifyPubEnv(pubEnvironment));
478484
buffer.writeln('exit code: $code');
479485
_logger.printTrace(buffer.toString());
480-
throwToolExit(null, exitCode: code);
486+
487+
// When this is null, but a failure happened, it is assumed that stderr
488+
// was already redirected to the process stderr. This handles the corner
489+
// case where we otherwise would log nothing. See
490+
// https://github.com/flutter/flutter/issues/148569 for details.
491+
if (pubStderr != null) {
492+
_logger.printError(pubStderr);
493+
}
494+
if (context == PubContext.updatePackages) {
495+
_logger.printWarning(
496+
'If the current version was resolved as $kUnknownFrameworkVersion '
497+
'and this is a fork of flutter/flutter, you forgot to set the remote '
498+
'upstream branch to point to the canonical flutter/flutter: \n\n'
499+
' git remote set-url upstream https://github.com/flutter/flutter.git\n'
500+
'\n'
501+
'See https://github.com/flutter/flutter/blob/main/docs/contributing/Setting-up-the-Framework-development-environment.md#set-up-your-environment.');
502+
}
503+
throwToolExit(
504+
'Failed to update packages.',
505+
exitCode: code,
506+
);
481507
}
482508
}
483509

packages/flutter_tools/lib/src/flutter_cache.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class PubDependencies extends ArtifactSet {
129129
fileSystem.directory(fileSystem.path.join(_flutterRoot(), 'packages', 'flutter_tools'))
130130
),
131131
offline: offline,
132-
outputMode: PubOutputMode.none,
132+
outputMode: PubOutputMode.failuresOnly,
133133
);
134134
}
135135
}

packages/flutter_tools/lib/src/version.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import 'cache.dart';
1515
import 'convert.dart';
1616
import 'globals.dart' as globals;
1717

18-
const String _unknownFrameworkVersion = '0.0.0-unknown';
18+
/// The default version when a version could not be determined.
19+
const String kUnknownFrameworkVersion = '0.0.0-unknown';
1920

2021
/// A git shortcut for the branch that is being tracked by the current one.
2122
///
@@ -228,7 +229,7 @@ abstract class FlutterVersion {
228229

229230
@override
230231
String toString() {
231-
final String versionText = frameworkVersion == _unknownFrameworkVersion ? '' : ' $frameworkVersion';
232+
final String versionText = frameworkVersion == kUnknownFrameworkVersion ? '' : ' $frameworkVersion';
232233
final String flutterText = 'Flutter$versionText • channel $channel • ${repositoryUrl ?? 'unknown source'}';
233234
final String frameworkText = 'Framework • revision $frameworkRevisionShort ($frameworkAge) • $frameworkCommitDate';
234235
final String engineText = 'Engine • revision $engineRevisionShort';
@@ -359,7 +360,7 @@ abstract class FlutterVersion {
359360

360361
/// Return a short string for the version (e.g. `master/0.0.59-pre.92`, `scroll_refactor/a76bc8e22b`).
361362
String getVersionString({ bool redactUnknownBranches = false }) {
362-
if (frameworkVersion != _unknownFrameworkVersion) {
363+
if (frameworkVersion != kUnknownFrameworkVersion) {
363364
return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkVersion';
364365
}
365366
return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevisionShort';
@@ -1053,7 +1054,7 @@ class GitTagVersion {
10531054

10541055
String frameworkVersionFor(String revision) {
10551056
if (x == null || y == null || z == null || (hash != null && !revision.startsWith(hash!))) {
1056-
return _unknownFrameworkVersion;
1057+
return kUnknownFrameworkVersion;
10571058
}
10581059
if (commits == 0 && gitTag != null) {
10591060
return gitTag!;

packages/flutter_tools/test/general.shard/cache_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,7 @@ void main() {
10391039
expect(
10401040
pub.invocations.first,
10411041
predicate<FakePubInvocation>(
1042-
(FakePubInvocation invocation) => invocation.outputMode == PubOutputMode.none,
1042+
(FakePubInvocation invocation) => invocation.outputMode == PubOutputMode.failuresOnly,
10431043
'Pub invoked with PubOutputMode.none',
10441044
),
10451045
);

packages/flutter_tools/test/general.shard/dart/pub_get_test.dart

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ exit code: 66
616616
context: PubContext.flutterTests,
617617
),
618618
throwsA(isA<ToolExit>()
619-
.having((ToolExit error) => error.message, 'message', null)),
619+
.having((ToolExit error) => error.message, 'message', contains('Failed to update packages'))),
620620
);
621621
expect(logger.statusText, isEmpty);
622622
expect(logger.traceText, contains(toolExitMessage));
@@ -629,6 +629,62 @@ exit code: 66
629629
expect(processManager, hasNoRemainingExpectations);
630630
});
631631

632+
testWithoutContext('pub get with failing exit code even with OutputMode == failuresOnly', () async {
633+
final BufferLogger logger = BufferLogger.test();
634+
final FileSystem fileSystem = MemoryFileSystem.test();
635+
636+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
637+
const FakeCommand(
638+
command: <String>[
639+
'bin/cache/dart-sdk/bin/dart',
640+
'pub',
641+
'--suppress-analytics',
642+
'--directory',
643+
'.',
644+
'get',
645+
'--example',
646+
],
647+
exitCode: 1,
648+
stderr: '===pub get failed stderr here===',
649+
stdout: 'out1\nout2\nout3\n',
650+
environment: <String, String>{
651+
'FLUTTER_ROOT': '',
652+
'PUB_ENVIRONMENT': 'flutter_cli:update_packages'
653+
},
654+
),
655+
]);
656+
657+
// Intentionally not using pub.test to simulate a real environment, but
658+
// we are using non-inherited I/O to avoid printing to the console.
659+
final Pub pub = Pub(
660+
platform: FakePlatform(),
661+
fileSystem: fileSystem,
662+
logger: logger,
663+
usage: TestUsage(),
664+
botDetector: const BotDetectorAlwaysNo(),
665+
processManager: processManager,
666+
);
667+
668+
await expectLater(
669+
() => pub.get(
670+
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
671+
context: PubContext.updatePackages,
672+
outputMode: PubOutputMode.failuresOnly,
673+
),
674+
throwsToolExit(
675+
message: 'Failed to update packages',
676+
),
677+
);
678+
expect(logger.statusText, isEmpty);
679+
expect(logger.errorText, contains('===pub get failed stderr here==='));
680+
expect(
681+
logger.warningText,
682+
contains('git remote set-url upstream'),
683+
reason: 'When update-packages fails, it is often because of missing an upsteam remote.',
684+
);
685+
expect(processManager, hasNoRemainingExpectations);
686+
});
687+
632688
testWithoutContext('pub get shows working directory on process exception',
633689
() async {
634690
final BufferLogger logger = BufferLogger.test();
@@ -745,7 +801,7 @@ exit code: 66
745801
await pub.get(
746802
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
747803
context: PubContext.flutterTests,
748-
outputMode: PubOutputMode.none,
804+
outputMode: PubOutputMode.failuresOnly,
749805
);
750806
} on ToolExit {
751807
// Ignore.

0 commit comments

Comments
 (0)