Unverified Commit 3246808c authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tools] cache flutter sdk version to disk (#124558)

Fixes https://github.com/flutter/flutter/issues/112833

Most of the actual changes here are in [packages/flutter_tools/lib/src/version.dart](https://github.com/flutter/flutter/pull/124558/files#diff-092e00109d9e1589fbc7c6de750e29a6ae512b2dd44e85d60028953561201605), while the rest is largely just addressing changes to the constructor of `FlutterVersion` which now has different dependencies.

This change makes `FlutterVersion` an interface with two concrete implementations:

1. `_FlutterVersionGit` which is mostly the previous implementation, and
2. `_FlutterVersionFromFile` which will read a new `.version.json` file from the root of the repo

The [`FlutterVersion` constructor](https://github.com/flutter/flutter/pull/124558/files#diff-092e00109d9e1589fbc7c6de750e29a6ae512b2dd44e85d60028953561201605R70) is now a factory that first checks if `.version.json` exists, and if so returns an instance of `_FlutterVersionFromGit` else it returns the fallback `_FlutterVersionGit` which will end up writing `.version.json` so that we don't need to re-calculate the version on the next invocation.

`.version.json` will be deleted in the bash/batch entrypoints any time we need to rebuild he tool (this will usually be because the user did `flutter upgrade` or `flutter channel`, or manually changed the commit with git).
parent 6d2b5ea3
...@@ -128,6 +128,7 @@ GOTO :after_subroutine ...@@ -128,6 +128,7 @@ GOTO :after_subroutine
:do_snapshot :do_snapshot
IF EXIST "%FLUTTER_ROOT%\version" DEL "%FLUTTER_ROOT%\version" IF EXIST "%FLUTTER_ROOT%\version" DEL "%FLUTTER_ROOT%\version"
IF EXIST "%FLUTTER_ROOT%\bin\cache\flutter.version.json" DEL "%FLUTTER_ROOT%\bin\cache\flutter.version.json"
ECHO: > "%cache_dir%\.dartignore" ECHO: > "%cache_dir%\.dartignore"
ECHO Building flutter tool... 1>&2 ECHO Building flutter tool... 1>&2
PUSHD "%flutter_tools_dir%" PUSHD "%flutter_tools_dir%"
......
...@@ -123,7 +123,10 @@ function upgrade_flutter () ( ...@@ -123,7 +123,10 @@ function upgrade_flutter () (
# * STAMP_PATH is an empty file, or # * STAMP_PATH is an empty file, or
# * Contents of STAMP_PATH is not what we are going to compile, or # * Contents of STAMP_PATH is not what we are going to compile, or
# * pubspec.yaml last modified after pubspec.lock # * pubspec.yaml last modified after pubspec.lock
if [[ ! -f "$SNAPSHOT_PATH" || ! -s "$STAMP_PATH" || "$(cat "$STAMP_PATH")" != "$compilekey" || "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then if [[ ! -f "$SNAPSHOT_PATH" || \
! -s "$STAMP_PATH" || \
"$(cat "$STAMP_PATH")" != "$compilekey" || \
"$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then
# Waits for the update lock to be acquired. Placing this check inside the # Waits for the update lock to be acquired. Placing this check inside the
# conditional allows the majority of flutter/dart installations to bypass # conditional allows the majority of flutter/dart installations to bypass
# the lock entirely, but as a result this required a second verification that # the lock entirely, but as a result this required a second verification that
...@@ -137,6 +140,7 @@ function upgrade_flutter () ( ...@@ -137,6 +140,7 @@ function upgrade_flutter () (
# Fetch Dart... # Fetch Dart...
rm -f "$FLUTTER_ROOT/version" rm -f "$FLUTTER_ROOT/version"
rm -f "$FLUTTER_ROOT/bin/cache/flutter.version.json"
touch "$FLUTTER_ROOT/bin/cache/.dartignore" touch "$FLUTTER_ROOT/bin/cache/.dartignore"
"$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh" "$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh"
......
...@@ -33,7 +33,6 @@ class DoctorCommand extends FlutterCommand { ...@@ -33,7 +33,6 @@ class DoctorCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
globals.flutterVersion.fetchTagsAndUpdate();
if (argResults?.wasParsed('check-for-remote-artifacts') ?? false) { if (argResults?.wasParsed('check-for-remote-artifacts') ?? false) {
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}'))) {
......
...@@ -92,7 +92,10 @@ class DowngradeCommand extends FlutterCommand { ...@@ -92,7 +92,10 @@ class DowngradeCommand extends FlutterCommand {
String workingDirectory = Cache.flutterRoot!; String workingDirectory = Cache.flutterRoot!;
if (argResults!.wasParsed('working-directory')) { if (argResults!.wasParsed('working-directory')) {
workingDirectory = stringArg('working-directory')!; workingDirectory = stringArg('working-directory')!;
_flutterVersion = FlutterVersion(workingDirectory: workingDirectory); _flutterVersion = FlutterVersion(
fs: _fileSystem!,
flutterRoot: workingDirectory,
);
} }
final String currentChannel = _flutterVersion!.channel; final String currentChannel = _flutterVersion!.channel;
......
...@@ -80,7 +80,7 @@ class UpgradeCommand extends FlutterCommand { ...@@ -80,7 +80,7 @@ class UpgradeCommand extends FlutterCommand {
gitTagVersion: GitTagVersion.determine(globals.processUtils, globals.platform), gitTagVersion: GitTagVersion.determine(globals.processUtils, globals.platform),
flutterVersion: stringArg('working-directory') == null flutterVersion: stringArg('working-directory') == null
? globals.flutterVersion ? globals.flutterVersion
: FlutterVersion(workingDirectory: _commandRunner.workingDirectory), : FlutterVersion(flutterRoot: _commandRunner.workingDirectory!, fs: globals.fs),
verifyOnly: boolArg('verify-only'), verifyOnly: boolArg('verify-only'),
); );
} }
...@@ -297,7 +297,11 @@ class UpgradeCommandRunner { ...@@ -297,7 +297,11 @@ class UpgradeCommandRunner {
'for instructions.' 'for instructions.'
); );
} }
return FlutterVersion(workingDirectory: workingDirectory, frameworkRevision: revision); return FlutterVersion.fromRevision(
flutterRoot: workingDirectory!,
frameworkRevision: revision,
fs: globals.fs,
);
} }
/// Attempts a hard reset to the given revision. /// Attempts a hard reset to the given revision.
......
...@@ -226,7 +226,10 @@ Future<T> runInContext<T>( ...@@ -226,7 +226,10 @@ Future<T> runInContext<T>(
config: globals.config, config: globals.config,
platform: globals.platform, platform: globals.platform,
), ),
FlutterVersion: () => FlutterVersion(), FlutterVersion: () => FlutterVersion(
fs: globals.fs,
flutterRoot: Cache.flutterRoot!,
),
FuchsiaArtifacts: () => FuchsiaArtifacts.find(), FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
FuchsiaDeviceTools: () => FuchsiaDeviceTools(), FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
FuchsiaSdk: () => FuchsiaSdk(), FuchsiaSdk: () => FuchsiaSdk(),
......
...@@ -123,7 +123,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -123,7 +123,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
FlutterValidator( FlutterValidator(
fileSystem: globals.fs, fileSystem: globals.fs,
platform: globals.platform, platform: globals.platform,
flutterVersion: () => globals.flutterVersion, flutterVersion: () => globals.flutterVersion.fetchTagsAndGetVersion(clock: globals.systemClock),
devToolsVersion: () => globals.cache.devToolsVersion, devToolsVersion: () => globals.cache.devToolsVersion,
processManager: globals.processManager, processManager: globals.processManager,
userMessages: userMessages, userMessages: userMessages,
......
...@@ -136,7 +136,10 @@ class VariableDumpMachineProjectValidator extends MachineProjectValidator { ...@@ -136,7 +136,10 @@ class VariableDumpMachineProjectValidator extends MachineProjectValidator {
)); ));
// FlutterVersion // FlutterVersion
final FlutterVersion version = FlutterVersion(workingDirectory: project.directory.absolute.path); final FlutterVersion version = FlutterVersion(
flutterRoot: Cache.flutterRoot!,
fs: fileSystem,
);
result.add(ProjectValidatorResult( result.add(ProjectValidatorResult(
name: 'FlutterVersion.frameworkRevision', name: 'FlutterVersion.frameworkRevision',
value: _toJsonValue(version.frameworkRevision), value: _toJsonValue(version.frameworkRevision),
......
...@@ -18,6 +18,7 @@ import '../cache.dart'; ...@@ -18,6 +18,7 @@ import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../tester/flutter_tester.dart'; import '../tester/flutter_tester.dart';
import '../version.dart';
import '../web/web_device.dart'; import '../web/web_device.dart';
/// Common flutter command line options. /// Common flutter command line options.
...@@ -318,14 +319,16 @@ class FlutterCommandRunner extends CommandRunner<void> { ...@@ -318,14 +319,16 @@ class FlutterCommandRunner extends CommandRunner<void> {
if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) { if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) {
globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag); globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag);
globals.flutterVersion.fetchTagsAndUpdate(); final FlutterVersion version = globals.flutterVersion.fetchTagsAndGetVersion(
String status; clock: globals.systemClock,
);
final String status;
if (machineFlag) { if (machineFlag) {
final Map<String, Object> jsonOut = globals.flutterVersion.toJson(); final Map<String, Object> jsonOut = version.toJson();
jsonOut['flutterRoot'] = Cache.flutterRoot!; jsonOut['flutterRoot'] = Cache.flutterRoot!;
status = const JsonEncoder.withIndent(' ').convert(jsonOut); status = const JsonEncoder.withIndent(' ').convert(jsonOut);
} else { } else {
status = globals.flutterVersion.toString(); status = version.toString();
} }
globals.printStatus(status); globals.printStatus(status);
return; return;
......
This diff is collapsed.
...@@ -13,8 +13,10 @@ import 'base/context.dart'; ...@@ -13,8 +13,10 @@ import 'base/context.dart';
import 'base/io.dart' as io; import 'base/io.dart' as io;
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'cache.dart';
import 'convert.dart'; import 'convert.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart' as globals;
import 'ios/xcodeproj.dart'; import 'ios/xcodeproj.dart';
import 'project.dart'; import 'project.dart';
import 'version.dart'; import 'version.dart';
...@@ -244,7 +246,10 @@ Future<vm_service.VmService> setUpVmService({ ...@@ -244,7 +246,10 @@ Future<vm_service.VmService> setUpVmService({
} }
vmService.registerServiceCallback(kFlutterVersionServiceName, (Map<String, Object?> params) async { vmService.registerServiceCallback(kFlutterVersionServiceName, (Map<String, Object?> params) async {
final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion(); final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion(
fs: globals.fs,
flutterRoot: Cache.flutterRoot!,
);
final Map<String, Object> versionJson = version.toJson(); final Map<String, Object> versionJson = version.toJson();
versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
versionJson['engineRevisionShort'] = version.engineRevisionShort; versionJson['engineRevisionShort'] = version.engineRevisionShort;
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:fake_async/fake_async.dart'; import 'package:fake_async/fake_async.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_studio_validator.dart'; import 'package:flutter_tools/src/android/android_studio_validator.dart';
...@@ -16,7 +15,6 @@ import 'package:flutter_tools/src/base/terminal.dart'; ...@@ -16,7 +15,6 @@ 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/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/doctor.dart';
import 'package:flutter_tools/src/custom_devices/custom_device_workflow.dart'; import 'package:flutter_tools/src/custom_devices/custom_device_workflow.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
...@@ -32,17 +30,16 @@ import 'package:test/fake.dart'; ...@@ -32,17 +30,16 @@ import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/fakes.dart'; import '../../src/fakes.dart';
import '../../src/test_flutter_command_runner.dart';
void main() { void main() {
late FakeFlutterVersion flutterVersion;
late BufferLogger logger; late BufferLogger logger;
late FakeProcessManager fakeProcessManager; late FakeProcessManager fakeProcessManager;
late MemoryFileSystem fs;
setUp(() { setUp(() {
flutterVersion = FakeFlutterVersion();
logger = BufferLogger.test(); logger = BufferLogger.test();
fakeProcessManager = FakeProcessManager.empty(); fakeProcessManager = FakeProcessManager.empty();
fs = MemoryFileSystem.test();
}); });
testWithoutContext('ValidationMessage equality and hashCode includes contextUrl', () { testWithoutContext('ValidationMessage equality and hashCode includes contextUrl', () {
...@@ -761,27 +758,55 @@ void main() { ...@@ -761,27 +758,55 @@ void main() {
contains(isA<CustomDeviceWorkflow>()), contains(isA<CustomDeviceWorkflow>()),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fs,
ProcessManager: () => fakeProcessManager, ProcessManager: () => fakeProcessManager,
}); });
testUsingContext('Fetches tags to get the right version', () async { group('FlutterValidator', () {
Cache.disableLocking(); late FakeFlutterVersion initialVersion;
late FakeFlutterVersion secondVersion;
final DoctorCommand doctorCommand = DoctorCommand(); late TestFeatureFlags featureFlags;
final CommandRunner<void> commandRunner = createTestCommandRunner(doctorCommand);
await commandRunner.run(<String>['doctor']); setUp(() {
secondVersion = FakeFlutterVersion(frameworkRevisionShort: '222');
initialVersion = FakeFlutterVersion(
frameworkRevisionShort: '111',
nextFlutterVersion: secondVersion,
);
featureFlags = TestFeatureFlags();
});
expect(flutterVersion.didFetchTagsAndUpdate, true); testUsingContext('FlutterValidator fetches tags and gets fresh version', () async {
Cache.enableLocking(); final Directory devtoolsDir = fs.directory('/path/to/flutter/bin/cache/dart-sdk/bin/resources/devtools')
..createSync(recursive: true);
fs.directory('/path/to/flutter/bin/cache/artifacts').createSync(recursive: true);
devtoolsDir.childFile('version.json').writeAsStringSync('{"version": "123"}');
fakeProcessManager.addCommands(const <FakeCommand>[
FakeCommand(command: <String>['which', 'java']),
]);
final List<DoctorValidator> validators = DoctorValidatorsProvider.test(
featureFlags: featureFlags,
platform: FakePlatform(),
).validators;
final FlutterValidator flutterValidator = validators.whereType<FlutterValidator>().first;
final ValidationResult result = await flutterValidator.validate();
expect(
result.messages.map((ValidationMessage msg) => msg.message),
contains(contains('Framework revision 222')),
);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.any(), Cache: () => Cache.test(
FileSystem: () => MemoryFileSystem.test(), rootOverride: fs.directory('/path/to/flutter'),
FlutterVersion: () => flutterVersion, fileSystem: fs,
Doctor: () => NoOpDoctor(), processManager: fakeProcessManager,
}, initializeFlutterRoot: false); ),
FileSystem: () => fs,
FlutterVersion: () => initialVersion,
Platform: () => FakePlatform(),
ProcessManager: () => fakeProcessManager,
TestFeatureFlags: () => featureFlags,
});
});
testUsingContext('If android workflow is disabled, AndroidStudio validator is not included', () { testUsingContext('If android workflow is disabled, AndroidStudio validator is not included', () {
final DoctorValidatorsProvider provider = DoctorValidatorsProvider.test( final DoctorValidatorsProvider provider = DoctorValidatorsProvider.test(
featureFlags: TestFeatureFlags(isAndroidEnabled: false), featureFlags: TestFeatureFlags(isAndroidEnabled: false),
...@@ -826,6 +851,7 @@ class NoOpDoctor implements Doctor { ...@@ -826,6 +851,7 @@ class NoOpDoctor implements Doctor {
bool showPii = true, bool showPii = true,
List<ValidatorTask>? startedValidatorTasks, List<ValidatorTask>? startedValidatorTasks,
bool sendEvent = true, bool sendEvent = true,
FlutterVersion? version,
}) async => true; }) async => true;
@override @override
......
...@@ -37,7 +37,8 @@ void main() { ...@@ -37,7 +37,8 @@ void main() {
setUp(() { setUp(() {
fakeCommandRunner = FakeUpgradeCommandRunner(); fakeCommandRunner = FakeUpgradeCommandRunner();
realCommandRunner = UpgradeCommandRunner(); realCommandRunner = UpgradeCommandRunner()
..workingDirectory = Cache.flutterRoot;
processManager = FakeProcessManager.empty(); processManager = FakeProcessManager.empty();
fakeCommandRunner.willHaveUncommittedChanges = false; fakeCommandRunner.willHaveUncommittedChanges = false;
fakePlatform = FakePlatform()..environment = Map<String, String>.unmodifiable(<String, String>{ fakePlatform = FakePlatform()..environment = Map<String, String>.unmodifiable(<String, String>{
......
...@@ -41,11 +41,14 @@ void main() { ...@@ -41,11 +41,14 @@ void main() {
group('analytics', () { group('analytics', () {
late Directory tempDir; late Directory tempDir;
late Config testConfig; late Config testConfig;
late FileSystem fs;
const String flutterRoot = '/path/to/flutter';
setUp(() { setUp(() {
Cache.flutterRoot = '../..'; Cache.flutterRoot = flutterRoot;
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_analytics_test.'); tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_analytics_test.');
testConfig = Config.test(); testConfig = Config.test();
fs = MemoryFileSystem.test();
}); });
tearDown(() { tearDown(() {
...@@ -77,7 +80,7 @@ void main() { ...@@ -77,7 +80,7 @@ void main() {
expect(count, 0); expect(count, 0);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(), FlutterVersion: () => FakeFlutterVersion(),
Usage: () => Usage( Usage: () => Usage(
configDirOverride: tempDir.path, configDirOverride: tempDir.path,
logFile: tempDir.childFile('analytics.log').path, logFile: tempDir.childFile('analytics.log').path,
...@@ -101,7 +104,7 @@ void main() { ...@@ -101,7 +104,7 @@ void main() {
expect(count, 0); expect(count, 0);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(), FlutterVersion: () => FakeFlutterVersion(),
Usage: () => Usage( Usage: () => Usage(
configDirOverride: tempDir.path, configDirOverride: tempDir.path,
logFile: tempDir.childFile('analytics.log').path, logFile: tempDir.childFile('analytics.log').path,
...@@ -118,12 +121,12 @@ void main() { ...@@ -118,12 +121,12 @@ void main() {
expect(globals.fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web')); expect(globals.fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(), FlutterVersion: () => FakeFlutterVersion(),
Config: () => testConfig, Config: () => testConfig,
Platform: () => FakePlatform(environment: <String, String>{ Platform: () => FakePlatform(environment: <String, String>{
'FLUTTER_ANALYTICS_LOG_FILE': 'test', 'FLUTTER_ANALYTICS_LOG_FILE': 'test',
}), }),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
...@@ -141,12 +144,12 @@ void main() { ...@@ -141,12 +144,12 @@ void main() {
contains('$featuresKey: enable-web,enable-linux-desktop,enable-macos-desktop'), contains('$featuresKey: enable-web,enable-linux-desktop,enable-macos-desktop'),
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(), FlutterVersion: () => FakeFlutterVersion(),
Config: () => testConfig, Config: () => testConfig,
Platform: () => FakePlatform(environment: <String, String>{ Platform: () => FakePlatform(environment: <String, String>{
'FLUTTER_ANALYTICS_LOG_FILE': 'test', 'FLUTTER_ANALYTICS_LOG_FILE': 'test',
}), }),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
}); });
...@@ -384,6 +387,7 @@ class FakeDoctor extends Fake implements Doctor { ...@@ -384,6 +387,7 @@ class FakeDoctor extends Fake implements Doctor {
bool showPii = true, bool showPii = true,
List<ValidatorTask>? startedValidatorTasks, List<ValidatorTask>? startedValidatorTasks,
bool sendEvent = true, bool sendEvent = true,
FlutterVersion? version,
}) async { }) async {
return diagnoseSucceeds; return diagnoseSucceeds;
} }
......
...@@ -4,12 +4,13 @@ ...@@ -4,12 +4,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.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/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
...@@ -18,7 +19,7 @@ import '../src/context.dart'; ...@@ -18,7 +19,7 @@ import '../src/context.dart';
import '../src/fake_process_manager.dart'; import '../src/fake_process_manager.dart';
import '../src/fakes.dart' show FakeFlutterVersion; import '../src/fakes.dart' show FakeFlutterVersion;
final SystemClock _testClock = SystemClock.fixed(DateTime(2015)); final SystemClock _testClock = SystemClock.fixed(DateTime.utc(2015));
final DateTime _stampUpToDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate ~/ 2); final DateTime _stampUpToDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate ~/ 2);
final DateTime _stampOutOfDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate * 2); final DateTime _stampOutOfDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate * 2);
...@@ -49,7 +50,11 @@ void main() { ...@@ -49,7 +50,11 @@ void main() {
} }
group('$FlutterVersion for $channel', () { group('$FlutterVersion for $channel', () {
late FileSystem fs;
const String flutterRoot = '/path/to/flutter';
setUpAll(() { setUpAll(() {
fs = MemoryFileSystem.test();
Cache.disableLocking(); Cache.disableLocking();
VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero; VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero;
}); });
...@@ -101,7 +106,7 @@ void main() { ...@@ -101,7 +106,7 @@ void main() {
), ),
]); ]);
final FlutterVersion flutterVersion = globals.flutterVersion; final FlutterVersion flutterVersion = FlutterVersion(clock: _testClock, fs: fs, flutterRoot: flutterRoot);
await flutterVersion.checkFlutterVersionFreshness(); await flutterVersion.checkFlutterVersionFreshness();
expect(flutterVersion.channel, channel); expect(flutterVersion.channel, channel);
expect(flutterVersion.repositoryUrl, flutterUpstreamUrl); expect(flutterVersion.repositoryUrl, flutterUpstreamUrl);
...@@ -124,7 +129,6 @@ void main() { ...@@ -124,7 +129,6 @@ void main() {
expect(testLogger.statusText, isEmpty); expect(testLogger.statusText, isEmpty);
expect(processManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => processManager, ProcessManager: () => processManager,
Cache: () => cache, Cache: () => cache,
}); });
...@@ -419,15 +423,197 @@ void main() { ...@@ -419,15 +423,197 @@ void main() {
), ),
]); ]);
final FlutterVersion flutterVersion = globals.flutterVersion; final MemoryFileSystem fs = MemoryFileSystem.test();
final FlutterVersion flutterVersion = FlutterVersion(
clock: _testClock,
fs: fs,
flutterRoot: '/path/to/flutter',
);
expect(flutterVersion.channel, '[user-branch]'); expect(flutterVersion.channel, '[user-branch]');
expect(flutterVersion.getVersionString(), 'feature-branch/1234abcd'); expect(flutterVersion.getVersionString(), 'feature-branch/1234abcd');
expect(flutterVersion.getBranchName(), 'feature-branch'); expect(flutterVersion.getBranchName(), 'feature-branch');
expect(flutterVersion.getVersionString(redactUnknownBranches: true), '[user-branch]/1234abcd'); expect(flutterVersion.getVersionString(redactUnknownBranches: true), '[user-branch]/1234abcd');
expect(flutterVersion.getBranchName(redactUnknownBranches: true), '[user-branch]'); expect(flutterVersion.getBranchName(redactUnknownBranches: true), '[user-branch]');
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
Cache: () => cache,
});
testUsingContext('ensureVersionFile() writes version information to disk', () async {
processManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>['git', '-c', 'log.showSignature=false', 'log', '-n', '1', '--pretty=format:%H'],
stdout: '1234abcd',
),
const FakeCommand(
command: <String>['git', 'tag', '--points-at', '1234abcd'],
),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
stdout: '0.1.2-3-1234abcd',
),
const FakeCommand(
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
stdout: 'feature-branch',
),
const FakeCommand(
command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'],
),
FakeCommand(
command: const <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'HEAD',
'-n',
'1',
'--pretty=format:%ad',
'--date=iso',
],
stdout: _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2).toString(),
),
]);
final MemoryFileSystem fs = MemoryFileSystem.test();
final Directory flutterRoot = fs.directory('/path/to/flutter');
flutterRoot.childDirectory('bin').childDirectory('cache').createSync(recursive: true);
final FlutterVersion flutterVersion = FlutterVersion(
clock: _testClock,
fs: fs,
flutterRoot: flutterRoot.path,
);
final File versionFile = fs.file('/path/to/flutter/bin/cache/flutter.version.json');
expect(versionFile.existsSync(), isFalse);
flutterVersion.ensureVersionFile();
expect(versionFile.existsSync(), isTrue);
expect(versionFile.readAsStringSync(), '''
{
"frameworkVersion": "0.0.0-unknown",
"channel": "[user-branch]",
"repositoryUrl": "unknown source",
"frameworkRevision": "1234abcd",
"frameworkCommitDate": "2014-10-02 00:00:00.000Z",
"engineRevision": "abcdefg",
"dartSdkVersion": "2.12.0",
"devToolsVersion": "2.8.0",
"flutterVersion": "0.0.0-unknown"
}''');
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
Cache: () => cache,
});
testUsingContext('version does not call git if a .version.json file exists', () async {
final MemoryFileSystem fs = MemoryFileSystem.test();
final Directory flutterRoot = fs.directory('/path/to/flutter');
final Directory cacheDir = flutterRoot
.childDirectory('bin')
.childDirectory('cache')
..createSync(recursive: true);
const String devToolsVersion = '0000000';
const Map<String, Object> versionJson = <String, Object>{
'channel': 'stable',
'frameworkVersion': '1.2.3',
'repositoryUrl': 'https://github.com/flutter/flutter.git',
'frameworkRevision': '1234abcd',
'frameworkCommitDate': '2023-04-28 12:34:56 -0400',
'engineRevision': 'deadbeef',
'dartSdkVersion': 'deadbeef2',
'devToolsVersion': devToolsVersion,
'flutterVersion': 'foo',
};
cacheDir.childFile('flutter.version.json').writeAsStringSync(
jsonEncode(versionJson),
);
final FlutterVersion flutterVersion = FlutterVersion(
clock: _testClock,
fs: fs,
flutterRoot: flutterRoot.path,
);
expect(flutterVersion.channel, 'stable');
expect(flutterVersion.getVersionString(), 'stable/1.2.3');
expect(flutterVersion.getBranchName(), 'stable');
expect(flutterVersion.dartSdkVersion, 'deadbeef2');
expect(flutterVersion.devToolsVersion, devToolsVersion);
expect(flutterVersion.engineRevision, 'deadbeef');
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
Cache: () => cache,
});
testUsingContext('FlutterVersion() falls back to git if .version.json is malformed', () async {
final MemoryFileSystem fs = MemoryFileSystem.test();
final Directory flutterRoot = fs.directory(fs.path.join('path', 'to', 'flutter'));
final Directory cacheDir = flutterRoot
.childDirectory('bin')
.childDirectory('cache')
..createSync(recursive: true);
final File legacyVersionFile = flutterRoot.childFile('version');
final File versionFile = cacheDir.childFile('flutter.version.json')..writeAsStringSync(
'{',
);
processManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>['git', '-c', 'log.showSignature=false', 'log', '-n', '1', '--pretty=format:%H'],
stdout: '1234abcd',
),
const FakeCommand(
command: <String>['git', 'tag', '--points-at', '1234abcd'],
),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
stdout: '0.1.2-3-1234abcd',
),
const FakeCommand(
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
stdout: 'feature-branch',
),
const FakeCommand(
command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'],
stdout: 'feature-branch',
),
FakeCommand(
command: const <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'HEAD',
'-n',
'1',
'--pretty=format:%ad',
'--date=iso',
],
stdout: _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2).toString(),
),
]);
// version file exists in a malformed state
expect(versionFile.existsSync(), isTrue);
final FlutterVersion flutterVersion = FlutterVersion(
clock: _testClock,
fs: fs,
flutterRoot: flutterRoot.path,
);
// version file was deleted because it couldn't be parsed
expect(versionFile.existsSync(), isFalse);
expect(legacyVersionFile.existsSync(), isFalse);
// version file was written to disk
flutterVersion.ensureVersionFile();
expect(processManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
expect(versionFile.existsSync(), isTrue);
expect(legacyVersionFile.existsSync(), isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => FlutterVersion(clock: _testClock),
ProcessManager: () => processManager, ProcessManager: () => processManager,
Cache: () => cache, Cache: () => cache,
}); });
......
...@@ -158,7 +158,6 @@ void main() { ...@@ -158,7 +158,6 @@ void main() {
expect(decoded['FlutterProject.isModule'], false); expect(decoded['FlutterProject.isModule'], false);
expect(decoded['FlutterProject.isPlugin'], false); expect(decoded['FlutterProject.isPlugin'], false);
expect(decoded['FlutterProject.manifest.appname'], 'test_project'); expect(decoded['FlutterProject.manifest.appname'], 'test_project');
expect(decoded['FlutterVersion.frameworkRevision'], '');
expect(decoded['Platform.isAndroid'], false); expect(decoded['Platform.isAndroid'], false);
expect(decoded['Platform.isIOS'], false); expect(decoded['Platform.isIOS'], false);
......
...@@ -43,8 +43,6 @@ void main() { ...@@ -43,8 +43,6 @@ void main() {
final Directory testDirectory = parentDirectory.childDirectory('flutter'); final Directory testDirectory = parentDirectory.childDirectory('flutter');
testDirectory.createSync(recursive: true); testDirectory.createSync(recursive: true);
int exitCode = 0;
// Enable longpaths for windows integration test. // Enable longpaths for windows integration test.
await processManager.run(<String>[ await processManager.run(<String>[
'git', 'config', '--system', 'core.longpaths', 'true', 'git', 'config', '--system', 'core.longpaths', 'true',
......
...@@ -24,14 +24,18 @@ void main() { ...@@ -24,14 +24,18 @@ void main() {
); );
exampleAppDir = tempDir.childDirectory('bbb').childDirectory('example'); exampleAppDir = tempDir.childDirectory('bbb').childDirectory('example');
processManager.runSync(<String>[ processManager.runSync(
<String>[
flutterBin, flutterBin,
...getLocalEngineArguments(), ...getLocalEngineArguments(),
'create', 'create',
'--template=plugin', '--template=plugin',
'--platforms=android', '--platforms=android',
'bbb', 'bbb',
], workingDirectory: tempDir.path); '-v',
],
workingDirectory: tempDir.path,
);
}); });
tearDown(() async { tearDown(() async {
...@@ -48,14 +52,17 @@ void main() { ...@@ -48,14 +52,17 @@ void main() {
// Ensure file is gone prior to configOnly running. // Ensure file is gone prior to configOnly running.
await gradleFile.delete(); await gradleFile.delete();
final ProcessResult result = processManager.runSync(<String>[ final ProcessResult result = processManager.runSync(
<String>[
flutterBin, flutterBin,
...getLocalEngineArguments(), ...getLocalEngineArguments(),
'build', 'build',
'apk', 'apk',
'--target-platform=android-arm', '--target-platform=android-arm',
'--config-only', '--config-only',
], workingDirectory: exampleAppDir.path); ],
workingDirectory: exampleAppDir.path,
);
expect(gradleFile, exists); expect(gradleFile, exists);
expect(result.stdout, contains(RegExp(r'Config complete'))); expect(result.stdout, contains(RegExp(r'Config complete')));
......
...@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -13,6 +13,7 @@ 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/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/convert.dart';
...@@ -337,6 +338,8 @@ class FakeFlutterVersion implements FlutterVersion { ...@@ -337,6 +338,8 @@ class FakeFlutterVersion implements FlutterVersion {
this.frameworkAge = '0 hours ago', this.frameworkAge = '0 hours ago',
this.frameworkCommitDate = '12/01/01', this.frameworkCommitDate = '12/01/01',
this.gitTagVersion = const GitTagVersion.unknown(), this.gitTagVersion = const GitTagVersion.unknown(),
this.flutterRoot = '/path/to/flutter',
this.nextFlutterVersion,
}); });
final String branch; final String branch;
...@@ -344,6 +347,17 @@ class FakeFlutterVersion implements FlutterVersion { ...@@ -344,6 +347,17 @@ class FakeFlutterVersion implements FlutterVersion {
bool get didFetchTagsAndUpdate => _didFetchTagsAndUpdate; bool get didFetchTagsAndUpdate => _didFetchTagsAndUpdate;
bool _didFetchTagsAndUpdate = false; bool _didFetchTagsAndUpdate = false;
/// Will be returned by [fetchTagsAndGetVersion] if not null.
final FlutterVersion? nextFlutterVersion;
@override
FlutterVersion fetchTagsAndGetVersion({
SystemClock clock = const SystemClock(),
}) {
_didFetchTagsAndUpdate = true;
return nextFlutterVersion ?? this;
}
bool get didCheckFlutterVersionFreshness => _didCheckFlutterVersionFreshness; bool get didCheckFlutterVersionFreshness => _didCheckFlutterVersionFreshness;
bool _didCheckFlutterVersionFreshness = false; bool _didCheckFlutterVersionFreshness = false;
...@@ -355,6 +369,9 @@ class FakeFlutterVersion implements FlutterVersion { ...@@ -355,6 +369,9 @@ class FakeFlutterVersion implements FlutterVersion {
return kUserBranch; return kUserBranch;
} }
@override
final String flutterRoot;
@override @override
final String devToolsVersion; final String devToolsVersion;
...@@ -385,16 +402,11 @@ class FakeFlutterVersion implements FlutterVersion { ...@@ -385,16 +402,11 @@ class FakeFlutterVersion implements FlutterVersion {
@override @override
final String frameworkCommitDate; final String frameworkCommitDate;
@override
String get frameworkDate => frameworkCommitDate;
@override @override
final GitTagVersion gitTagVersion; final GitTagVersion gitTagVersion;
@override @override
void fetchTagsAndUpdate() { FileSystem get fs => throw UnimplementedError('FakeFlutterVersion.fs is not implemented');
_didFetchTagsAndUpdate = true;
}
@override @override
Future<void> checkFlutterVersionFreshness() async { Future<void> checkFlutterVersionFreshness() async {
......
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