flutter/packages/flutter_tools/lib/src/commands/upgrade.dart
Ian Hickson 449f4a6673
License update (#45373)
* Update project.pbxproj files to say Flutter rather than Chromium

Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright.

* Update the copyright notice checker to require a standard notice on all files

* Update copyrights on Dart files. (This was a mechanical commit.)

* Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine.

Some were already marked "The Flutter Authors", not clear why. Their
dates have been normalized. Some were missing the blank line after the
license. Some were randomly different in trivial ways for no apparent
reason (e.g. missing the trailing period).

* Clean up the copyrights in non-Dart files. (Manual edits.)

Also, make sure templates don't have copyrights.

* Fix some more ORGANIZATIONNAMEs
2019-11-27 15:04:02 -08:00

302 lines
9.8 KiB
Dart

// 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.
import 'dart:async';
import 'package:meta/meta.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../cache.dart';
import '../dart/pub.dart';
import '../globals.dart';
import '../persistent_tool_state.dart';
import '../runner/flutter_command.dart';
import '../version.dart';
import 'channel.dart';
class UpgradeCommand extends FlutterCommand {
UpgradeCommand([UpgradeCommandRunner commandRunner])
: _commandRunner = commandRunner ?? UpgradeCommandRunner() {
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.',
);
}
final UpgradeCommandRunner _commandRunner;
@override
final String name = 'upgrade';
@override
final String description = 'Upgrade your copy of Flutter.';
@override
bool get shouldUpdateCache => false;
@override
Future<FlutterCommandResult> runCommand() async {
await _commandRunner.runCommand(
boolArg('force'),
boolArg('continue'),
GitTagVersion.determine(),
FlutterVersion.instance,
);
return null;
}
}
@visibleForTesting
class UpgradeCommandRunner {
Future<FlutterCommandResult> runCommand(
bool force,
bool continueFlow,
GitTagVersion gitTagVersion,
FlutterVersion flutterVersion,
) async {
if (!continueFlow) {
await runCommandFirstHalf(force, gitTagVersion, flutterVersion);
} else {
await runCommandSecondHalf(flutterVersion);
}
return null;
}
Future<void> 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,
// explain that we are avoiding potential damage.
if (flutterVersion.channel != 'master' && FlutterVersion.officialChannels.contains(flutterVersion.channel)) {
throwToolExit(
'Unknown flutter tag. Abandoning upgrade to avoid destroying local '
'changes. It is recommended to use git directly if not working on '
'an official channel.'
);
// Otherwise explain that local changes can be lost.
} else {
throwToolExit(
'Unknown flutter tag. Abandoning upgrade to avoid destroying local '
'changes. If it is okay to remove local changes, then re-run this '
'command with --force.'
);
}
}
// If there are uncommitted changes we might be on the right commit but
// we should still warn.
if (!force && await hasUncomittedChanges()) {
throwToolExit(
'Your flutter checkout has local changes that would be erased by '
'upgrading. If you want to keep these changes, it is recommended that '
'you stash them via "git stash" or else commit the changes to a local '
'branch. If it is okay to remove local changes, then re-run this '
'command with --force.'
);
}
await resetChanges(gitTagVersion);
await upgradeChannel(flutterVersion);
await attemptFastForward();
await flutterUpgradeContinue();
}
Future<void> flutterUpgradeContinue() async {
final int code = await processUtils.stream(
<String>[
fs.path.join('bin', 'flutter'),
'upgrade',
'--continue',
'--no-version-check',
],
workingDirectory: Cache.flutterRoot,
allowReentrantFlutter: true,
environment: Map<String, String>.of(platform.environment),
);
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<void> runCommandSecondHalf(FlutterVersion flutterVersion) async {
// Make sure the welcome message re-display is delayed until the end.
persistentToolState.redisplayWelcomeMessage = false;
await precacheArtifacts();
await updatePackages(flutterVersion);
await runDoctor();
// Force the welcome message to re-display following the upgrade.
persistentToolState.redisplayWelcomeMessage = true;
}
Future<bool> hasUncomittedChanges() async {
try {
final RunResult result = await processUtils.run(
<String>['git', 'status', '-s'],
throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
return result.stdout.trim().isNotEmpty;
} on ProcessException catch (error) {
throwToolExit(
'The tool could not verify the status of the current flutter checkout. '
'This might be due to git not being installed or an internal error.'
'If it is okay to ignore potential local changes, then re-run this'
'command with --force.'
'\nError: $error.'
);
}
return false;
}
/// Check if there is an upstream repository configured.
///
/// Exits tool if there is no upstream.
Future<void> verifyUpstreamConfigured() async {
try {
await processUtils.run(
<String>[ 'git', 'rev-parse', '@{u}'],
throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
} catch (e) {
throwToolExit(
'Unable to upgrade Flutter: no origin repository configured. '
'Run \'git remote add origin '
'https://github.com/flutter/flutter\' in ${Cache.flutterRoot}',
);
}
}
/// Attempts to reset to the last known tag or branch. This should restore the
/// history to something that is compatible with the regular upgrade
/// process.
Future<void> resetChanges(GitTagVersion gitTagVersion) async {
// We only got here by using --force.
String tag;
if (gitTagVersion == const GitTagVersion.unknown()) {
tag = 'v0.0.0';
} else {
tag = 'v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}';
}
try {
await processUtils.run(
<String>['git', 'reset', '--hard', tag],
throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
} on ProcessException catch (error) {
throwToolExit(
'Unable to upgrade Flutter: The tool could not update to the version $tag. '
'This may be due to git not being installed or an internal error.'
'Please ensure that git is installed on your computer and retry again.'
'\nError: $error.'
);
}
}
/// Attempts to upgrade the channel.
///
/// If the user is on a deprecated channel, attempts to migrate them off of
/// it.
Future<void> upgradeChannel(FlutterVersion flutterVersion) async {
printStatus('Upgrading Flutter from ${Cache.flutterRoot}...');
await ChannelCommand.upgradeChannel();
}
/// Attempts to rebase the upstream onto the local branch.
///
/// If there haven't been any hot fixes or local changes, this is equivalent
/// to a fast-forward.
Future<void> attemptFastForward() async {
final int code = await processUtils.stream(
<String>['git', 'pull', '--ff'],
workingDirectory: Cache.flutterRoot,
mapFunction: (String line) => matchesGitLine(line) ? null : line,
);
if (code != 0) {
throwToolExit(null, exitCode: code);
}
}
/// Update the engine repository and precache all artifacts.
///
/// Check for and download any engine and pkg/ updates. We run the 'flutter'
/// shell script re-entrantly here so that it will download the updated
/// Dart and so forth if necessary.
Future<void> precacheArtifacts() async {
printStatus('');
printStatus('Upgrading engine...');
final int code = await processUtils.stream(
<String>[
fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache',
],
workingDirectory: Cache.flutterRoot,
allowReentrantFlutter: true,
environment: Map<String, String>.of(platform.environment),
);
if (code != 0) {
throwToolExit(null, exitCode: code);
}
}
/// Update the user's packages.
Future<void> updatePackages(FlutterVersion flutterVersion) async {
printStatus('');
printStatus(flutterVersion.toString());
final String projectRoot = findProjectRoot();
if (projectRoot != null) {
printStatus('');
await pub.get(context: PubContext.pubUpgrade, directory: projectRoot, upgrade: true, checkLastModified: false);
}
}
/// Run flutter doctor in case requirements have changed.
Future<void> runDoctor() async {
printStatus('');
printStatus('Running flutter doctor...');
await processUtils.stream(
<String>[
fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor',
],
workingDirectory: Cache.flutterRoot,
allowReentrantFlutter: true,
);
}
// dev/benchmarks/complex_layout/lib/main.dart | 24 +-
static final RegExp _gitDiffRegex = RegExp(r' (\S+)\s+\|\s+\d+ [+-]+');
// rename {packages/flutter/doc => dev/docs}/styles.html (92%)
// delete mode 100644 doc/index.html
// create mode 100644 examples/flutter_gallery/lib/gallery/demo.dart
static final RegExp _gitChangedRegex = RegExp(r' (rename|delete mode|create mode) .+');
static bool matchesGitLine(String line) {
return _gitDiffRegex.hasMatch(line)
|| _gitChangedRegex.hasMatch(line)
|| line == 'Fast-forward';
}
}