diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 51c7ee8f37..542ffba0a0 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -20,12 +20,19 @@ import 'channel.dart'; class UpgradeCommand extends FlutterCommand { UpgradeCommand() { - argParser.addFlag( - 'force', - abbr: 'f', - help: 'force upgrade the flutter branch, potentially discarding local changes.', - negatable: false, - ); + argParser + ..addFlag( + 'force', + abbr: 'f', + help: 'Force upgrade the flutter branch, potentially discarding local changes.', + negatable: false, + ) + ..addFlag( + 'continue', + hide: true, + negatable: false, + help: 'For the second half of the upgrade flow requiring the new version of Flutter. Should not be invoked manually, but re-entrantly by the standard upgrade command.', + ); } @override @@ -45,7 +52,12 @@ class UpgradeCommand extends FlutterCommand { @override Future runCommand() async { final UpgradeCommandRunner upgradeCommandRunner = UpgradeCommandRunner(); - await upgradeCommandRunner.runCommand(argResults['force'], GitTagVersion.determine(), FlutterVersion.instance); + await upgradeCommandRunner.runCommand( + argResults['force'], + argResults['continue'], + GitTagVersion.determine(), + FlutterVersion.instance, + ); return null; } } @@ -53,7 +65,25 @@ class UpgradeCommand extends FlutterCommand { @visibleForTesting class UpgradeCommandRunner { - Future runCommand(bool force, GitTagVersion gitTagVersion, FlutterVersion flutterVersion) async { + Future runCommand( + bool force, + bool continueFlow, + GitTagVersion gitTagVersion, + FlutterVersion flutterVersion, + ) async { + if (!continueFlow) { + await runCommandFirstHalf(force, gitTagVersion, flutterVersion); + } else { + await runCommandSecondHalf(flutterVersion); + } + return null; + } + + Future runCommandFirstHalf( + bool force, + GitTagVersion gitTagVersion, + FlutterVersion flutterVersion, + ) async { await verifyUpstreamConfigured(); if (!force && gitTagVersion == const GitTagVersion.unknown()) { // If the commit is a recognized branch and not master, @@ -87,10 +117,31 @@ class UpgradeCommandRunner { await resetChanges(gitTagVersion); await upgradeChannel(flutterVersion); await attemptFastForward(); + await flutterUpgradeContinue(); + } + + Future flutterUpgradeContinue() async { + final int code = await runCommandAndStreamOutput( + [ + fs.path.join('bin', 'flutter'), + 'upgrade', + '--continue', + '--no-version-check', + ], + workingDirectory: Cache.flutterRoot, + allowReentrantFlutter: true, + ); + if (code != 0) { + throwToolExit(null, exitCode: code); + } + } + + // This method should only be called if the upgrade command is invoked + // re-entrantly with the `--continue` flag + Future runCommandSecondHalf(FlutterVersion flutterVersion) async { await precacheArtifacts(); await updatePackages(flutterVersion); await runDoctor(); - return null; } Future hasUncomittedChanges() async { diff --git a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart index ac9879d9fa..499697dc7b 100644 --- a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; @@ -16,6 +18,21 @@ import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr = '' }) { + final Stream> stdoutStream = Stream>.fromIterable(>[ + utf8.encode(stdout), + ]); + final Stream> stderrStream = Stream>.fromIterable(>[ + utf8.encode(stderr), + ]); + final Process process = MockProcess(); + + when(process.stdout).thenAnswer((_) => stdoutStream); + when(process.stderr).thenAnswer((_) => stderrStream); + when(process.exitCode).thenAnswer((_) => Future.value(exitCode)); + return process; +} + void main() { group('UpgradeCommandRunner', () { FakeUpgradeCommandRunner fakeCommandRunner; @@ -29,11 +46,24 @@ void main() { fakeCommandRunner = FakeUpgradeCommandRunner(); realCommandRunner = UpgradeCommandRunner(); processManager = MockProcessManager(); + when(processManager.start( + [ + fs.path.join('bin', 'flutter'), + 'upgrade', + '--continue', + '--no-version-check', + ], + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((Invocation invocation) async { + return Future.value(createMockProcess()); + }); fakeCommandRunner.willHaveUncomittedChanges = false; }); test('throws on unknown tag, official branch, noforce', () async { final Future result = fakeCommandRunner.runCommand( + false, false, const GitTagVersion.unknown(), flutterVersion, @@ -41,18 +71,22 @@ void main() { expect(result, throwsA(isInstanceOf())); }); - test('does not throw on unknown tag, official branch, force', () async { + testUsingContext('does not throw on unknown tag, official branch, force', () async { final Future result = fakeCommandRunner.runCommand( true, + false, const GitTagVersion.unknown(), flutterVersion, ); expect(await result, null); + }, overrides: { + ProcessManager: () => processManager, }); test('throws tool exit with uncommitted changes', () async { fakeCommandRunner.willHaveUncomittedChanges = true; final Future result = fakeCommandRunner.runCommand( + false, false, gitTagVersion, flutterVersion, @@ -60,30 +94,37 @@ void main() { expect(result, throwsA(isA())); }); - test('does not throw tool exit with uncommitted changes and force', () async { + testUsingContext('does not throw tool exit with uncommitted changes and force', () async { fakeCommandRunner.willHaveUncomittedChanges = true; + final Future result = fakeCommandRunner.runCommand( true, - gitTagVersion, - flutterVersion, - ); - expect(await result, null); - }); - - test('Doesn\'t throw on known tag, dev branch, no force', () async { - final Future result = fakeCommandRunner.runCommand( false, gitTagVersion, flutterVersion, ); expect(await result, null); + }, overrides: { + ProcessManager: () => processManager, + }); + + testUsingContext('Doesn\'t throw on known tag, dev branch, no force', () async { + final Future result = fakeCommandRunner.runCommand( + false, + false, + gitTagVersion, + flutterVersion, + ); + expect(await result, null); + }, overrides: { + ProcessManager: () => processManager, }); testUsingContext('verifyUpstreamConfigured', () async { when(processManager.run( ['git', 'rev-parse', '@{u}'], environment:anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory')) + workingDirectory: anyNamed('workingDirectory')), ).thenAnswer((Invocation invocation) async { return FakeProcessResult() ..exitCode = 0; @@ -176,6 +217,7 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { } class MockFlutterVersion extends Mock implements FlutterVersion {} +class MockProcess extends Mock implements Process {} class MockProcessManager extends Mock implements ProcessManager {} class FakeProcessResult implements ProcessResult { @override