From 9e42e4b88f5a4297615927a6cabfa6f4d56a122b Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 18 Jan 2018 07:59:06 -0800 Subject: [PATCH] New release process (#14061) Generate the "version" file from git tags. Remove the old VERSION file and mentions of versions in pubspec.yaml files. Replace the old update_versions.dart script with a new roll_dev.dart script. Update "flutter channel". Update "flutter upgrade", including making it transition from alpha to dev. Update "flutter --version" and "flutter doctor". --- .gitignore | 1 + .travis.yml | 34 +-- VERSION | 9 - bin/flutter | 1 + bin/flutter.bat | 1 + dev/devicelab/pubspec.yaml | 1 - dev/tools/dartdoc.dart | 25 ++- dev/tools/lib/roll_dev.dart | 158 ++++++++++++++ dev/tools/update_versions.dart | 205 ------------------ packages/flutter/pubspec.yaml | 1 - packages/flutter_driver/pubspec.yaml | 1 - packages/flutter_localizations/pubspec.yaml | 1 - packages/flutter_test/pubspec.yaml | 1 - packages/flutter_tools/lib/executable.dart | 2 +- .../lib/src/commands/channel.dart | 56 ++++- .../lib/src/commands/upgrade.dart | 3 + packages/flutter_tools/lib/src/doctor.dart | 2 +- .../src/runner/flutter_command_runner.dart | 1 + packages/flutter_tools/lib/src/usage.dart | 4 +- packages/flutter_tools/lib/src/version.dart | 112 ++++++++-- 20 files changed, 340 insertions(+), 279 deletions(-) delete mode 100644 VERSION create mode 100644 dev/tools/lib/roll_dev.dart delete mode 100644 dev/tools/update_versions.dart diff --git a/.gitignore b/.gitignore index 5d0e2f0576..e8f27b1d28 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ /dev/docs/lib/ /dev/docs/pubspec.yaml /packages/flutter/coverage/ +version # Flutter/Dart/Pub related **/doc/api/ diff --git a/.travis.yml b/.travis.yml index d3f9d9c438..51dccfd78c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,21 @@ +# ENVIRONMENTS os: - linux - osx +env: + - SHARD=analyze + - SHARD=tests + - SHARD=docs +matrix: + exclude: + - os: osx + env: SHARD=analyze + - os: osx + env: SHARD=docs sudo: false filter_secrets: false + +# INSTALLATION addons: apt: # sky_shell binary depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 @@ -14,23 +27,18 @@ addons: language: node_js node_js: - "6" +git: + # We rely on git tags for determining the version. + depth: false +cache: + directories: + - $HOME/.pub-cache install: - ./dev/bots/travis_install.sh -env: - - SHARD=analyze - - SHARD=tests - - SHARD=docs + +# TESTING before_script: - ./dev/bots/travis_setup.sh script: - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976 - (./bin/cache/dart-sdk/bin/dart ./dev/bots/test.dart && ./dev/bots/travis_upload.sh) -cache: - directories: - - $HOME/.pub-cache -matrix: - exclude: - - os: osx - env: SHARD=analyze - - os: osx - env: SHARD=docs diff --git a/VERSION b/VERSION deleted file mode 100644 index 5fbe99ddc2..0000000000 --- a/VERSION +++ /dev/null @@ -1,9 +0,0 @@ -# This file defines a semantic version for the Flutter SDK. This version will -# describes breaking changes in the SDK as a whole, separately from the -# individual packages in the SDK. For example, if the flutter command line -# tool's options change in an incompatible way, this version will update to -# reflect that change. However, if the API for package:flutter changes in an -# incompatible way, this version number might not change. Instead, the version -# number for package:flutter will update to reflect that change. - -0.0.21-dev diff --git a/bin/flutter b/bin/flutter index d068fcbbda..d6b607236c 100755 --- a/bin/flutter +++ b/bin/flutter @@ -73,6 +73,7 @@ function upgrade_flutter () { local revision=`(cd "$FLUTTER_ROOT"; git rev-parse HEAD)` if [ ! -f "$SNAPSHOT_PATH" ] || [ ! -s "$STAMP_PATH" ] || [ `cat "$STAMP_PATH"` != "$revision" ] || [ "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]; then + rm -f "$FLUTTER_ROOT/version" mkdir -p "$FLUTTER_ROOT/bin/cache" touch "$FLUTTER_ROOT/bin/cache/.dartignore" "$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh" diff --git a/bin/flutter.bat b/bin/flutter.bat index f69b277592..da43e491af 100644 --- a/bin/flutter.bat +++ b/bin/flutter.bat @@ -93,6 +93,7 @@ GOTO :after_subroutine ) :do_snapshot + IF EXIST "%FLUTTER_ROOT%\version" DEL "%FLUTTER_ROOT%\version" ECHO: > "%cache_dir%\.dartignore" ECHO Updating flutter tool... PUSHD "%flutter_tools_dir%" diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 20370b13f6..4929f3a276 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -1,5 +1,4 @@ name: flutter_devicelab -version: 0.0.1 author: Flutter Authors description: Flutter continuous integration performance and correctness tests. homepage: https://github.com/flutter/flutter diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart index dc50387706..fabc95b294 100644 --- a/dev/tools/dartdoc.dart +++ b/dev/tools/dartdoc.dart @@ -8,7 +8,6 @@ import 'dart:io'; import 'package:intl/intl.dart'; import 'package:path/path.dart' as path; -import 'update_versions.dart'; /// Whether to report all error messages (true) or attempt to filter out some /// known false positives (false). @@ -36,15 +35,18 @@ Future main(List args) async { if (path.basename(Directory.current.path) == 'tools') Directory.current = Directory.current.parent.parent; - final RawVersion version = new RawVersion('VERSION'); + final ProcessResult flutter = Process.runSync('flutter', []); + final File versionFile = new File('version'); + if (flutter.exitCode != 0 || !versionFile.existsSync()) + throw new Exception('Failed to determine Flutter version.'); + final String version = versionFile.readAsStringSync(); // Create the pubspec.yaml file. - final StringBuffer buf = new StringBuffer(''' -name: Flutter -homepage: https://flutter.io -version: $version -dependencies: -'''); + final StringBuffer buf = new StringBuffer(); + buf.writeln('name: Flutter'); + buf.writeln('homepage: https://flutter.io'); + buf.writeln('version: $version'); + buf.writeln('dependencies:'); for (String package in findPackageNames()) { buf.writeln(' $package:'); buf.writeln(' sdk: flutter'); @@ -100,7 +102,7 @@ dependencies: workingDirectory: 'dev/docs', environment: pubEnvironment, ); - print('\n${result.stdout}'); + print('\n${result.stdout}flutter version: $version\n'); // Generate the documentation. final List args = [ @@ -159,7 +161,10 @@ void createFooter(String footerPath) { const int kGitRevisionLength = 10; final ProcessResult gitResult = Process.runSync('git', ['rev-parse', 'HEAD']); - String gitRevision = (gitResult.exitCode == 0) ? gitResult.stdout.trim() : 'unknown'; + if (gitResult.exitCode != 0) + throw 'git exit with non-zero exit code: ${gitResult.exitCode}'; + String gitRevision = gitResult.stdout.trim(); + gitRevision = gitRevision.length > kGitRevisionLength ? gitRevision.substring(0, kGitRevisionLength) : gitRevision; final String timestamp = new DateFormat('yyyy-MM-dd HH:mm').format(new DateTime.now()); diff --git a/dev/tools/lib/roll_dev.dart b/dev/tools/lib/roll_dev.dart new file mode 100644 index 0000000000..1512363fb4 --- /dev/null +++ b/dev/tools/lib/roll_dev.dart @@ -0,0 +1,158 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Rolls the dev channel. +// Only tested on Linux. +// +// See: https://github.com/flutter/flutter/wiki/Release-process + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:path/path.dart' as path; + +const String kIncrement = 'increment'; +const String kX = 'x'; +const String kY = 'y'; +const String kZ = 'z'; +const String kHelp = 'help'; + +void main(List args) { + // If we're run from the `tools` dir, set the cwd to the repo root. + if (path.basename(Directory.current.path) == 'tools') + Directory.current = Directory.current.parent.parent; + + final ArgParser argParser = new ArgParser(allowTrailingOptions: false); + argParser.addOption( + kIncrement, + help: 'Specifies which part of the x.y.z version number to increment. Required.', + valueHelp: 'level', + allowed: [kX, kY, kZ], + allowedHelp: { + kX: 'Indicates a major development, e.g. typically changed after a big press event.', + kY: 'Indicates a minor development, e.g. typically changed after a beta release.', + kZ: 'Indicates the least notable level of change. You normally want this.', + }, + ); + argParser.addFlag(kHelp, negatable: false, help: 'Show this help message.', hide: true); + ArgResults argResults; + try { + argResults = argParser.parse(args); + } on ArgParserException catch (error) { + print(error.message); + print(argParser.usage); + exit(1); + } + + final String level = argResults[kIncrement]; + final bool help = argResults[kHelp]; + + if (help || level == null) { + print('roll_dev.dart --increment=x • update the version tags and roll a new dev build.\n'); + print(argParser.usage); + exit(0); + } + + runGit('checkout master', 'switch to master branch'); + + if (getGitOutput('status --porcelain', 'check status of your local checkout') != '') { + print('Your git repository is not clean. Try running "git clean -fd". Warning, this '); + print('will delete files! Run with -n to find out which ones.'); + exit(1); + } + + String version = getFullTag(); + final Match match = parseFullTag(version); + if (match == null) { + print('Could not determine the version for this build.'); + if (version.isNotEmpty) + print('Git reported the latest version as "$version", which does not fit the expected pattern.'); + exit(1); + } + + final List parts = match.groups([1, 2, 3]).map(int.parse).toList(); + + if (match.group(4) == '0') { + print('This commit has already been released, as version ${parts.join(".")}.'); + exit(0); + } + + switch (level) { + case kX: + parts[0] += 1; + parts[1] = 0; + parts[2] = 0; + break; + case kY: + parts[1] += 1; + parts[2] = 0; + break; + case kZ: + parts[2] += 1; + break; + default: + print('Unknown increment level. The valid values are "$kX", "$kY", and "$kZ".'); + exit(1); + } + version = parts.join('.'); + + runGit('fetch upstream', 'fetch upstream'); + runGit('reset upstream/master --hard', 'check out master branch'); + runGit('tag $version', 'tag the commit with the version label'); + + print('Your tree is ready to publish Flutter $version to the "dev" channel.'); + stdout.write('Are you? [yes/no] '); + if (stdin.readLineSync() != 'yes') { + runGit('tag -d $version', 'remove the tag you did not want to publish'); + print('The dev roll has been aborted.'); + exit(0); + } + + runGit('push upstream $version', 'publish the version'); + runGit('push upstream HEAD:dev', 'land the new version on the "dev" branch'); + print('Flutter version $version has been rolled to the "dev" channel!'); +} + +String getFullTag() { + return getGitOutput( + 'describe --match v*.*.* --exclude *-* --first-parent --long --tags', + 'obtain last released version number', + ); +} + +Match parseFullTag(String version) { + final RegExp versionPattern = new RegExp('^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-g([a-f0-9]+)\$'); + return versionPattern.matchAsPrefix(version); +} + +String getGitOutput(String command, String explanation) { + final ProcessResult result = _runGit(command); + if (result.stderr.isEmpty && result.exitCode == 0) + return result.stdout.trim(); + _reportGitFailureAndExit(result, explanation); + return null; // for the analyzer's sake +} + +void runGit(String command, String explanation) { + final ProcessResult result = _runGit(command); + if (result.exitCode != 0) + _reportGitFailureAndExit(result, explanation); +} + +ProcessResult _runGit(String command) { + return Process.runSync('git', command.split(' ')); +} + +void _reportGitFailureAndExit(ProcessResult result, String explanation) { + if (result.exitCode != 0) { + print('Failed to $explanation. Git exitted with error code ${result.exitCode}.'); + } else { + print('Failed to $explanation.'); + } + if (result.stdout.isNotEmpty) + print('stdout from git:\n${result.stdout}\n'); + if (result.stderr.isNotEmpty) + print('stderr from git:\n${result.stderr}\n'); + exit(1); +} diff --git a/dev/tools/update_versions.dart b/dev/tools/update_versions.dart deleted file mode 100644 index 8f62bccf49..0000000000 --- a/dev/tools/update_versions.dart +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Updates the version numbers of the Flutter repo. -// Only tested on Linux. -// -// See: https://github.com/flutter/flutter/wiki/Release-process - -import 'dart:io'; - -import 'package:args/args.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; - -const String kIncrement = 'increment'; -const String kBrokeSdk = 'broke-sdk'; -const String kBrokeFramework = 'broke-framework'; -const String kBrokeTest = 'broke-test'; -const String kBrokeDriver = 'broke-driver'; -const String kMarkRelease = 'release'; -const String kHelp = 'help'; - -const String kYamlVersionPrefix = 'version: '; -const String kDev = '-dev'; - -enum VersionKind { dev, release } - -void main(List args) { - // If we're run from the `tools` dir, set the cwd to the repo root. - if (path.basename(Directory.current.path) == 'tools') - Directory.current = Directory.current.parent.parent; - - final ArgParser argParser = new ArgParser(); - argParser.addFlag(kIncrement, defaultsTo: false, help: 'Increment all the version numbers. Cannot be specified with --$kMarkRelease or with any --broke-* commands.'); - argParser.addFlag(kBrokeSdk, defaultsTo: false, negatable: false, help: 'Increment the Flutter SDK version number to indicate that there has been a breaking change to the SDK (for example, to the command line options).'); - argParser.addFlag(kBrokeFramework, defaultsTo: false, negatable: false, help: 'Increment the "flutter" package version number to indicate that there has been a breaking change to the Flutter framework.'); - argParser.addFlag(kBrokeTest, defaultsTo: false, negatable: false, help: 'Increment the "flutter_test" package version number to indicate that there has been a breaking change to the test API framework.'); - argParser.addFlag(kBrokeDriver, defaultsTo: false, negatable: false, help: 'Increment the "flutter_driver" package version number to indicate that there has been a breaking change to the driver API framework.'); - argParser.addFlag(kMarkRelease, defaultsTo: false, help: 'Remove "-dev" from each version number. This is used when releasing. When not present, "-dev" is added to each version number. Cannot be specified with --$kIncrement or with any --broke-* commands.'); - argParser.addFlag(kHelp, negatable: false, help: 'Show this help message.'); - final ArgResults argResults = argParser.parse(args); - - final bool increment = argResults[kIncrement]; - final bool brokeSdk = argResults[kBrokeSdk]; - final bool brokeFramework = argResults[kBrokeFramework]; - final bool brokeTest = argResults[kBrokeTest]; - final bool brokeDriver = argResults[kBrokeDriver]; - final bool brokeAnything = brokeSdk || brokeFramework || brokeTest || brokeDriver; - final VersionKind level = argResults[kMarkRelease] ? VersionKind.release : VersionKind.dev; - final bool help = argResults[kHelp]; - - if (help) { - print('update_versions.dart - update version numbers of Flutter packages and SDK'); - print(argParser.usage); - exit(0); - } - - final bool release = level == VersionKind.release; - if ((brokeAnything && release) || (brokeAnything && increment) || (release && increment)) { - print('You can either increment all the version numbers (--$kIncrement), indicate that some packages have had breaking changes (--broke-*), or switch to release mode (--$kMarkRelease).'); - print('You cannot combine these, however.'); - exit(1); - } - - final RawVersion sdk = new RawVersion('VERSION'); - final PubSpecVersion framework = new PubSpecVersion('packages/flutter/pubspec.yaml'); - final PubSpecVersion test = new PubSpecVersion('packages/flutter_test/pubspec.yaml'); - final PubSpecVersion driver = new PubSpecVersion('packages/flutter_driver/pubspec.yaml'); - - if (increment || brokeAnything) - sdk.increment(brokeAnything); - sdk.setMode(level); - - if (increment || brokeFramework) - framework.increment(brokeFramework); - framework.setMode(level); - - if (increment || brokeTest) - test.increment(brokeTest); - test.setMode(level); - - if (increment || brokeDriver) - driver.increment(brokeDriver); - driver.setMode(level); - - sdk.write(); - framework.write(); - test.write(); - driver.write(); - - print('Flutter SDK is now at version: $sdk'); - print('flutter package is now at version: $framework'); - print('flutter_test package is now at version: $test'); - print('flutter_driver package is now at version: $driver'); - - if (release) { - print('\nDuring the tagging step in the instructions, the commands will be:'); - print('git tag $sdk'); - print('git push upstream $sdk'); - } -} - -abstract class Version { - Version() { - read(); - } - - @protected - final List version = []; - - @protected - VersionKind level; - - @protected - bool dirty = false; - - @protected - void read(); - - void interpret(String value) { - level = value.endsWith(kDev) ? VersionKind.dev : VersionKind.release; - if (level == VersionKind.dev) - value = value.substring(0, value.length - kDev.length); - version.addAll(value.split('.').map(int.parse)); - } - - void increment(bool breaking) { - assert(version.length == 3); - if (breaking) { - version[1] += 1; - version[2] = 0; - } else { - version[2] += 1; - } - dirty = true; - } - - void setMode(VersionKind value) { - if (value != level) { - level = value; - dirty = true; - } - } - - void write(); - - @override - String toString() => version.join('.') + (level == VersionKind.dev ? kDev : ''); -} - -class PubSpecVersion extends Version { - PubSpecVersion(this.path); - - final String path; - - @override - void read() { - final List lines = new File(path).readAsLinesSync(); - final String versionLine = lines.where((String line) => line.startsWith(kYamlVersionPrefix)).single; - interpret(versionLine.substring(kYamlVersionPrefix.length)); - } - - @override - void write() { - if (!dirty) - return; - final List lines = new File(path).readAsLinesSync(); - for (int index = 0; index < lines.length; index += 1) { - final String line = lines[index]; - if (line.startsWith(kYamlVersionPrefix)) { - lines[index] = '$kYamlVersionPrefix$this'; - break; - } - } - new File(path).writeAsStringSync(lines.join('\n') + '\n'); - } -} - -class RawVersion extends Version { - RawVersion(this.path); - - final String path; - - @override - void read() { - final List lines = new File(path).readAsLinesSync(); - interpret(lines.where((String line) => line.isNotEmpty && !line.startsWith('#')).single); - } - - @override - void write() { - if (!dirty) - return; - final List lines = new File(path).readAsLinesSync(); - for (int index = 0; index < lines.length; index += 1) { - final String line = lines[index]; - if (line.isNotEmpty && !line.startsWith('#')) { - lines[index] = '$this'; - break; - } - } - new File(path).writeAsStringSync(lines.join('\n') + '\n'); - } -} diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index f71118edf8..57e8d98752 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -1,5 +1,4 @@ name: flutter -version: 0.0.41-dev author: Flutter Authors description: A framework for writing Flutter applications homepage: http://flutter.io diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index 78686912a7..4a94b49f9b 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -1,5 +1,4 @@ name: flutter_driver -version: 0.0.19-dev description: Integration and performance test API for Flutter applications homepage: http://flutter.io author: Flutter Authors diff --git a/packages/flutter_localizations/pubspec.yaml b/packages/flutter_localizations/pubspec.yaml index aa1f228fb5..298441ab8f 100644 --- a/packages/flutter_localizations/pubspec.yaml +++ b/packages/flutter_localizations/pubspec.yaml @@ -1,5 +1,4 @@ name: flutter_localizations -version: 0.0.1-dev dependencies: # To update these, use "flutter update-packages --force-upgrade". diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 4367aa0584..f42bfeed89 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -1,5 +1,4 @@ name: flutter_test -version: 0.0.19-dev dependencies: # To update these, use "flutter update-packages --force-upgrade". diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index d20863c46c..c3e38357d8 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -44,7 +44,7 @@ Future main(List args) async { await runner.run(args, [ new AnalyzeCommand(verboseHelp: verboseHelp), new BuildCommand(verboseHelp: verboseHelp), - new ChannelCommand(), + new ChannelCommand(verboseHelp: verboseHelp), new CleanCommand(), new InjectPluginsCommand(hidden: !verboseHelp), new ConfigCommand(verboseHelp: verboseHelp), diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart index 9b1ec42bb0..4c1367da2f 100644 --- a/packages/flutter_tools/lib/src/commands/channel.dart +++ b/packages/flutter_tools/lib/src/commands/channel.dart @@ -9,8 +9,19 @@ import '../base/process.dart'; import '../cache.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; +import '../version.dart'; class ChannelCommand extends FlutterCommand { + ChannelCommand({ bool verboseHelp: false }) { + argParser.addFlag( + 'all', + abbr: 'a', + help: 'Include all the available branches (including local branches) when listing channels.', + defaultsTo: false, + hide: !verboseHelp, + ); + } + @override final String name = 'channel'; @@ -24,7 +35,7 @@ class ChannelCommand extends FlutterCommand { Future runCommand() { switch (argResults.rest.length) { case 0: - return _listChannels(); + return _listChannels(showAll: argResults['all']); case 1: return _switchChannel(argResults.rest[0]); default: @@ -32,10 +43,12 @@ class ChannelCommand extends FlutterCommand { } } - Future _listChannels() async { - final String currentBranch = runSync( - ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], - workingDirectory: Cache.flutterRoot); + Future _listChannels({ bool showAll }) async { + // Beware: currentBranch could contain PII. See getBranchName(). + final String currentChannel = FlutterVersion.instance.channel; + final String currentBranch = FlutterVersion.instance.getBranchName(); + + showAll = showAll || currentChannel != currentBranch; printStatus('Flutter channels:'); final int result = await runCommandAndStreamOutput( @@ -46,24 +59,45 @@ class ChannelCommand extends FlutterCommand { if (split.length < 2) return null; final String branchName = split[1]; - if (branchName.startsWith('HEAD')) - return null; if (branchName == currentBranch) return '* $branchName'; - return ' $branchName'; + if (!branchName.startsWith('HEAD ') && + (showAll || FlutterVersion.officialChannels.contains(branchName))) + return ' $branchName'; + return null; }, ); if (result != 0) throwToolExit('List channels failed: $result', exitCode: result); } - Future _switchChannel(String branchName) async { - printStatus('Switching to flutter channel named $branchName'); + Future _switchChannel(String branchName) { + printStatus("Switching to flutter channel '$branchName'..."); + if (FlutterVersion.obsoleteBranches.containsKey(branchName)) { + final String alternative = FlutterVersion.obsoleteBranches[branchName]; + printStatus("This channel is obsolete. Consider switching to the '$alternative' channel instead."); + } else if (!FlutterVersion.officialChannels.contains(branchName)) { + printStatus('This is not an official channel. For a list of available channels, try "flutter channel".'); + } + return _checkout(branchName); + } + + static Future upgradeChannel() async { + final String channel = FlutterVersion.instance.channel; + if (FlutterVersion.obsoleteBranches.containsKey(channel)) { + final String alternative = FlutterVersion.obsoleteBranches[channel]; + printStatus("Transitioning from '$channel' to '$alternative'..."); + return _checkout(alternative); + } + } + + static Future _checkout(String branchName) async { final int result = await runCommandAndStreamOutput( ['git', 'checkout', branchName], workingDirectory: Cache.flutterRoot, + prefix: 'git: ', ); if (result != 0) - throwToolExit('Switch channel failed: $result', exitCode: result); + throwToolExit('Switching channels failed with error code $result.', exitCode: result); } } diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 77ac728494..bce432b3e4 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -14,6 +14,7 @@ import '../doctor.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; import '../version.dart'; +import 'channel.dart'; class UpgradeCommand extends FlutterCommand { @override @@ -39,6 +40,8 @@ class UpgradeCommand extends FlutterCommand { printStatus('Upgrading Flutter from ${Cache.flutterRoot}...'); + await ChannelCommand.upgradeChannel(); + int code = await runCommandAndStreamOutput( ['git', 'pull', '--ff-only'], workingDirectory: Cache.flutterRoot, diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index dfaabdc8b8..8af7acc68e 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -198,7 +198,7 @@ class _FlutterValidator extends DoctorValidator { final FlutterVersion version = FlutterVersion.instance; - messages.add(new ValidationMessage('Flutter at ${Cache.flutterRoot}')); + messages.add(new ValidationMessage('Flutter version ${version.frameworkVersion} at ${Cache.flutterRoot}')); if (Cache.flutterRoot.contains(' ')) messages.add(new ValidationMessage.error( 'Flutter SDK install paths with spaces are not yet supported. (https://github.com/flutter/flutter/issues/6577)\n' diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 0c7a455023..8476617b41 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -247,6 +247,7 @@ class FlutterCommandRunner extends CommandRunner { flutterUsage.suppressAnalytics = true; _checkFlutterCopy(); + await FlutterVersion.instance.ensureVersionFile(); await FlutterVersion.instance.checkFlutterVersionFreshness(); if (globalResults.wasParsed('packages')) diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart index abdb3d6f4a..e1efafbaf2 100644 --- a/packages/flutter_tools/lib/src/usage.dart +++ b/packages/flutter_tools/lib/src/usage.dart @@ -24,7 +24,7 @@ class Usage { /// used for testing. Usage({ String settingsName: 'flutter', String versionOverride, String configDirOverride}) { final FlutterVersion flutterVersion = FlutterVersion.instance; - final String version = versionOverride ?? flutterVersion.getVersionString(whitelistBranchName: true); + final String version = versionOverride ?? flutterVersion.getVersionString(redactUnknownBranches: true); _analytics = new AnalyticsIO(_kFlutterUA, settingsName, version, // Analyzer doesn't recognize that [Directory] objects match up due to a // conditional import. @@ -34,7 +34,7 @@ class Usage { // Report a more detailed OS version string than package:usage does by default. _analytics.setSessionValue('cd1', os.name); // Send the branch name as the "channel". - _analytics.setSessionValue('cd2', flutterVersion.getBranchName(whitelistBranchName: true)); + _analytics.setSessionValue('cd2', flutterVersion.getBranchName(redactUnknownBranches: true)); // Record the host as the application installer ID - the context that flutter_tools is running in. if (platform.environment.containsKey('FLUTTER_HOST')) { _analytics.setSessionValue('aiid', platform.environment['FLUTTER_HOST']); diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index d8e752e57a..2051932978 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -10,20 +10,13 @@ import 'package:quiver/time.dart'; import 'base/common.dart'; import 'base/context.dart'; +import 'base/file_system.dart'; import 'base/io.dart'; import 'base/process.dart'; import 'base/process_manager.dart'; import 'cache.dart'; import 'globals.dart'; -final Set kKnownBranchNames = new Set.from([ - 'master', - 'alpha', - 'hackathon', - 'codelab', - 'beta' -]); - class FlutterVersion { @visibleForTesting FlutterVersion(this._clock) { @@ -42,6 +35,7 @@ class FlutterVersion { _frameworkRevision = _runGit('git log -n 1 --pretty=format:%H'); _frameworkAge = _runGit('git log -n 1 --pretty=format:%ar'); + _frameworkVersion = GitTagVersion.determine().frameworkVersionFor(_frameworkRevision); } final Clock _clock; @@ -49,11 +43,31 @@ class FlutterVersion { String _repositoryUrl; String get repositoryUrl => _repositoryUrl; + static Set officialChannels = new Set.from([ + 'master', + 'dev', + 'beta', + 'release', + ]); + + /// This maps old branch names to the names of branches that replaced them. + /// + /// For example, in early 2018 we changed from having an "alpha" branch to + /// having a "dev" branch, so anyone using "alpha" now gets transitioned to + /// "dev". + static Map obsoleteBranches = { + 'alpha': 'dev', + 'hackathon': 'dev', + 'codelab': 'dev', + }; + String _channel; - /// `master`, `alpha`, `hackathon`, ... + /// The channel is the upstream branch. + /// `master`, `dev`, `beta`, `release`; or old ones, like `alpha`, `hackathon`, ... String get channel => _channel; - /// The name of the local branch + /// The name of the local branch. + /// Use getBranchName() to read this. String _branch; String _frameworkRevision; @@ -63,6 +77,9 @@ class FlutterVersion { String _frameworkAge; String get frameworkAge => _frameworkAge; + String _frameworkVersion; + String get frameworkVersion => _frameworkVersion; + String get frameworkDate => frameworkCommitDate; String get dartSdkVersion => Cache.instance.dartSdkVersion.split(' ')[0]; @@ -71,16 +88,19 @@ class FlutterVersion { String get engineRevision => Cache.instance.engineRevision; String get engineRevisionShort => _shortGitRevision(engineRevision); - String _runGit(String command) => runSync(command.split(' '), workingDirectory: Cache.flutterRoot); + Future ensureVersionFile() { + return fs.file(fs.path.join(Cache.flutterRoot, 'version')).writeAsString(_frameworkVersion); + } @override String toString() { - final String flutterText = 'Flutter • channel $channel • ${repositoryUrl == null ? 'unknown source' : repositoryUrl}'; + final String versionText = frameworkVersion == 'unknown' ? '' : ' $frameworkVersion'; + final String flutterText = 'Flutter$versionText • channel $channel • ${repositoryUrl == null ? 'unknown source' : repositoryUrl}'; final String frameworkText = 'Framework • revision $frameworkRevisionShort ($frameworkAge) • $frameworkCommitDate'; final String engineText = 'Engine • revision $engineRevisionShort'; final String toolsText = 'Tools • Dart $dartSdkVersion'; - // Flutter • channel master • https://github.com/flutter/flutter.git + // Flutter 1.3.922-pre.2 • channel master • https://github.com/flutter/flutter.git // Framework • revision 2259c59be8 • 19 minutes ago • 2016-08-15 22:51:40 // Engine • revision fe509b0d96 // Tools • Dart 1.19.0-dev.5.0 @@ -150,20 +170,22 @@ class FlutterVersion { static FlutterVersion get instance => context.putIfAbsent(FlutterVersion, () => new FlutterVersion(const Clock())); - /// Return a short string for the version (`alpha/a76bc8e22b`). - String getVersionString({bool whitelistBranchName: false}) { - return '${getBranchName(whitelistBranchName: whitelistBranchName)}/$frameworkRevisionShort'; + /// Return a short string for the version (e.g. `master/0.0.59-pre.92`, `scroll_refactor/a76bc8e22b`). + String getVersionString({bool redactUnknownBranches: false}) { + if (frameworkVersion != 'unknown') + return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkVersion'; + return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevisionShort'; } /// Return the branch name. /// - /// If [whitelistBranchName] is true and the branch is unknown, - /// the branch name will be returned as 'dev'. - String getBranchName({ bool whitelistBranchName: false }) { - if (whitelistBranchName || _branch.isEmpty) { + /// If [redactUnknownBranches] is true and the branch is unknown, + /// the branch name will be returned as `'[user-branch]'`. + String getBranchName({ bool redactUnknownBranches: false }) { + if (redactUnknownBranches || _branch.isEmpty) { // Only return the branch names we know about; arbitrary branch names might contain PII. - if (!kKnownBranchNames.contains(_branch)) - return 'dev'; + if (!officialChannels.contains(_branch) && !obsoleteBranches.containsKey(_branch)) + return '[user-branch]'; } return _branch; } @@ -424,6 +446,10 @@ String _runSync(List command, {bool lenient: true}) { return ''; } +String _runGit(String command) { + return runSync(command.split(' '), workingDirectory: Cache.flutterRoot); +} + /// Runs [command] in the root of the Flutter installation and returns the /// standard output as a string. /// @@ -445,3 +471,45 @@ String _shortGitRevision(String revision) { return ''; return revision.length > 10 ? revision.substring(0, 10) : revision; } + +class GitTagVersion { + const GitTagVersion(this.x, this.y, this.z, this.commits, this.hash); + const GitTagVersion.unknown() : x = null, y = null, z = null, commits = 0, hash = ''; + + /// The X in vX.Y.Z. + final int x; + + /// The Y in vX.Y.Z. + final int y; + + /// The Z in vX.Y.Z. + final int z; + + /// Number of commits since the vX.Y.Z tag. + final int commits; + + /// The git hash (or an abbreviation thereof) for this commit. + final String hash; + + static GitTagVersion determine() { + final String version = _runGit('git describe --match v*.*.* --exclude *-* --first-parent --long --tags'); + final RegExp versionPattern = new RegExp('^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-g([a-f0-9]+)\$'); + final List parts = versionPattern.matchAsPrefix(version)?.groups([1, 2, 3, 4, 5]); + if (parts == null) { + printTrace('Could not interpret results of "git describe": $version'); + return const GitTagVersion.unknown(); + } + final List parsedParts = parts.take(4).map( + (String value) => int.parse(value, onError: (String value) => null), + ).toList(); + return new GitTagVersion(parsedParts[0], parsedParts[1], parsedParts[2], parsedParts[3], parts[4]); + } + + String frameworkVersionFor(String revision) { + if (x == null || y == null || z == null || !revision.startsWith(hash)) + return 'unknown'; + if (commits == 0) + return '$x.$y.$z'; + return '$x.$y.${z + 1}-pre.$commits'; + } +}