Unverified Commit e13e1700 authored by Dan Field's avatar Dan Field Committed by GitHub

Reland fix `--version` (#52141)

parent db1f33fd
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import '../base/common.dart'; import '../base/common.dart';
import '../doctor.dart'; import '../doctor.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class DoctorCommand extends FlutterCommand { class DoctorCommand extends FlutterCommand {
...@@ -42,6 +43,7 @@ class DoctorCommand extends FlutterCommand { ...@@ -42,6 +43,7 @@ class DoctorCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
globals.flutterVersion.fetchTagsAndUpdate();
if (argResults.wasParsed('check-for-remote-artifacts')) { if (argResults.wasParsed('check-for-remote-artifacts')) {
final String engineRevision = stringArg('check-for-remote-artifacts'); final String engineRevision = stringArg('check-for-remote-artifacts');
if (engineRevision.startsWith(RegExp(r'[a-f0-9]{1,40}'))) { if (engineRevision.startsWith(RegExp(r'[a-f0-9]{1,40}'))) {
......
...@@ -322,6 +322,7 @@ class FlutterCommandRunner extends CommandRunner<void> { ...@@ -322,6 +322,7 @@ class FlutterCommandRunner extends CommandRunner<void> {
if (topLevelResults['version'] as bool) { if (topLevelResults['version'] as bool) {
globals.flutterUsage.sendCommand('version'); globals.flutterUsage.sendCommand('version');
globals.flutterVersion.fetchTagsAndUpdate();
String status; String status;
if (machineFlag) { if (machineFlag) {
status = const JsonEncoder.withIndent(' ').convert(globals.flutterVersion.toJson()); status = const JsonEncoder.withIndent(' ').convert(globals.flutterVersion.toJson());
......
...@@ -23,6 +23,9 @@ enum Channel { ...@@ -23,6 +23,9 @@ enum Channel {
stable, stable,
} }
/// The flutter GitHub repository.
const String _flutterGit = 'https://github.com/flutter/flutter.git';
/// Retrieve a human-readable name for a given [channel]. /// Retrieve a human-readable name for a given [channel].
/// ///
/// Requires [FlutterVersion.officialChannels] to be correctly ordered. /// Requires [FlutterVersion.officialChannels] to be correctly ordered.
...@@ -42,13 +45,29 @@ Channel getChannelForName(String name) { ...@@ -42,13 +45,29 @@ Channel getChannelForName(String name) {
} }
class FlutterVersion { class FlutterVersion {
/// Parses the Flutter version from currently available tags in the local
/// repo.
///
/// Call [fetchTagsAndUpdate] to update the version based on the latest tags
/// available upstream.
FlutterVersion([this._clock = const SystemClock(), this._workingDirectory]) { FlutterVersion([this._clock = const SystemClock(), this._workingDirectory]) {
_frameworkRevision = _runGit( _frameworkRevision = _runGit(
gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '), gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '),
processUtils, processUtils,
_workingDirectory, _workingDirectory,
); );
_gitTagVersion = GitTagVersion.determine(processUtils, _workingDirectory); _gitTagVersion = GitTagVersion.determine(processUtils, workingDirectory: _workingDirectory, fetchTags: false);
_frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision);
}
/// Fetchs tags from the upstream Flutter repository and re-calculates the
/// version.
///
/// This carries a performance penalty, and should only be called when the
/// user explicitly wants to get the version, e.g. for `flutter --version` or
/// `flutter doctor`.
void fetchTagsAndUpdate() {
_gitTagVersion = GitTagVersion.determine(processUtils, workingDirectory: _workingDirectory, fetchTags: true);
_frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision); _frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision);
} }
...@@ -233,7 +252,7 @@ class FlutterVersion { ...@@ -233,7 +252,7 @@ class FlutterVersion {
'remote', 'remote',
'add', 'add',
_versionCheckRemote, _versionCheckRemote,
'https://github.com/flutter/flutter.git', _flutterGit,
]); ]);
await _run(<String>['git', 'fetch', _versionCheckRemote, branch]); await _run(<String>['git', 'fetch', _versionCheckRemote, branch]);
return _latestGitCommitDate( return _latestGitCommitDate(
...@@ -701,7 +720,10 @@ class GitTagVersion { ...@@ -701,7 +720,10 @@ class GitTagVersion {
/// The git hash (or an abbreviation thereof) for this commit. /// The git hash (or an abbreviation thereof) for this commit.
final String hash; final String hash;
static GitTagVersion determine(ProcessUtils processUtils, [String workingDirectory]) { static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false}) {
if (fetchTags) {
_runGit('git fetch $_flutterGit --tags', processUtils, workingDirectory);
}
return parse(_runGit('git describe --match v*.*.* --first-parent --long --tags', processUtils, workingDirectory)); return parse(_runGit('git describe --match v*.*.* --first-parent --long --tags', processUtils, workingDirectory));
} }
......
...@@ -4,12 +4,17 @@ ...@@ -4,12 +4,17 @@
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
...@@ -722,6 +727,50 @@ void main() { ...@@ -722,6 +727,50 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
ProcessManager: () => MockProcessManager(), ProcessManager: () => MockProcessManager(),
}); });
testUsingContext('Fetches tags to get the right version', () async {
Cache.disableLocking();
final DoctorCommand doctorCommand = DoctorCommand();
final CommandRunner<void> commandRunner = createTestCommandRunner(doctorCommand);
await commandRunner.run(<String>['doctor']);
verify(mockFlutterVersion.fetchTagsAndUpdate()).called(1);
Cache.enableLocking();
}, overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => MemoryFileSystem.test(),
FlutterVersion: () => mockFlutterVersion,
Doctor: () => NoOpDoctor(),
}, initializeFlutterRoot: false);
}
class NoOpDoctor implements Doctor {
@override
bool get canLaunchAnything => true;
@override
bool get canListAnything => true;
@override
Future<bool> checkRemoteArtifacts(String engineRevision) async => true;
@override
Future<bool> diagnose({ bool androidLicenses = false, bool verbose = true, bool showColor = true }) async => true;
@override
List<ValidatorTask> startValidatorTasks() => <ValidatorTask>[];
@override
Future<void> summary() => null;
@override
List<DoctorValidator> get validators => <DoctorValidator>[];
@override
List<Workflow> get workflows => <Workflow>[];
} }
class MockUsage extends Mock implements Usage {} class MockUsage extends Mock implements Usage {}
...@@ -1062,4 +1111,3 @@ class VsCodeValidatorTestTargets extends VsCodeValidator { ...@@ -1062,4 +1111,3 @@ class VsCodeValidatorTestTargets extends VsCodeValidator {
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockFlutterVersion extends Mock implements FlutterVersion {}
...@@ -88,6 +88,18 @@ void main() { ...@@ -88,6 +88,18 @@ void main() {
Platform: () => platform, Platform: () => platform,
}, initializeFlutterRoot: false); }, initializeFlutterRoot: false);
testUsingContext('Fetches tags when --version is used', () async {
final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
await runner.run(<String>['--version']);
verify(version.fetchTagsAndUpdate()).called(1);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
}, initializeFlutterRoot: false);
testUsingContext('throw tool exit if the version file cannot be written', () async { testUsingContext('throw tool exit if the version file cannot be written', () async {
final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion; final MockFlutterVersion version = globals.flutterVersion as MockFlutterVersion;
when(version.ensureVersionFile()).thenThrow(const FileSystemException()); when(version.ensureVersionFile()).thenThrow(const FileSystemException());
...@@ -165,6 +177,8 @@ void main() { ...@@ -165,6 +177,8 @@ void main() {
workingDirectory: Cache.flutterRoot)).thenReturn(result); workingDirectory: Cache.flutterRoot)).thenReturn(result);
when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ar'.split(' ')), when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ar'.split(' ')),
workingDirectory: Cache.flutterRoot)).thenReturn(result); workingDirectory: Cache.flutterRoot)).thenReturn(result);
when(processManager.runSync('git fetch https://github.com/flutter/flutter.git --tags'.split(' '),
workingDirectory: Cache.flutterRoot)).thenReturn(result);
when(processManager.runSync('git describe --match v*.*.* --first-parent --long --tags'.split(' '), when(processManager.runSync('git describe --match v*.*.* --first-parent --long --tags'.split(' '),
workingDirectory: Cache.flutterRoot)).thenReturn(result); workingDirectory: Cache.flutterRoot)).thenReturn(result);
when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ad --date=iso'.split(' ')), when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ad --date=iso'.split(' ')),
......
...@@ -7,6 +7,7 @@ import 'dart:convert'; ...@@ -7,6 +7,7 @@ import 'dart:convert';
import 'package:collection/collection.dart' show ListEquality; import 'package:collection/collection.dart' show ListEquality;
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
...@@ -411,6 +412,60 @@ void main() { ...@@ -411,6 +412,60 @@ void main() {
'Could not interpret results of "git describe": v1.2.3-4-gxabcdef\n', 'Could not interpret results of "git describe": v1.2.3-4-gxabcdef\n',
); );
}); });
testUsingContext('determine does not call fetch --tags', () {
final MockProcessUtils processUtils = MockProcessUtils();
when(processUtils.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(105, 0, '', ''), <String>['git', 'fetch']));
when(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''), <String>['git', 'describe']));
GitTagVersion.determine(processUtils, workingDirectory: '.');
verifyNever(processUtils.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
));
verify(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).called(1);
});
testUsingContext('determine calls fetch --tags', () {
final MockProcessUtils processUtils = MockProcessUtils();
when(processUtils.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(105, 0, '', ''), <String>['git', 'fetch']));
when(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''), <String>['git', 'describe']));
GitTagVersion.determine(processUtils, workingDirectory: '.', fetchTags: true);
verify(processUtils.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).called(1);
verify(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).called(1);
});
} }
void _expectVersionMessage(String message) { void _expectVersionMessage(String message) {
...@@ -489,6 +544,8 @@ void fakeData( ...@@ -489,6 +544,8 @@ void fakeData(
// Careful here! argsAre accepts 9 arguments and FlutterVersion.gitLog adds 4. // Careful here! argsAre accepts 9 arguments and FlutterVersion.gitLog adds 4.
} else if (remoteCommitDate != null && listArgsAre(FlutterVersion.gitLog(<String>['__flutter_version_check__/$channel', '-n', '1', '--pretty=format:%ad', '--date=iso']))) { } else if (remoteCommitDate != null && listArgsAre(FlutterVersion.gitLog(<String>['__flutter_version_check__/$channel', '-n', '1', '--pretty=format:%ad', '--date=iso']))) {
return success(remoteCommitDate.toString()); return success(remoteCommitDate.toString());
} else if (argsAre('git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags')) {
return success('');
} }
throw StateError('Unexpected call to ProcessManager.run(${invocation.positionalArguments}, ${invocation.namedArguments})'); throw StateError('Unexpected call to ProcessManager.run(${invocation.positionalArguments}, ${invocation.namedArguments})');
...@@ -519,13 +576,18 @@ void fakeData( ...@@ -519,13 +576,18 @@ void fakeData(
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenReturn(ProcessResult(104, 0, '1 second ago', '')); )).thenReturn(ProcessResult(104, 0, '1 second ago', ''));
when(pm.runSync(
<String>['git', 'fetch', 'https://github.com/flutter/flutter', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(105, 0, '', ''));
when(pm.runSync( when(pm.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], <String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'), workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'), environment: anyNamed('environment'),
)).thenReturn(ProcessResult(105, 0, 'v0.1.2-3-1234abcd', '')); )).thenReturn(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''));
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockProcessUtils extends Mock implements ProcessUtils {}
class MockCache extends Mock implements Cache {} class MockCache extends Mock implements Cache {}
...@@ -647,6 +647,9 @@ class FakeHttpHeaders extends HttpHeaders { ...@@ -647,6 +647,9 @@ class FakeHttpHeaders extends HttpHeaders {
} }
class FakeFlutterVersion implements FlutterVersion { class FakeFlutterVersion implements FlutterVersion {
@override
void fetchTagsAndUpdate() { }
@override @override
String get channel => 'master'; String get channel => 'master';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment