Ian Hickson | 449f4a6 | 2019-11-27 15:04:02 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
Adam Barth | 9662d49 | 2015-11-28 21:07:16 -0800 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 5 | import 'package:meta/meta.dart'; |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 6 | |
| 7 | import 'base/common.dart'; |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 8 | import 'base/file_system.dart'; |
Todd Volkert | 016b5ab | 2017-01-09 08:37:00 -0800 | [diff] [blame] | 9 | import 'base/io.dart'; |
Devon Carew | bd564a0 | 2016-05-05 19:51:22 -0700 | [diff] [blame] | 10 | import 'base/process.dart'; |
Jonah Williams | a871d59 | 2018-11-10 17:02:32 -0800 | [diff] [blame] | 11 | import 'base/time.dart'; |
Jason Simmons | 32846de | 2016-05-12 15:54:35 -0700 | [diff] [blame] | 12 | import 'cache.dart'; |
Jonah Williams | 91fd89e | 2019-01-25 16:16:26 -0800 | [diff] [blame] | 13 | import 'convert.dart'; |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 14 | import 'globals.dart' as globals; |
Adam Barth | 9662d49 | 2015-11-28 21:07:16 -0800 | [diff] [blame] | 15 | |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 16 | /// The flutter GitHub repository. |
| 17 | String get _flutterGit => globals.platform.environment['FLUTTER_GIT_URL'] ?? 'https://github.com/flutter/flutter.git'; |
| 18 | |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 19 | /// The names of each channel/branch in order of increasing stability. |
| 20 | enum Channel { |
| 21 | master, |
| 22 | dev, |
| 23 | beta, |
| 24 | stable, |
| 25 | } |
| 26 | |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 27 | // Beware: Keep order in accordance with stability |
| 28 | const Set<String> kOfficialChannels = <String>{ |
| 29 | 'master', |
| 30 | 'dev', |
| 31 | 'beta', |
| 32 | 'stable', |
| 33 | }; |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 34 | |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 35 | /// Retrieve a human-readable name for a given [channel]. |
| 36 | /// |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 37 | /// Requires [kOfficialChannels] to be correctly ordered. |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 38 | String getNameForChannel(Channel channel) { |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 39 | return kOfficialChannels.elementAt(channel.index); |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 40 | } |
| 41 | |
| 42 | /// Retrieve the [Channel] representation for a string [name]. |
| 43 | /// |
| 44 | /// Returns `null` if [name] is not in the list of official channels, according |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 45 | /// to [kOfficialChannels]. |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 46 | Channel getChannelForName(String name) { |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 47 | if (kOfficialChannels.contains(name)) { |
| 48 | return Channel.values[kOfficialChannels.toList().indexOf(name)]; |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 49 | } |
| 50 | return null; |
| 51 | } |
| 52 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 53 | class FlutterVersion { |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 54 | /// Parses the Flutter version from currently available tags in the local |
| 55 | /// repo. |
| 56 | /// |
| 57 | /// Call [fetchTagsAndUpdate] to update the version based on the latest tags |
| 58 | /// available upstream. |
Jonah Williams | 0b88269 | 2020-11-12 15:29:03 -0800 | [diff] [blame] | 59 | FlutterVersion({ |
| 60 | SystemClock clock = const SystemClock(), |
| 61 | String workingDirectory, |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 62 | String frameworkRevision, |
Jonah Williams | 0b88269 | 2020-11-12 15:29:03 -0800 | [diff] [blame] | 63 | }) : _clock = clock, |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 64 | _workingDirectory = workingDirectory { |
| 65 | _frameworkRevision = frameworkRevision ?? _runGit( |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 66 | gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '), |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 67 | globals.processUtils, |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 68 | _workingDirectory, |
| 69 | ); |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 70 | _gitTagVersion = GitTagVersion.determine(globals.processUtils, workingDirectory: _workingDirectory, fetchTags: false, gitRef: _frameworkRevision); |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 71 | _frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision); |
| 72 | } |
| 73 | |
Jonah Williams | 0b88269 | 2020-11-12 15:29:03 -0800 | [diff] [blame] | 74 | final SystemClock _clock; |
| 75 | final String _workingDirectory; |
| 76 | |
Jonah Williams | 08576cb | 2020-10-12 09:31:02 -0700 | [diff] [blame] | 77 | /// Fetches tags from the upstream Flutter repository and re-calculates the |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 78 | /// version. |
| 79 | /// |
| 80 | /// This carries a performance penalty, and should only be called when the |
| 81 | /// user explicitly wants to get the version, e.g. for `flutter --version` or |
| 82 | /// `flutter doctor`. |
| 83 | void fetchTagsAndUpdate() { |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 84 | _gitTagVersion = GitTagVersion.determine(globals.processUtils, workingDirectory: _workingDirectory, fetchTags: true); |
Jenn Magder | 9861a1c | 2019-12-23 13:12:36 -0800 | [diff] [blame] | 85 | _frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision); |
Adam Barth | 9662d49 | 2015-11-28 21:07:16 -0800 | [diff] [blame] | 86 | } |
Devon Carew | 864b7f4 | 2016-01-29 10:45:59 -0800 | [diff] [blame] | 87 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 88 | String _repositoryUrl; |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 89 | String get repositoryUrl { |
| 90 | final String _ = channel; |
| 91 | return _repositoryUrl; |
| 92 | } |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 93 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 94 | String _channel; |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 95 | /// The channel is the upstream branch. |
Greg Spencer | 1b9cba4 | 2018-10-30 16:57:54 -0700 | [diff] [blame] | 96 | /// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ... |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 97 | String get channel { |
| 98 | if (_channel == null) { |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 99 | final String channel = _runGit( |
| 100 | 'git rev-parse --abbrev-ref --symbolic @{u}', |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 101 | globals.processUtils, |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 102 | _workingDirectory, |
| 103 | ); |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 104 | final int slash = channel.indexOf('/'); |
| 105 | if (slash != -1) { |
| 106 | final String remote = channel.substring(0, slash); |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 107 | _repositoryUrl = _runGit( |
| 108 | 'git ls-remote --get-url $remote', |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 109 | globals.processUtils, |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 110 | _workingDirectory, |
| 111 | ); |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 112 | _channel = channel.substring(slash + 1); |
Alexandre Ardhuin | b873162 | 2019-09-24 21:03:37 +0200 | [diff] [blame] | 113 | } else if (channel.isEmpty) { |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 114 | _channel = 'unknown'; |
| 115 | } else { |
| 116 | _channel = channel; |
| 117 | } |
| 118 | } |
| 119 | return _channel; |
| 120 | } |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 121 | |
Jenn Magder | 9861a1c | 2019-12-23 13:12:36 -0800 | [diff] [blame] | 122 | GitTagVersion _gitTagVersion; |
| 123 | GitTagVersion get gitTagVersion => _gitTagVersion; |
| 124 | |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 125 | /// The name of the local branch. |
| 126 | /// Use getBranchName() to read this. |
Mehmet Fidanboylu | 96942bc | 2017-11-10 17:31:18 -0800 | [diff] [blame] | 127 | String _branch; |
| 128 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 129 | String _frameworkRevision; |
| 130 | String get frameworkRevision => _frameworkRevision; |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 131 | String get frameworkRevisionShort => _shortGitRevision(frameworkRevision); |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 132 | |
| 133 | String _frameworkAge; |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 134 | String get frameworkAge { |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 135 | return _frameworkAge ??= _runGit( |
| 136 | gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '), |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 137 | globals.processUtils, |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 138 | _workingDirectory, |
| 139 | ); |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 140 | } |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 141 | |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 142 | String _frameworkVersion; |
| 143 | String get frameworkVersion => _frameworkVersion; |
| 144 | |
Devon Carew | 7ab48f4 | 2016-08-16 08:38:19 -0700 | [diff] [blame] | 145 | String get frameworkDate => frameworkCommitDate; |
| 146 | |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 147 | String get dartSdkVersion => globals.cache.dartSdkVersion; |
Phil Quitslund | 803fbec | 2016-08-11 16:39:33 -0700 | [diff] [blame] | 148 | |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 149 | String get engineRevision => globals.cache.engineRevision; |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 150 | String get engineRevisionShort => _shortGitRevision(engineRevision); |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 151 | |
Jonah Williams | 0b88269 | 2020-11-12 15:29:03 -0800 | [diff] [blame] | 152 | void ensureVersionFile() { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 153 | globals.fs.file(globals.fs.path.join(Cache.flutterRoot, 'version')).writeAsStringSync(_frameworkVersion); |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 154 | } |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 155 | |
| 156 | @override |
| 157 | String toString() { |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 158 | final String versionText = frameworkVersion == 'unknown' ? '' : ' $frameworkVersion'; |
Alexandre Ardhuin | 4fa32df | 2019-05-16 22:25:51 +0200 | [diff] [blame] | 159 | final String flutterText = 'Flutter$versionText • channel $channel • ${repositoryUrl ?? 'unknown source'}'; |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 160 | final String frameworkText = 'Framework • revision $frameworkRevisionShort ($frameworkAge) • $frameworkCommitDate'; |
| 161 | final String engineText = 'Engine • revision $engineRevisionShort'; |
| 162 | final String toolsText = 'Tools • Dart $dartSdkVersion'; |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 163 | |
Greg Spencer | dc2cc63 | 2018-10-26 13:21:36 -0700 | [diff] [blame] | 164 | // Flutter 1.10.2-pre.69 • channel master • https://github.com/flutter/flutter.git |
| 165 | // Framework • revision 340c158f32 (84 minutes ago) • 2018-10-26 11:27:22 -0400 |
| 166 | // Engine • revision 9c46333e14 |
| 167 | // Tools • Dart 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1) |
Devon Carew | 7ab48f4 | 2016-08-16 08:38:19 -0700 | [diff] [blame] | 168 | |
| 169 | return '$flutterText\n$frameworkText\n$engineText\n$toolsText'; |
Phil Quitslund | abeb5c7 | 2016-08-15 11:07:37 -0700 | [diff] [blame] | 170 | } |
| 171 | |
Kevin Moore | 1b56cb7 | 2017-06-06 18:40:32 -0700 | [diff] [blame] | 172 | Map<String, Object> toJson() => <String, Object>{ |
Alexandre Ardhuin | f11c341 | 2019-09-27 10:46:45 +0200 | [diff] [blame] | 173 | 'frameworkVersion': frameworkVersion ?? 'unknown', |
| 174 | 'channel': channel, |
| 175 | 'repositoryUrl': repositoryUrl ?? 'unknown source', |
| 176 | 'frameworkRevision': frameworkRevision, |
| 177 | 'frameworkCommitDate': frameworkCommitDate, |
| 178 | 'engineRevision': engineRevision, |
| 179 | 'dartSdkVersion': dartSdkVersion, |
| 180 | }; |
Kevin Moore | 1b56cb7 | 2017-06-06 18:40:32 -0700 | [diff] [blame] | 181 | |
Phil Quitslund | abeb5c7 | 2016-08-15 11:07:37 -0700 | [diff] [blame] | 182 | /// A date String describing the last framework commit. |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 183 | /// |
| 184 | /// If a git command fails, this will return a placeholder date. |
| 185 | String get frameworkCommitDate => _latestGitCommitDate(lenient: true); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 186 | |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 187 | // The date of the latest commit on the given branch. If no branch is |
| 188 | // specified, then it is the current local branch. |
| 189 | // |
| 190 | // If lenient is true, and the git command fails, a placeholder date is |
| 191 | // returned. Otherwise, the VersionCheckError exception is propagated. |
| 192 | static String _latestGitCommitDate({ |
| 193 | String branch, |
| 194 | bool lenient = false, |
| 195 | }) { |
∂ω∂ | 4277f36 | 2019-08-21 23:55:57 +0300 | [diff] [blame] | 196 | final List<String> args = gitLog(<String>[ |
Alexandre Ardhuin | 758009b | 2019-07-02 21:11:56 +0200 | [diff] [blame] | 197 | if (branch != null) branch, |
| 198 | '-n', |
| 199 | '1', |
| 200 | '--pretty=format:%ad', |
| 201 | '--date=iso', |
∂ω∂ | 4277f36 | 2019-08-21 23:55:57 +0300 | [diff] [blame] | 202 | ]); |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 203 | try { |
| 204 | // Don't plumb 'lenient' through directly so that we can print an error |
| 205 | // if something goes wrong. |
| 206 | return _runSync(args, lenient: false); |
| 207 | } on VersionCheckError catch (e) { |
| 208 | if (lenient) { |
| 209 | final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0); |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 210 | globals.printError('Failed to find the latest git commit date: $e\n' |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 211 | 'Returning $dummyDate instead.'); |
| 212 | // Return something that DateTime.parse() can parse. |
| 213 | return dummyDate.toString(); |
| 214 | } else { |
| 215 | rethrow; |
| 216 | } |
| 217 | } |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 218 | } |
| 219 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 220 | /// The name of the temporary git remote used to check for the latest |
| 221 | /// available Flutter framework version. |
| 222 | /// |
| 223 | /// In the absence of bugs and crashes a Flutter developer should never see |
| 224 | /// this remote appear in their `git remote` list, but also if it happens to |
| 225 | /// persist we do the proper clean-up for extra robustness. |
Greg Spencer | 3c5a7a3 | 2018-05-17 23:04:41 -0700 | [diff] [blame] | 226 | static const String _versionCheckRemote = '__flutter_version_check__'; |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 227 | |
| 228 | /// The date of the latest framework commit in the remote repository. |
| 229 | /// |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 230 | /// Throws [VersionCheckError] if a git command fails, for example, when the |
| 231 | /// remote git repository is not reachable due to a network issue. |
Jason Simmons | 9455fb6 | 2017-04-14 09:58:31 -0700 | [diff] [blame] | 232 | static Future<String> fetchRemoteFrameworkCommitDate(String branch) async { |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 233 | await _removeVersionCheckRemoteIfExists(); |
| 234 | try { |
| 235 | await _run(<String>[ |
| 236 | 'git', |
| 237 | 'remote', |
| 238 | 'add', |
Greg Spencer | 3c5a7a3 | 2018-05-17 23:04:41 -0700 | [diff] [blame] | 239 | _versionCheckRemote, |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 240 | _flutterGit, |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 241 | ]); |
Greg Spencer | 3c5a7a3 | 2018-05-17 23:04:41 -0700 | [diff] [blame] | 242 | await _run(<String>['git', 'fetch', _versionCheckRemote, branch]); |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 243 | return _latestGitCommitDate( |
| 244 | branch: '$_versionCheckRemote/$branch', |
| 245 | lenient: false, |
| 246 | ); |
雷宇辰 | 7779a14 | 2020-07-25 07:16:31 +0800 | [diff] [blame] | 247 | } on VersionCheckError catch (error) { |
| 248 | if (globals.platform.environment.containsKey('FLUTTER_GIT_URL')) { |
Jonah Williams | 08576cb | 2020-10-12 09:31:02 -0700 | [diff] [blame] | 249 | globals.logger.printError('Warning: the Flutter git upstream was overridden ' |
雷宇辰 | 7779a14 | 2020-07-25 07:16:31 +0800 | [diff] [blame] | 250 | 'by the environment variable FLUTTER_GIT_URL = $_flutterGit'); |
| 251 | } |
| 252 | globals.logger.printError(error.toString()); |
| 253 | rethrow; |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 254 | } finally { |
| 255 | await _removeVersionCheckRemoteIfExists(); |
| 256 | } |
| 257 | } |
| 258 | |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 259 | static Future<void> _removeVersionCheckRemoteIfExists() async { |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 260 | final List<String> remotes = (await _run(<String>['git', 'remote'])) |
| 261 | .split('\n') |
Alexandre Ardhuin | f62afdc | 2018-10-01 21:29:08 +0200 | [diff] [blame] | 262 | .map<String>((String name) => name.trim()) // to account for OS-specific line-breaks |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 263 | .toList(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 264 | if (remotes.contains(_versionCheckRemote)) { |
Greg Spencer | 3c5a7a3 | 2018-05-17 23:04:41 -0700 | [diff] [blame] | 265 | await _run(<String>['git', 'remote', 'remove', _versionCheckRemote]); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 266 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 267 | } |
| 268 | |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 269 | /// Return a short string for the version (e.g. `master/0.0.59-pre.92`, `scroll_refactor/a76bc8e22b`). |
Alexandre Ardhuin | 5169ab5 | 2019-02-21 09:27:07 +0100 | [diff] [blame] | 270 | String getVersionString({ bool redactUnknownBranches = false }) { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 271 | if (frameworkVersion != 'unknown') { |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 272 | return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkVersion'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 273 | } |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 274 | return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevisionShort'; |
Seth Ladd | b471a9c | 2017-06-20 08:20:57 -0700 | [diff] [blame] | 275 | } |
| 276 | |
| 277 | /// Return the branch name. |
| 278 | /// |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 279 | /// If [redactUnknownBranches] is true and the branch is unknown, |
| 280 | /// the branch name will be returned as `'[user-branch]'`. |
Alexandre Ardhuin | 09276be | 2018-06-05 08:50:40 +0200 | [diff] [blame] | 281 | String getBranchName({ bool redactUnknownBranches = false }) { |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 282 | _branch ??= () { |
Jenn Magder | 537cf33 | 2020-10-14 13:12:46 -0700 | [diff] [blame] | 283 | final String branch = _runGit('git rev-parse --abbrev-ref HEAD', globals.processUtils); |
Jonah Williams | b123846 | 2019-03-20 13:58:57 -0700 | [diff] [blame] | 284 | return branch == 'HEAD' ? channel : branch; |
| 285 | }(); |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 286 | if (redactUnknownBranches || _branch.isEmpty) { |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 287 | // Only return the branch names we know about; arbitrary branch names might contain PII. |
Jonah Williams | 0b88269 | 2020-11-12 15:29:03 -0800 | [diff] [blame] | 288 | if (!kOfficialChannels.contains(_branch)) { |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 289 | return '[user-branch]'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 290 | } |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 291 | } |
Mehmet Fidanboylu | 96942bc | 2017-11-10 17:31:18 -0800 | [diff] [blame] | 292 | return _branch; |
Devon Carew | adac927 | 2016-04-26 16:25:11 -0700 | [diff] [blame] | 293 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 294 | |
xster | 9a0d4cf | 2017-11-14 18:50:15 -0800 | [diff] [blame] | 295 | /// Returns true if `tentativeDescendantRevision` is a direct descendant to |
| 296 | /// the `tentativeAncestorRevision` revision on the Flutter framework repo |
| 297 | /// tree. |
| 298 | bool checkRevisionAncestry({ |
| 299 | String tentativeDescendantRevision, |
| 300 | String tentativeAncestorRevision, |
| 301 | }) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 302 | final ProcessResult result = globals.processManager.runSync( |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 303 | <String>[ |
| 304 | 'git', |
| 305 | 'merge-base', |
| 306 | '--is-ancestor', |
| 307 | tentativeAncestorRevision, |
| 308 | tentativeDescendantRevision |
| 309 | ], |
xster | 9a0d4cf | 2017-11-14 18:50:15 -0800 | [diff] [blame] | 310 | workingDirectory: Cache.flutterRoot, |
| 311 | ); |
| 312 | return result.exitCode == 0; |
| 313 | } |
| 314 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 315 | /// The amount of time we wait before pinging the server to check for the |
| 316 | /// availability of a newer version of Flutter. |
| 317 | @visibleForTesting |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 318 | static const Duration checkAgeConsideredUpToDate = Duration(days: 3); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 319 | |
| 320 | /// We warn the user if the age of their Flutter installation is greater than |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 321 | /// this duration. The durations are slightly longer than the expected release |
| 322 | /// cadence for each channel, to give the user a grace period before they get |
| 323 | /// notified. |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 324 | /// |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 325 | /// For example, for the beta channel, this is set to five weeks because |
| 326 | /// beta releases happen approximately every month. |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 327 | @visibleForTesting |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 328 | static Duration versionAgeConsideredUpToDate(String channel) { |
| 329 | switch (channel) { |
| 330 | case 'stable': |
| 331 | return const Duration(days: 365 ~/ 2); // Six months |
| 332 | case 'beta': |
| 333 | return const Duration(days: 7 * 8); // Eight weeks |
| 334 | case 'dev': |
| 335 | return const Duration(days: 7 * 4); // Four weeks |
| 336 | default: |
| 337 | return const Duration(days: 7 * 3); // Three weeks |
| 338 | } |
| 339 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 340 | |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 341 | /// The amount of time we wait between issuing a warning. |
| 342 | /// |
| 343 | /// This is to avoid annoying users who are unable to upgrade right away. |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 344 | @visibleForTesting |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 345 | static const Duration maxTimeSinceLastWarning = Duration(days: 1); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 346 | |
| 347 | /// The amount of time we pause for to let the user read the message about |
| 348 | /// outdated Flutter installation. |
| 349 | /// |
| 350 | /// This can be customized in tests to speed them up. |
| 351 | @visibleForTesting |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 352 | static Duration timeToPauseToLetUserReadTheMessage = const Duration(seconds: 2); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 353 | |
Greg Spencer | 7cebaac | 2018-08-29 21:53:39 -0700 | [diff] [blame] | 354 | /// Reset the version freshness information by removing the stamp file. |
| 355 | /// |
| 356 | /// New version freshness information will be regenerated when |
| 357 | /// [checkFlutterVersionFreshness] is called after this. This is typically |
| 358 | /// used when switching channels so that stale information from another |
| 359 | /// channel doesn't linger. |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 360 | static Future<void> resetFlutterVersionFreshnessCheck() async { |
Greg Spencer | 7cebaac | 2018-08-29 21:53:39 -0700 | [diff] [blame] | 361 | try { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 362 | await globals.cache.getStampFileFor( |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 363 | VersionCheckStamp.flutterVersionCheckStampFile, |
Greg Spencer | 7cebaac | 2018-08-29 21:53:39 -0700 | [diff] [blame] | 364 | ).delete(); |
| 365 | } on FileSystemException { |
| 366 | // Ignore, since we don't mind if the file didn't exist in the first place. |
| 367 | } |
| 368 | } |
| 369 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 370 | /// Checks if the currently installed version of Flutter is up-to-date, and |
| 371 | /// warns the user if it isn't. |
| 372 | /// |
| 373 | /// This function must run while [Cache.lock] is acquired because it reads and |
| 374 | /// writes shared cache files. |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 375 | Future<void> checkFlutterVersionFreshness() async { |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 376 | // Don't perform update checks if we're not on an official channel. |
includecmath | 71c42c9 | 2020-09-25 04:07:03 +0800 | [diff] [blame] | 377 | if (!kOfficialChannels.contains(channel)) { |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 378 | return; |
| 379 | } |
| 380 | |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 381 | DateTime localFrameworkCommitDate; |
| 382 | try { |
| 383 | localFrameworkCommitDate = DateTime.parse(_latestGitCommitDate( |
| 384 | lenient: false |
| 385 | )); |
| 386 | } on VersionCheckError { |
Jenn Magder | f654346 | 2020-09-23 13:27:24 -0700 | [diff] [blame] | 387 | // Don't perform the update check if the version check failed. |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 388 | return; |
| 389 | } |
| 390 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 391 | final Duration frameworkAge = _clock.now().difference(localFrameworkCommitDate); |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 392 | final bool installationSeemsOutdated = frameworkAge > versionAgeConsideredUpToDate(channel); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 393 | |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 394 | // Get whether there's a newer version on the remote. This only goes |
| 395 | // to the server if we haven't checked recently so won't happen on every |
| 396 | // command. |
| 397 | final DateTime latestFlutterCommitDate = await _getLatestAvailableFlutterDate(); |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 398 | final VersionCheckResult remoteVersionStatus = latestFlutterCommitDate == null |
| 399 | ? VersionCheckResult.unknown |
| 400 | : latestFlutterCommitDate.isAfter(localFrameworkCommitDate) |
| 401 | ? VersionCheckResult.newVersionAvailable |
| 402 | : VersionCheckResult.versionIsCurrent; |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 403 | |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 404 | // Do not load the stamp before the above server check as it may modify the stamp file. |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 405 | final VersionCheckStamp stamp = await VersionCheckStamp.load(); |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 406 | final DateTime lastTimeWarningWasPrinted = stamp.lastTimeWarningWasPrinted ?? _clock.ago(maxTimeSinceLastWarning * 2); |
| 407 | final bool beenAWhileSinceWarningWasPrinted = _clock.now().difference(lastTimeWarningWasPrinted) > maxTimeSinceLastWarning; |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 408 | |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 409 | // We show a warning if either we know there is a new remote version, or we couldn't tell but the local |
| 410 | // version is outdated. |
| 411 | final bool canShowWarning = |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 412 | remoteVersionStatus == VersionCheckResult.newVersionAvailable || |
| 413 | (remoteVersionStatus == VersionCheckResult.unknown && |
| 414 | installationSeemsOutdated); |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 415 | |
| 416 | if (beenAWhileSinceWarningWasPrinted && canShowWarning) { |
| 417 | final String updateMessage = |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 418 | remoteVersionStatus == VersionCheckResult.newVersionAvailable |
| 419 | ? newVersionAvailableMessage() |
| 420 | : versionOutOfDateMessage(frameworkAge); |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 421 | globals.printStatus(updateMessage, emphasis: true); |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 422 | await Future.wait<void>(<Future<void>>[ |
xster | 0339351 | 2017-06-16 14:22:10 -0700 | [diff] [blame] | 423 | stamp.store( |
| 424 | newTimeWarningWasPrinted: _clock.now(), |
| 425 | ), |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 426 | Future<void>.delayed(timeToPauseToLetUserReadTheMessage), |
xster | 45446ae | 2017-06-15 18:49:19 -0700 | [diff] [blame] | 427 | ]); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 428 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 429 | } |
| 430 | |
∂ω∂ | 4277f36 | 2019-08-21 23:55:57 +0300 | [diff] [blame] | 431 | /// log.showSignature=false is a user setting and it will break things, |
| 432 | /// so we want to disable it for every git log call. This is a convenience |
| 433 | /// wrapper that does that. |
| 434 | @visibleForTesting |
| 435 | static List<String> gitLog(List<String> args) { |
| 436 | return <String>['git', '-c', 'log.showSignature=false', 'log'] + args; |
| 437 | } |
| 438 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 439 | @visibleForTesting |
| 440 | static String versionOutOfDateMessage(Duration frameworkAge) { |
| 441 | String warning = 'WARNING: your installation of Flutter is ${frameworkAge.inDays} days old.'; |
| 442 | // Append enough spaces to match the message box width. |
| 443 | warning += ' ' * (74 - warning.length); |
| 444 | |
| 445 | return ''' |
| 446 | ╔════════════════════════════════════════════════════════════════════════════╗ |
| 447 | ║ $warning ║ |
| 448 | ║ ║ |
| 449 | ║ To update to the latest version, run "flutter upgrade". ║ |
| 450 | ╚════════════════════════════════════════════════════════════════════════════╝ |
| 451 | '''; |
| 452 | } |
| 453 | |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 454 | @visibleForTesting |
| 455 | static String newVersionAvailableMessage() { |
| 456 | return ''' |
| 457 | ╔════════════════════════════════════════════════════════════════════════════╗ |
| 458 | ║ A new version of Flutter is available! ║ |
| 459 | ║ ║ |
| 460 | ║ To update to the latest version, run "flutter upgrade". ║ |
| 461 | ╚════════════════════════════════════════════════════════════════════════════╝ |
| 462 | '''; |
| 463 | } |
| 464 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 465 | /// Gets the release date of the latest available Flutter version. |
| 466 | /// |
| 467 | /// This method sends a server request if it's been more than |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 468 | /// [checkAgeConsideredUpToDate] since the last version check. |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 469 | /// |
Ian Hickson | 0f1a703 | 2017-06-08 17:13:03 -0700 | [diff] [blame] | 470 | /// Returns null if the cached version is out-of-date or missing, and we are |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 471 | /// unable to reach the server to get the latest version. |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 472 | Future<DateTime> _getLatestAvailableFlutterDate() async { |
Jonah Williams | 2fb53d8 | 2020-11-06 15:26:45 -0800 | [diff] [blame] | 473 | globals.cache.checkLockAcquired(); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 474 | final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 475 | |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 476 | if (versionCheckStamp.lastTimeVersionWasChecked != null) { |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 477 | final Duration timeSinceLastCheck = _clock.now().difference( |
| 478 | versionCheckStamp.lastTimeVersionWasChecked, |
| 479 | ); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 480 | |
| 481 | // Don't ping the server too often. Return cached value if it's fresh. |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 482 | if (timeSinceLastCheck < checkAgeConsideredUpToDate) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 483 | return versionCheckStamp.lastKnownRemoteVersion; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 484 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 485 | } |
| 486 | |
| 487 | // Cache is empty or it's been a while since the last server ping. Ping the server. |
| 488 | try { |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 489 | final DateTime remoteFrameworkCommitDate = DateTime.parse( |
| 490 | await FlutterVersion.fetchRemoteFrameworkCommitDate(channel), |
| 491 | ); |
xster | 0339351 | 2017-06-16 14:22:10 -0700 | [diff] [blame] | 492 | await versionCheckStamp.store( |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 493 | newTimeVersionWasChecked: _clock.now(), |
| 494 | newKnownRemoteVersion: remoteFrameworkCommitDate, |
| 495 | ); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 496 | return remoteFrameworkCommitDate; |
| 497 | } on VersionCheckError catch (error) { |
| 498 | // This happens when any of the git commands fails, which can happen when |
| 499 | // there's no Internet connectivity. Remote version check is best effort |
| 500 | // only. We do not prevent the command from running when it fails. |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 501 | globals.printTrace('Failed to check Flutter version in the remote repository: $error'); |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 502 | // Still update the timestamp to avoid us hitting the server on every single |
| 503 | // command if for some reason we cannot connect (eg. we may be offline). |
| 504 | await versionCheckStamp.store( |
| 505 | newTimeVersionWasChecked: _clock.now(), |
| 506 | ); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 507 | return null; |
| 508 | } |
| 509 | } |
Devon Carew | adac927 | 2016-04-26 16:25:11 -0700 | [diff] [blame] | 510 | } |
| 511 | |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 512 | /// Contains data and load/save logic pertaining to Flutter version checks. |
| 513 | @visibleForTesting |
| 514 | class VersionCheckStamp { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 515 | const VersionCheckStamp({ |
| 516 | this.lastTimeVersionWasChecked, |
| 517 | this.lastKnownRemoteVersion, |
| 518 | this.lastTimeWarningWasPrinted, |
| 519 | }); |
| 520 | |
| 521 | final DateTime lastTimeVersionWasChecked; |
| 522 | final DateTime lastKnownRemoteVersion; |
| 523 | final DateTime lastTimeWarningWasPrinted; |
| 524 | |
Alexandre Ardhuin | 2ea1d81 | 2018-10-04 07:28:07 +0200 | [diff] [blame] | 525 | /// The prefix of the stamp file where we cache Flutter version check data. |
| 526 | @visibleForTesting |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 527 | static const String flutterVersionCheckStampFile = 'flutter_version_check'; |
Alexandre Ardhuin | 2ea1d81 | 2018-10-04 07:28:07 +0200 | [diff] [blame] | 528 | |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 529 | static Future<VersionCheckStamp> load() async { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 530 | final String versionCheckStamp = globals.cache.getStampFor(flutterVersionCheckStampFile); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 531 | |
| 532 | if (versionCheckStamp != null) { |
| 533 | // Attempt to parse stamp JSON. |
| 534 | try { |
Jason Simmons | 466d154 | 2018-03-12 11:06:32 -0700 | [diff] [blame] | 535 | final dynamic jsonObject = json.decode(versionCheckStamp); |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 536 | if (jsonObject is Map<String, dynamic>) { |
Jason Simmons | 466d154 | 2018-03-12 11:06:32 -0700 | [diff] [blame] | 537 | return fromJson(jsonObject); |
Yegor | fa47c34 | 2017-05-08 12:59:33 -0700 | [diff] [blame] | 538 | } else { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 539 | globals.printTrace('Warning: expected version stamp to be a Map but found: $jsonObject'); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 540 | } |
Zachary Anderson | 6c408a0 | 2020-03-06 10:22:12 -0800 | [diff] [blame] | 541 | } on Exception catch (error, stackTrace) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 542 | // Do not crash if JSON is malformed. |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 543 | globals.printTrace('${error.runtimeType}: $error\n$stackTrace'); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 544 | } |
| 545 | } |
| 546 | |
| 547 | // Stamp is missing or is malformed. |
Yegor | a5593b1 | 2017-04-10 14:20:47 -0700 | [diff] [blame] | 548 | return const VersionCheckStamp(); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 549 | } |
| 550 | |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 551 | static VersionCheckStamp fromJson(Map<String, dynamic> jsonObject) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 552 | DateTime readDateTime(String property) { |
Jason Simmons | 466d154 | 2018-03-12 11:06:32 -0700 | [diff] [blame] | 553 | return jsonObject.containsKey(property) |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 554 | ? DateTime.parse(jsonObject[property] as String) |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 555 | : null; |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 556 | } |
| 557 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 558 | return VersionCheckStamp( |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 559 | lastTimeVersionWasChecked: readDateTime('lastTimeVersionWasChecked'), |
| 560 | lastKnownRemoteVersion: readDateTime('lastKnownRemoteVersion'), |
| 561 | lastTimeWarningWasPrinted: readDateTime('lastTimeWarningWasPrinted'), |
| 562 | ); |
| 563 | } |
| 564 | |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 565 | Future<void> store({ |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 566 | DateTime newTimeVersionWasChecked, |
| 567 | DateTime newKnownRemoteVersion, |
| 568 | DateTime newTimeWarningWasPrinted, |
| 569 | }) async { |
| 570 | final Map<String, String> jsonData = toJson(); |
| 571 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 572 | if (newTimeVersionWasChecked != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 573 | jsonData['lastTimeVersionWasChecked'] = '$newTimeVersionWasChecked'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 574 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 575 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 576 | if (newKnownRemoteVersion != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 577 | jsonData['lastKnownRemoteVersion'] = '$newKnownRemoteVersion'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 578 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 579 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 580 | if (newTimeWarningWasPrinted != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 581 | jsonData['lastTimeWarningWasPrinted'] = '$newTimeWarningWasPrinted'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 582 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 583 | |
Greg Spencer | eb35f89 | 2018-11-14 16:51:12 -0800 | [diff] [blame] | 584 | const JsonEncoder prettyJsonEncoder = JsonEncoder.withIndent(' '); |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 585 | globals.cache.setStampFor(flutterVersionCheckStampFile, prettyJsonEncoder.convert(jsonData)); |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 586 | } |
| 587 | |
| 588 | Map<String, String> toJson({ |
| 589 | DateTime updateTimeVersionWasChecked, |
| 590 | DateTime updateKnownRemoteVersion, |
| 591 | DateTime updateTimeWarningWasPrinted, |
| 592 | }) { |
| 593 | updateTimeVersionWasChecked = updateTimeVersionWasChecked ?? lastTimeVersionWasChecked; |
| 594 | updateKnownRemoteVersion = updateKnownRemoteVersion ?? lastKnownRemoteVersion; |
| 595 | updateTimeWarningWasPrinted = updateTimeWarningWasPrinted ?? lastTimeWarningWasPrinted; |
| 596 | |
| 597 | final Map<String, String> jsonData = <String, String>{}; |
| 598 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 599 | if (updateTimeVersionWasChecked != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 600 | jsonData['lastTimeVersionWasChecked'] = '$updateTimeVersionWasChecked'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 601 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 602 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 603 | if (updateKnownRemoteVersion != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 604 | jsonData['lastKnownRemoteVersion'] = '$updateKnownRemoteVersion'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 605 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 606 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 607 | if (updateTimeWarningWasPrinted != null) { |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 608 | jsonData['lastTimeWarningWasPrinted'] = '$updateTimeWarningWasPrinted'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 609 | } |
Yegor | 5efbe05 | 2017-04-10 13:21:02 -0700 | [diff] [blame] | 610 | |
| 611 | return jsonData; |
| 612 | } |
| 613 | } |
| 614 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 615 | /// Thrown when we fail to check Flutter version. |
| 616 | /// |
| 617 | /// This can happen when we attempt to `git fetch` but there is no network, or |
| 618 | /// when the installation is not git-based (e.g. a user clones the repo but |
| 619 | /// then removes .git). |
| 620 | class VersionCheckError implements Exception { |
| 621 | |
| 622 | VersionCheckError(this.message); |
| 623 | |
| 624 | final String message; |
| 625 | |
| 626 | @override |
| 627 | String toString() => '$VersionCheckError: $message'; |
| 628 | } |
| 629 | |
| 630 | /// Runs [command] and returns the standard output as a string. |
| 631 | /// |
Ian Hickson | efb45ea | 2017-09-27 16:13:48 -0700 | [diff] [blame] | 632 | /// If [lenient] is true and the command fails, returns an empty string. |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 633 | /// Otherwise, throws a [ToolExit] exception. |
Alexandre Ardhuin | 5169ab5 | 2019-02-21 09:27:07 +0100 | [diff] [blame] | 634 | String _runSync(List<String> command, { bool lenient = true }) { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 635 | final ProcessResult results = globals.processManager.runSync( |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 636 | command, |
| 637 | workingDirectory: Cache.flutterRoot, |
| 638 | ); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 639 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 640 | if (results.exitCode == 0) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 641 | return (results.stdout as String).trim(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 642 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 643 | |
| 644 | if (!lenient) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 645 | throw VersionCheckError( |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 646 | 'Command exited with code ${results.exitCode}: ${command.join(' ')}\n' |
Zachary Anderson | e4b809b | 2019-11-27 10:18:43 -0800 | [diff] [blame] | 647 | 'Standard out: ${results.stdout}\n' |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 648 | 'Standard error: ${results.stderr}' |
| 649 | ); |
| 650 | } |
| 651 | |
| 652 | return ''; |
| 653 | } |
| 654 | |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 655 | String _runGit(String command, ProcessUtils processUtils, [String workingDirectory]) { |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 656 | return processUtils.runSync( |
| 657 | command.split(' '), |
Jonah Williams | e2554a9 | 2020-02-13 11:56:45 -0800 | [diff] [blame] | 658 | workingDirectory: workingDirectory ?? Cache.flutterRoot, |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 659 | ).stdout.trim(); |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 660 | } |
| 661 | |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 662 | /// Runs [command] in the root of the Flutter installation and returns the |
| 663 | /// standard output as a string. |
| 664 | /// |
| 665 | /// If the command fails, throws a [ToolExit] exception. |
| 666 | Future<String> _run(List<String> command) async { |
Jonah Williams | ee7a37f | 2020-01-06 11:04:20 -0800 | [diff] [blame] | 667 | final ProcessResult results = await globals.processManager.run(command, workingDirectory: Cache.flutterRoot); |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 668 | |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 669 | if (results.exitCode == 0) { |
Alexandre Ardhuin | adc7351 | 2019-11-19 07:57:42 +0100 | [diff] [blame] | 670 | return (results.stdout as String).trim(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 671 | } |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 672 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 673 | throw VersionCheckError( |
Yegor | 93126a8 | 2017-04-04 10:45:43 -0700 | [diff] [blame] | 674 | 'Command exited with code ${results.exitCode}: ${command.join(' ')}\n' |
| 675 | 'Standard error: ${results.stderr}' |
| 676 | ); |
Adam Barth | 9662d49 | 2015-11-28 21:07:16 -0800 | [diff] [blame] | 677 | } |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 678 | |
| 679 | String _shortGitRevision(String revision) { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 680 | if (revision == null) { |
Todd Volkert | cedbfd4 | 2016-10-14 15:43:44 -0700 | [diff] [blame] | 681 | return ''; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 682 | } |
Devon Carew | b0ebc71 | 2016-04-27 10:47:52 -0700 | [diff] [blame] | 683 | return revision.length > 10 ? revision.substring(0, 10) : revision; |
| 684 | } |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 685 | |
Nolan Scobie | 43c1b34 | 2020-08-06 19:18:52 -0400 | [diff] [blame] | 686 | /// Version of Flutter SDK parsed from Git. |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 687 | class GitTagVersion { |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 688 | const GitTagVersion({ |
| 689 | this.x, |
| 690 | this.y, |
| 691 | this.z, |
| 692 | this.hotfix, |
| 693 | this.devVersion, |
| 694 | this.devPatch, |
| 695 | this.commits, |
| 696 | this.hash, |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 697 | this.gitTag, |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 698 | }); |
Alexandre Ardhuin | ef276ff | 2019-01-29 21:47:16 +0100 | [diff] [blame] | 699 | const GitTagVersion.unknown() |
| 700 | : x = null, |
| 701 | y = null, |
| 702 | z = null, |
Ian Hickson | a07c9a1 | 2019-03-08 19:26:34 -0800 | [diff] [blame] | 703 | hotfix = null, |
Alexandre Ardhuin | ef276ff | 2019-01-29 21:47:16 +0100 | [diff] [blame] | 704 | commits = 0, |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 705 | devVersion = null, |
| 706 | devPatch = null, |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 707 | hash = '', |
| 708 | gitTag = ''; |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 709 | |
| 710 | /// The X in vX.Y.Z. |
| 711 | final int x; |
| 712 | |
| 713 | /// The Y in vX.Y.Z. |
| 714 | final int y; |
| 715 | |
| 716 | /// The Z in vX.Y.Z. |
| 717 | final int z; |
| 718 | |
Nolan Scobie | 43c1b34 | 2020-08-06 19:18:52 -0400 | [diff] [blame] | 719 | /// the F in vX.Y.Z+hotfix.F. |
Ian Hickson | a07c9a1 | 2019-03-08 19:26:34 -0800 | [diff] [blame] | 720 | final int hotfix; |
| 721 | |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 722 | /// Number of commits since the vX.Y.Z tag. |
| 723 | final int commits; |
| 724 | |
| 725 | /// The git hash (or an abbreviation thereof) for this commit. |
| 726 | final String hash; |
| 727 | |
Nolan Scobie | 43c1b34 | 2020-08-06 19:18:52 -0400 | [diff] [blame] | 728 | /// The N in X.Y.Z-dev.N.M. |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 729 | final int devVersion; |
| 730 | |
Nolan Scobie | 43c1b34 | 2020-08-06 19:18:52 -0400 | [diff] [blame] | 731 | /// The M in X.Y.Z-dev.N.M. |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 732 | final int devPatch; |
| 733 | |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 734 | /// The git tag that is this version's closest ancestor. |
| 735 | final String gitTag; |
| 736 | |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 737 | static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false, String gitRef = 'HEAD'}) { |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 738 | if (fetchTags) { |
Dan Field | c8efcb6 | 2020-03-27 22:31:01 -0700 | [diff] [blame] | 739 | final String channel = _runGit('git rev-parse --abbrev-ref HEAD', processUtils, workingDirectory); |
| 740 | if (channel == 'dev' || channel == 'beta' || channel == 'stable') { |
| 741 | globals.printTrace('Skipping request to fetchTags - on well known channel $channel.'); |
| 742 | } else { |
Jonah Williams | eae7780 | 2020-06-04 13:54:32 -0700 | [diff] [blame] | 743 | _runGit('git fetch $_flutterGit --tags -f', processUtils, workingDirectory); |
Dan Field | c8efcb6 | 2020-03-27 22:31:01 -0700 | [diff] [blame] | 744 | } |
Dan Field | e13e170 | 2020-03-06 21:38:35 -0800 | [diff] [blame] | 745 | } |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 746 | final List<String> tags = _runGit( |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 747 | 'git tag --points-at $gitRef', processUtils, workingDirectory).trim().split('\n'); |
Ian Hickson | a07c9a1 | 2019-03-08 19:26:34 -0800 | [diff] [blame] | 748 | |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 749 | // Check first for a stable tag |
| 750 | final RegExp stableTagPattern = RegExp(r'^\d+\.\d+\.\d+$'); |
| 751 | for (final String tag in tags) { |
Christopher Fujino | 8dcb4c3 | 2020-08-11 17:29:35 -0700 | [diff] [blame] | 752 | if (stableTagPattern.hasMatch(tag.trim())) { |
| 753 | return parse(tag); |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 754 | } |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 755 | } |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 756 | // Next check for a dev tag |
| 757 | final RegExp devTagPattern = RegExp(r'^\d+\.\d+\.\d+-\d+\.\d+\.pre$'); |
| 758 | for (final String tag in tags) { |
Christopher Fujino | 8dcb4c3 | 2020-08-11 17:29:35 -0700 | [diff] [blame] | 759 | if (devTagPattern.hasMatch(tag.trim())) { |
| 760 | return parse(tag); |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 761 | } |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 762 | } |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 763 | |
| 764 | // If we're not currently on a tag, use git describe to find the most |
| 765 | // recent tag and number of commits past. |
| 766 | return parse( |
| 767 | _runGit( |
Anurag Roy | 05dadb0 | 2020-11-20 07:33:05 +0530 | [diff] [blame] | 768 | 'git describe --match *.*.* --long --tags $gitRef', |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 769 | processUtils, |
| 770 | workingDirectory, |
| 771 | ) |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 772 | ); |
| 773 | } |
| 774 | |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 775 | /// Parse a version string. |
| 776 | /// |
| 777 | /// The version string can either be an exact release tag (e.g. '1.2.3' for |
| 778 | /// stable or 1.2.3-4.5.pre for a dev) or the output of `git describe` (e.g. |
| 779 | /// for commit abc123 that is 6 commits after tag 1.2.3-4.5.pre, git would |
| 780 | /// return '1.2.3-4.5.pre-6-gabc123'). |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 781 | static GitTagVersion parseVersion(String version) { |
| 782 | final RegExp versionPattern = RegExp( |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 783 | r'^(\d+)\.(\d+)\.(\d+)(-\d+\.\d+\.pre)?(?:-(\d+)-g([a-f0-9]+))?$'); |
| 784 | final Match match = versionPattern.firstMatch(version.trim()); |
| 785 | if (match == null) { |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 786 | return const GitTagVersion.unknown(); |
| 787 | } |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 788 | |
| 789 | final List<String> matchGroups = match.groups(<int>[1, 2, 3, 4, 5, 6]); |
| 790 | final int x = matchGroups[0] == null ? null : int.tryParse(matchGroups[0]); |
| 791 | final int y = matchGroups[1] == null ? null : int.tryParse(matchGroups[1]); |
| 792 | final int z = matchGroups[2] == null ? null : int.tryParse(matchGroups[2]); |
| 793 | final String devString = matchGroups[3]; |
| 794 | int devVersion, devPatch; |
| 795 | if (devString != null) { |
| 796 | final Match devMatch = RegExp(r'^-(\d+)\.(\d+)\.pre$') |
| 797 | .firstMatch(devString); |
| 798 | final List<String> devGroups = devMatch.groups(<int>[1, 2]); |
| 799 | devVersion = devGroups[0] == null ? null : int.tryParse(devGroups[0]); |
| 800 | devPatch = devGroups[1] == null ? null : int.tryParse(devGroups[1]); |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 801 | } |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 802 | // count of commits past last tagged version |
| 803 | final int commits = matchGroups[4] == null ? 0 : int.tryParse(matchGroups[4]); |
| 804 | final String hash = matchGroups[5] ?? ''; |
| 805 | |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 806 | return GitTagVersion( |
Christopher Fujino | 37ac901 | 2020-04-23 15:19:27 -0700 | [diff] [blame] | 807 | x: x, |
| 808 | y: y, |
| 809 | z: z, |
| 810 | devVersion: devVersion, |
| 811 | devPatch: devPatch, |
| 812 | commits: commits, |
| 813 | hash: hash, |
| 814 | gitTag: '$x.$y.$z${devString ?? ''}', // e.g. 1.2.3-4.5.pre |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 815 | ); |
| 816 | } |
| 817 | |
| 818 | static GitTagVersion parse(String version) { |
| 819 | GitTagVersion gitTagVersion; |
| 820 | |
Christopher Fujino | 2396616 | 2020-04-03 09:39:28 -0700 | [diff] [blame] | 821 | gitTagVersion = parseVersion(version); |
| 822 | if (gitTagVersion != const GitTagVersion.unknown()) { |
| 823 | return gitTagVersion; |
| 824 | } |
| 825 | globals.printTrace('Could not interpret results of "git describe": $version'); |
| 826 | return const GitTagVersion.unknown(); |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 827 | } |
| 828 | |
| 829 | String frameworkVersionFor(String revision) { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 830 | if (x == null || y == null || z == null || !revision.startsWith(hash)) { |
Ian Hickson | 64e2e00 | 2018-01-26 16:59:56 -0800 | [diff] [blame] | 831 | return '0.0.0-unknown'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 832 | } |
Ian Hickson | a07c9a1 | 2019-03-08 19:26:34 -0800 | [diff] [blame] | 833 | if (commits == 0) { |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 834 | return gitTag; |
Ian Hickson | a07c9a1 | 2019-03-08 19:26:34 -0800 | [diff] [blame] | 835 | } |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 836 | if (hotfix != null) { |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 837 | // This is an unexpected state where untagged commits exist past a hotfix |
| 838 | return '$x.$y.$z+hotfix.${hotfix + 1}.pre.$commits'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 839 | } |
Christopher Fujino | 19c8948 | 2020-04-08 12:47:05 -0700 | [diff] [blame] | 840 | if (devPatch != null && devVersion != null) { |
| 841 | return '$x.$y.$z-${devVersion + 1}.0.pre.$commits'; |
| 842 | } |
Chris Yang | c9cd825 | 2020-08-05 15:20:27 -0700 | [diff] [blame] | 843 | return '$x.$y.${z + 1}-0.0.pre.$commits'; |
Ian Hickson | 9e42e4b | 2018-01-18 07:59:06 -0800 | [diff] [blame] | 844 | } |
| 845 | } |
Danny Tuppeny | e616c6c | 2018-06-27 12:50:40 +0100 | [diff] [blame] | 846 | |
| 847 | enum VersionCheckResult { |
| 848 | /// Unable to check whether a new version is available, possibly due to |
| 849 | /// a connectivity issue. |
| 850 | unknown, |
| 851 | /// The current version is up to date. |
| 852 | versionIsCurrent, |
| 853 | /// A newer version is available. |
| 854 | newVersionAvailable, |
| 855 | } |