mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[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).
This commit is contained in:
parent
6d2b5ea30e
commit
3246808cd2
@ -128,6 +128,7 @@ GOTO :after_subroutine
|
||||
|
||||
:do_snapshot
|
||||
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 Building flutter tool... 1>&2
|
||||
PUSHD "%flutter_tools_dir%"
|
||||
|
@ -123,7 +123,10 @@ function upgrade_flutter () (
|
||||
# * STAMP_PATH is an empty file, or
|
||||
# * Contents of STAMP_PATH is not what we are going to compile, or
|
||||
# * 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
|
||||
# conditional allows the majority of flutter/dart installations to bypass
|
||||
# the lock entirely, but as a result this required a second verification that
|
||||
@ -137,6 +140,7 @@ function upgrade_flutter () (
|
||||
|
||||
# Fetch Dart...
|
||||
rm -f "$FLUTTER_ROOT/version"
|
||||
rm -f "$FLUTTER_ROOT/bin/cache/flutter.version.json"
|
||||
touch "$FLUTTER_ROOT/bin/cache/.dartignore"
|
||||
"$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh"
|
||||
|
||||
|
@ -33,7 +33,6 @@ class DoctorCommand extends FlutterCommand {
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
globals.flutterVersion.fetchTagsAndUpdate();
|
||||
if (argResults?.wasParsed('check-for-remote-artifacts') ?? false) {
|
||||
final String engineRevision = stringArg('check-for-remote-artifacts')!;
|
||||
if (engineRevision.startsWith(RegExp(r'[a-f0-9]{1,40}'))) {
|
||||
|
@ -92,7 +92,10 @@ class DowngradeCommand extends FlutterCommand {
|
||||
String workingDirectory = Cache.flutterRoot!;
|
||||
if (argResults!.wasParsed('working-directory')) {
|
||||
workingDirectory = stringArg('working-directory')!;
|
||||
_flutterVersion = FlutterVersion(workingDirectory: workingDirectory);
|
||||
_flutterVersion = FlutterVersion(
|
||||
fs: _fileSystem!,
|
||||
flutterRoot: workingDirectory,
|
||||
);
|
||||
}
|
||||
|
||||
final String currentChannel = _flutterVersion!.channel;
|
||||
|
@ -80,7 +80,7 @@ class UpgradeCommand extends FlutterCommand {
|
||||
gitTagVersion: GitTagVersion.determine(globals.processUtils, globals.platform),
|
||||
flutterVersion: stringArg('working-directory') == null
|
||||
? globals.flutterVersion
|
||||
: FlutterVersion(workingDirectory: _commandRunner.workingDirectory),
|
||||
: FlutterVersion(flutterRoot: _commandRunner.workingDirectory!, fs: globals.fs),
|
||||
verifyOnly: boolArg('verify-only'),
|
||||
);
|
||||
}
|
||||
@ -297,7 +297,11 @@ class UpgradeCommandRunner {
|
||||
'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.
|
||||
|
@ -226,7 +226,10 @@ Future<T> runInContext<T>(
|
||||
config: globals.config,
|
||||
platform: globals.platform,
|
||||
),
|
||||
FlutterVersion: () => FlutterVersion(),
|
||||
FlutterVersion: () => FlutterVersion(
|
||||
fs: globals.fs,
|
||||
flutterRoot: Cache.flutterRoot!,
|
||||
),
|
||||
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
|
||||
FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
|
||||
FuchsiaSdk: () => FuchsiaSdk(),
|
||||
|
@ -123,7 +123,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
FlutterValidator(
|
||||
fileSystem: globals.fs,
|
||||
platform: globals.platform,
|
||||
flutterVersion: () => globals.flutterVersion,
|
||||
flutterVersion: () => globals.flutterVersion.fetchTagsAndGetVersion(clock: globals.systemClock),
|
||||
devToolsVersion: () => globals.cache.devToolsVersion,
|
||||
processManager: globals.processManager,
|
||||
userMessages: userMessages,
|
||||
|
@ -136,7 +136,10 @@ class VariableDumpMachineProjectValidator extends MachineProjectValidator {
|
||||
));
|
||||
|
||||
// FlutterVersion
|
||||
final FlutterVersion version = FlutterVersion(workingDirectory: project.directory.absolute.path);
|
||||
final FlutterVersion version = FlutterVersion(
|
||||
flutterRoot: Cache.flutterRoot!,
|
||||
fs: fileSystem,
|
||||
);
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterVersion.frameworkRevision',
|
||||
value: _toJsonValue(version.frameworkRevision),
|
||||
|
@ -18,6 +18,7 @@ import '../cache.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../tester/flutter_tester.dart';
|
||||
import '../version.dart';
|
||||
import '../web/web_device.dart';
|
||||
|
||||
/// Common flutter command line options.
|
||||
@ -318,14 +319,16 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
|
||||
if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) {
|
||||
globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag);
|
||||
globals.flutterVersion.fetchTagsAndUpdate();
|
||||
String status;
|
||||
final FlutterVersion version = globals.flutterVersion.fetchTagsAndGetVersion(
|
||||
clock: globals.systemClock,
|
||||
);
|
||||
final String status;
|
||||
if (machineFlag) {
|
||||
final Map<String, Object> jsonOut = globals.flutterVersion.toJson();
|
||||
final Map<String, Object> jsonOut = version.toJson();
|
||||
jsonOut['flutterRoot'] = Cache.flutterRoot!;
|
||||
status = const JsonEncoder.withIndent(' ').convert(jsonOut);
|
||||
} else {
|
||||
status = globals.flutterVersion.toString();
|
||||
status = version.toString();
|
||||
}
|
||||
globals.printStatus(status);
|
||||
return;
|
||||
|
@ -79,104 +79,153 @@ Channel? getChannelForName(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
class FlutterVersion {
|
||||
abstract 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({
|
||||
factory FlutterVersion({
|
||||
SystemClock clock = const SystemClock(),
|
||||
String? workingDirectory,
|
||||
String? frameworkRevision,
|
||||
}) : _clock = clock,
|
||||
_workingDirectory = workingDirectory {
|
||||
_frameworkRevision = frameworkRevision ?? _runGit(
|
||||
gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '),
|
||||
globals.processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
_gitTagVersion = GitTagVersion.determine(globals.processUtils, globals.platform, workingDirectory: _workingDirectory, gitRef: _frameworkRevision);
|
||||
_frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision);
|
||||
}
|
||||
required FileSystem fs,
|
||||
required String flutterRoot,
|
||||
@protected
|
||||
bool fetchTags = false,
|
||||
}) {
|
||||
final File versionFile = getVersionFile(fs, flutterRoot);
|
||||
|
||||
final SystemClock _clock;
|
||||
final String? _workingDirectory;
|
||||
|
||||
/// Fetches 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(globals.processUtils, globals.platform, workingDirectory: _workingDirectory, fetchTags: true);
|
||||
_frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision);
|
||||
}
|
||||
|
||||
String? _repositoryUrl;
|
||||
String? get repositoryUrl {
|
||||
if (_repositoryUrl == null) {
|
||||
final String gitChannel = _runGit(
|
||||
'git rev-parse --abbrev-ref --symbolic $kGitTrackingUpstream',
|
||||
globals.processUtils,
|
||||
_workingDirectory,
|
||||
if (!fetchTags && versionFile.existsSync()) {
|
||||
final _FlutterVersionFromFile? version = _FlutterVersionFromFile.tryParseFromFile(
|
||||
versionFile,
|
||||
flutterRoot: flutterRoot,
|
||||
);
|
||||
final int slash = gitChannel.indexOf('/');
|
||||
if (slash != -1) {
|
||||
final String remote = gitChannel.substring(0, slash);
|
||||
_repositoryUrl = _runGit(
|
||||
'git ls-remote --get-url $remote',
|
||||
globals.processUtils,
|
||||
_workingDirectory,
|
||||
);
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return _repositoryUrl;
|
||||
}
|
||||
|
||||
/// The channel is the current branch if we recognize it, or "[user-branch]" (kUserBranch).
|
||||
/// `master`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, `dev`, ...
|
||||
String get channel {
|
||||
final String channel = getBranchName(redactUnknownBranches: true);
|
||||
assert(kOfficialChannels.contains(channel) || kObsoleteBranches.containsKey(channel) || channel == kUserBranch, 'Potential PII leak in channel name: "$channel"');
|
||||
return channel;
|
||||
}
|
||||
// if we are fetching tags, ignore cached versionFile
|
||||
if (fetchTags && versionFile.existsSync()) {
|
||||
versionFile.deleteSync();
|
||||
final File legacyVersionFile = fs.file(fs.path.join(flutterRoot, 'version'));
|
||||
if (legacyVersionFile.existsSync()) {
|
||||
legacyVersionFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
late GitTagVersion _gitTagVersion;
|
||||
GitTagVersion get gitTagVersion => _gitTagVersion;
|
||||
|
||||
/// The name of the local branch.
|
||||
/// Use getBranchName() to read this.
|
||||
String? _branch;
|
||||
|
||||
late String _frameworkRevision;
|
||||
String get frameworkRevision => _frameworkRevision;
|
||||
String get frameworkRevisionShort => _shortGitRevision(frameworkRevision);
|
||||
|
||||
String? _frameworkAge;
|
||||
String get frameworkAge {
|
||||
return _frameworkAge ??= _runGit(
|
||||
gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '),
|
||||
final String frameworkRevision = _runGit(
|
||||
gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '),
|
||||
globals.processUtils,
|
||||
_workingDirectory,
|
||||
flutterRoot,
|
||||
);
|
||||
|
||||
return FlutterVersion.fromRevision(
|
||||
clock: clock,
|
||||
frameworkRevision: frameworkRevision,
|
||||
fs: fs,
|
||||
flutterRoot: flutterRoot,
|
||||
fetchTags: fetchTags,
|
||||
);
|
||||
}
|
||||
|
||||
late String _frameworkVersion;
|
||||
String get frameworkVersion => _frameworkVersion;
|
||||
FlutterVersion._({
|
||||
required SystemClock clock,
|
||||
required this.flutterRoot,
|
||||
required this.fs,
|
||||
}) : _clock = clock;
|
||||
|
||||
String get devToolsVersion => globals.cache.devToolsVersion;
|
||||
factory FlutterVersion.fromRevision({
|
||||
SystemClock clock = const SystemClock(),
|
||||
required String flutterRoot,
|
||||
required String frameworkRevision,
|
||||
required FileSystem fs,
|
||||
bool fetchTags = false,
|
||||
}) {
|
||||
final GitTagVersion gitTagVersion = GitTagVersion.determine(
|
||||
globals.processUtils,
|
||||
globals.platform,
|
||||
gitRef: frameworkRevision,
|
||||
workingDirectory: flutterRoot,
|
||||
fetchTags: fetchTags,
|
||||
);
|
||||
final String frameworkVersion = gitTagVersion.frameworkVersionFor(frameworkRevision);
|
||||
return _FlutterVersionGit._(
|
||||
clock: clock,
|
||||
flutterRoot: flutterRoot,
|
||||
frameworkRevision: frameworkRevision,
|
||||
frameworkVersion: frameworkVersion,
|
||||
gitTagVersion: gitTagVersion,
|
||||
fs: fs,
|
||||
);
|
||||
}
|
||||
|
||||
String get dartSdkVersion => globals.cache.dartSdkVersion;
|
||||
/// Ensure the latest git tags are fetched and recalculate [FlutterVersion].
|
||||
///
|
||||
/// This is only required when not on beta or stable and we need to calculate
|
||||
/// the current version relative to upstream tags.
|
||||
///
|
||||
/// This is a method and not a factory constructor so that test classes can
|
||||
/// override it.
|
||||
FlutterVersion fetchTagsAndGetVersion({
|
||||
SystemClock clock = const SystemClock(),
|
||||
}) {
|
||||
// We don't need to fetch tags on beta and stable to calculate the version,
|
||||
// we should already exactly be on a tag that was pushed when this release
|
||||
// was published.
|
||||
if (channel != 'master' && channel != 'main') {
|
||||
return this;
|
||||
}
|
||||
return FlutterVersion(
|
||||
clock: clock,
|
||||
flutterRoot: flutterRoot,
|
||||
fs: fs,
|
||||
fetchTags: true,
|
||||
);
|
||||
}
|
||||
|
||||
String get engineRevision => globals.cache.engineRevision;
|
||||
final FileSystem fs;
|
||||
|
||||
final SystemClock _clock;
|
||||
|
||||
String? get repositoryUrl;
|
||||
|
||||
GitTagVersion get gitTagVersion;
|
||||
|
||||
/// The channel is the upstream branch.
|
||||
///
|
||||
/// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ...
|
||||
String get channel;
|
||||
|
||||
String get frameworkRevision;
|
||||
String get frameworkRevisionShort => _shortGitRevision(frameworkRevision);
|
||||
|
||||
String get frameworkVersion;
|
||||
|
||||
String get devToolsVersion;
|
||||
|
||||
String get dartSdkVersion;
|
||||
|
||||
String get engineRevision;
|
||||
String get engineRevisionShort => _shortGitRevision(engineRevision);
|
||||
|
||||
void ensureVersionFile() {
|
||||
globals.fs.file(globals.fs.path.join(Cache.flutterRoot!, 'version')).writeAsStringSync(_frameworkVersion);
|
||||
// This is static as it is called from a constructor.
|
||||
static File getVersionFile(FileSystem fs, String flutterRoot) {
|
||||
return fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'flutter.version.json'));
|
||||
}
|
||||
|
||||
final String flutterRoot;
|
||||
|
||||
String? _frameworkAge;
|
||||
|
||||
// TODO(fujino): calculate this relative to frameworkCommitDate for
|
||||
// _FlutterVersionFromFile so we don't need a git call.
|
||||
String get frameworkAge {
|
||||
return _frameworkAge ??= _runGit(
|
||||
FlutterVersion.gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '),
|
||||
globals.processUtils,
|
||||
flutterRoot,
|
||||
);
|
||||
}
|
||||
|
||||
void ensureVersionFile();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String versionText = frameworkVersion == _unknownFrameworkVersion ? '' : ' $frameworkVersion';
|
||||
@ -202,47 +251,13 @@ class FlutterVersion {
|
||||
'engineRevision': engineRevision,
|
||||
'dartSdkVersion': dartSdkVersion,
|
||||
'devToolsVersion': devToolsVersion,
|
||||
'flutterVersion': frameworkVersion,
|
||||
};
|
||||
|
||||
String get frameworkDate => frameworkCommitDate;
|
||||
|
||||
/// A date String describing the last framework commit.
|
||||
///
|
||||
/// If a git command fails, this will return a placeholder date.
|
||||
String get frameworkCommitDate => _gitCommitDate(lenient: true);
|
||||
|
||||
// The date of the given commit hash as [gitRef]. If no hash is specified,
|
||||
// then it is the HEAD of the current local branch.
|
||||
//
|
||||
// If lenient is true, and the git command fails, a placeholder date is
|
||||
// returned. Otherwise, the VersionCheckError exception is propagated.
|
||||
static String _gitCommitDate({
|
||||
String gitRef = 'HEAD',
|
||||
bool lenient = false,
|
||||
}) {
|
||||
final List<String> args = gitLog(<String>[
|
||||
gitRef,
|
||||
'-n',
|
||||
'1',
|
||||
'--pretty=format:%ad',
|
||||
'--date=iso',
|
||||
]);
|
||||
try {
|
||||
// Don't plumb 'lenient' through directly so that we can print an error
|
||||
// if something goes wrong.
|
||||
return _runSync(args, lenient: false);
|
||||
} on VersionCheckError catch (e) {
|
||||
if (lenient) {
|
||||
final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
globals.printError('Failed to find the latest git commit date: $e\n'
|
||||
'Returning $dummyDate instead.');
|
||||
// Return something that DateTime.parse() can parse.
|
||||
return dummyDate.toString();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
String get frameworkCommitDate;
|
||||
|
||||
/// Checks if the currently installed version of Flutter is up-to-date, and
|
||||
/// warns the user if it isn't.
|
||||
@ -261,7 +276,7 @@ class FlutterVersion {
|
||||
DateTime localFrameworkCommitDate;
|
||||
try {
|
||||
// Don't perform the update check if fetching the latest local commit failed.
|
||||
localFrameworkCommitDate = DateTime.parse(_gitCommitDate());
|
||||
localFrameworkCommitDate = DateTime.parse(_gitCommitDate(workingDirectory: flutterRoot));
|
||||
} on VersionCheckError {
|
||||
return;
|
||||
}
|
||||
@ -278,6 +293,53 @@ class FlutterVersion {
|
||||
).run();
|
||||
}
|
||||
|
||||
/// Gets the release date of the latest available Flutter version.
|
||||
///
|
||||
/// This method sends a server request if it's been more than
|
||||
/// [checkAgeConsideredUpToDate] since the last version check.
|
||||
///
|
||||
/// Returns null if the cached version is out-of-date or missing, and we are
|
||||
/// unable to reach the server to get the latest version.
|
||||
Future<DateTime?> _getLatestAvailableFlutterDate() async {
|
||||
globals.cache.checkLockAcquired();
|
||||
final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(globals.cache, globals.logger);
|
||||
|
||||
final DateTime now = _clock.now();
|
||||
if (versionCheckStamp.lastTimeVersionWasChecked != null) {
|
||||
final Duration timeSinceLastCheck = now.difference(
|
||||
versionCheckStamp.lastTimeVersionWasChecked!,
|
||||
);
|
||||
|
||||
// Don't ping the server too often. Return cached value if it's fresh.
|
||||
if (timeSinceLastCheck < VersionFreshnessValidator.checkAgeConsideredUpToDate) {
|
||||
return versionCheckStamp.lastKnownRemoteVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache is empty or it's been a while since the last server ping. Ping the server.
|
||||
try {
|
||||
final DateTime remoteFrameworkCommitDate = DateTime.parse(
|
||||
await fetchRemoteFrameworkCommitDate(),
|
||||
);
|
||||
await versionCheckStamp.store(
|
||||
newTimeVersionWasChecked: now,
|
||||
newKnownRemoteVersion: remoteFrameworkCommitDate,
|
||||
);
|
||||
return remoteFrameworkCommitDate;
|
||||
} on VersionCheckError catch (error) {
|
||||
// This happens when any of the git commands fails, which can happen when
|
||||
// there's no Internet connectivity. Remote version check is best effort
|
||||
// only. We do not prevent the command from running when it fails.
|
||||
globals.printTrace('Failed to check Flutter version in the remote repository: $error');
|
||||
// Still update the timestamp to avoid us hitting the server on every single
|
||||
// command if for some reason we cannot connect (eg. we may be offline).
|
||||
await versionCheckStamp.store(
|
||||
newTimeVersionWasChecked: now,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// The date of the latest framework commit in the remote repository.
|
||||
///
|
||||
/// Throws [VersionCheckError] if a git command fails, for example, when the
|
||||
@ -286,7 +348,7 @@ class FlutterVersion {
|
||||
try {
|
||||
// Fetch upstream branch's commit and tags
|
||||
await _run(<String>['git', 'fetch', '--tags']);
|
||||
return _gitCommitDate(gitRef: kGitTrackingUpstream);
|
||||
return _gitCommitDate(gitRef: kGitTrackingUpstream, workingDirectory: Cache.flutterRoot);
|
||||
} on VersionCheckError catch (error) {
|
||||
globals.printError(error.message);
|
||||
rethrow;
|
||||
@ -301,13 +363,18 @@ class FlutterVersion {
|
||||
return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevisionShort';
|
||||
}
|
||||
|
||||
/// The name of the local branch.
|
||||
///
|
||||
/// Use getBranchName() to read this.
|
||||
String? _branch;
|
||||
|
||||
/// Return the branch name.
|
||||
///
|
||||
/// If [redactUnknownBranches] is true and the branch is unknown,
|
||||
/// the branch name will be returned as `'[user-branch]'` ([kUserBranch]).
|
||||
String getBranchName({ bool redactUnknownBranches = false }) {
|
||||
_branch ??= () {
|
||||
final String branch = _runGit('git symbolic-ref --short HEAD', globals.processUtils, _workingDirectory);
|
||||
final String branch = _runGit('git symbolic-ref --short HEAD', globals.processUtils, flutterRoot);
|
||||
return branch == 'HEAD' ? '' : branch;
|
||||
}();
|
||||
if (redactUnknownBranches || _branch!.isEmpty) {
|
||||
@ -342,51 +409,205 @@ class FlutterVersion {
|
||||
static List<String> gitLog(List<String> args) {
|
||||
return <String>['git', '-c', 'log.showSignature=false', 'log'] + args;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the release date of the latest available Flutter version.
|
||||
///
|
||||
/// This method sends a server request if it's been more than
|
||||
/// [checkAgeConsideredUpToDate] since the last version check.
|
||||
///
|
||||
/// Returns null if the cached version is out-of-date or missing, and we are
|
||||
/// unable to reach the server to get the latest version.
|
||||
Future<DateTime?> _getLatestAvailableFlutterDate() async {
|
||||
globals.cache.checkLockAcquired();
|
||||
final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(globals.cache, globals.logger);
|
||||
// The date of the given commit hash as [gitRef]. If no hash is specified,
|
||||
// then it is the HEAD of the current local branch.
|
||||
//
|
||||
// If lenient is true, and the git command fails, a placeholder date is
|
||||
// returned. Otherwise, the VersionCheckError exception is propagated.
|
||||
String _gitCommitDate({
|
||||
String gitRef = 'HEAD',
|
||||
bool lenient = false,
|
||||
required String? workingDirectory,
|
||||
}) {
|
||||
final List<String> args = FlutterVersion.gitLog(<String>[
|
||||
gitRef,
|
||||
'-n',
|
||||
'1',
|
||||
'--pretty=format:%ad',
|
||||
'--date=iso',
|
||||
]);
|
||||
try {
|
||||
// Don't plumb 'lenient' through directly so that we can print an error
|
||||
// if something goes wrong.
|
||||
return _runSync(
|
||||
args,
|
||||
lenient: false,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
} on VersionCheckError catch (e) {
|
||||
if (lenient) {
|
||||
final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
globals.printError('Failed to find the latest git commit date: $e\n'
|
||||
'Returning $dummyDate instead.');
|
||||
// Return something that DateTime.parse() can parse.
|
||||
return dummyDate.toString();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final DateTime now = _clock.now();
|
||||
if (versionCheckStamp.lastTimeVersionWasChecked != null) {
|
||||
final Duration timeSinceLastCheck = now.difference(
|
||||
versionCheckStamp.lastTimeVersionWasChecked!,
|
||||
class _FlutterVersionFromFile extends FlutterVersion {
|
||||
_FlutterVersionFromFile._({
|
||||
required super.clock,
|
||||
required this.frameworkVersion,
|
||||
required this.channel,
|
||||
required this.repositoryUrl,
|
||||
required this.frameworkRevision,
|
||||
required this.frameworkCommitDate,
|
||||
required this.engineRevision,
|
||||
required this.dartSdkVersion,
|
||||
required this.devToolsVersion,
|
||||
required this.gitTagVersion,
|
||||
required super.flutterRoot,
|
||||
required super.fs,
|
||||
}) : super._();
|
||||
|
||||
static _FlutterVersionFromFile? tryParseFromFile(
|
||||
File jsonFile, {
|
||||
required String flutterRoot,
|
||||
SystemClock clock = const SystemClock(),
|
||||
}) {
|
||||
try {
|
||||
final String jsonContents = jsonFile.readAsStringSync();
|
||||
final Map<String, Object?> manifest = jsonDecode(jsonContents) as Map<String, Object?>;
|
||||
|
||||
return _FlutterVersionFromFile._(
|
||||
clock: clock,
|
||||
frameworkVersion: manifest['frameworkVersion']! as String,
|
||||
channel: manifest['channel']! as String,
|
||||
repositoryUrl: manifest['repositoryUrl']! as String,
|
||||
frameworkRevision: manifest['frameworkRevision']! as String,
|
||||
frameworkCommitDate: manifest['frameworkCommitDate']! as String,
|
||||
engineRevision: manifest['engineRevision']! as String,
|
||||
dartSdkVersion: manifest['dartSdkVersion']! as String,
|
||||
devToolsVersion: manifest['devToolsVersion']! as String,
|
||||
gitTagVersion: GitTagVersion.parse(manifest['flutterVersion']! as String),
|
||||
flutterRoot: flutterRoot,
|
||||
fs: jsonFile.fileSystem,
|
||||
);
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (err) {
|
||||
globals.printTrace('Failed to parse ${jsonFile.path} with $err');
|
||||
try {
|
||||
jsonFile.deleteSync();
|
||||
} on FileSystemException {
|
||||
globals.printTrace('Failed to delete ${jsonFile.path}');
|
||||
}
|
||||
// Returning null means fallback to git implementation.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't ping the server too often. Return cached value if it's fresh.
|
||||
if (timeSinceLastCheck < VersionFreshnessValidator.checkAgeConsideredUpToDate) {
|
||||
return versionCheckStamp.lastKnownRemoteVersion;
|
||||
@override
|
||||
final GitTagVersion gitTagVersion;
|
||||
|
||||
@override
|
||||
final String frameworkVersion;
|
||||
|
||||
@override
|
||||
final String channel;
|
||||
|
||||
@override
|
||||
String getBranchName({bool redactUnknownBranches = false}) => channel;
|
||||
|
||||
@override
|
||||
final String repositoryUrl;
|
||||
|
||||
@override
|
||||
final String frameworkRevision;
|
||||
|
||||
@override
|
||||
final String frameworkCommitDate;
|
||||
|
||||
@override
|
||||
final String engineRevision;
|
||||
|
||||
@override
|
||||
final String dartSdkVersion;
|
||||
|
||||
@override
|
||||
final String devToolsVersion;
|
||||
|
||||
@override
|
||||
void ensureVersionFile() {}
|
||||
}
|
||||
|
||||
class _FlutterVersionGit extends FlutterVersion {
|
||||
_FlutterVersionGit._({
|
||||
required super.clock,
|
||||
required super.flutterRoot,
|
||||
required this.frameworkRevision,
|
||||
required this.frameworkVersion,
|
||||
required this.gitTagVersion,
|
||||
required super.fs,
|
||||
}) : super._();
|
||||
|
||||
@override
|
||||
final GitTagVersion gitTagVersion;
|
||||
|
||||
@override
|
||||
final String frameworkRevision;
|
||||
|
||||
@override
|
||||
String get frameworkCommitDate => _gitCommitDate(lenient: true, workingDirectory: flutterRoot);
|
||||
|
||||
String? _repositoryUrl;
|
||||
@override
|
||||
String? get repositoryUrl {
|
||||
if (_repositoryUrl == null) {
|
||||
final String gitChannel = _runGit(
|
||||
'git rev-parse --abbrev-ref --symbolic $kGitTrackingUpstream',
|
||||
globals.processUtils,
|
||||
flutterRoot,
|
||||
);
|
||||
final int slash = gitChannel.indexOf('/');
|
||||
if (slash != -1) {
|
||||
final String remote = gitChannel.substring(0, slash);
|
||||
_repositoryUrl = _runGit(
|
||||
'git ls-remote --get-url $remote',
|
||||
globals.processUtils,
|
||||
flutterRoot,
|
||||
);
|
||||
}
|
||||
}
|
||||
return _repositoryUrl;
|
||||
}
|
||||
|
||||
// Cache is empty or it's been a while since the last server ping. Ping the server.
|
||||
try {
|
||||
final DateTime remoteFrameworkCommitDate = DateTime.parse(
|
||||
await FlutterVersion.fetchRemoteFrameworkCommitDate(),
|
||||
);
|
||||
await versionCheckStamp.store(
|
||||
newTimeVersionWasChecked: now,
|
||||
newKnownRemoteVersion: remoteFrameworkCommitDate,
|
||||
);
|
||||
return remoteFrameworkCommitDate;
|
||||
} on VersionCheckError catch (error) {
|
||||
// This happens when any of the git commands fails, which can happen when
|
||||
// there's no Internet connectivity. Remote version check is best effort
|
||||
// only. We do not prevent the command from running when it fails.
|
||||
globals.printTrace('Failed to check Flutter version in the remote repository: $error');
|
||||
// Still update the timestamp to avoid us hitting the server on every single
|
||||
// command if for some reason we cannot connect (eg. we may be offline).
|
||||
await versionCheckStamp.store(
|
||||
newTimeVersionWasChecked: now,
|
||||
);
|
||||
return null;
|
||||
@override
|
||||
String get devToolsVersion => globals.cache.devToolsVersion;
|
||||
|
||||
@override
|
||||
String get dartSdkVersion => globals.cache.dartSdkVersion;
|
||||
|
||||
@override
|
||||
String get engineRevision => globals.cache.engineRevision;
|
||||
|
||||
@override
|
||||
final String frameworkVersion;
|
||||
|
||||
/// The channel is the current branch if we recognize it, or "[user-branch]" (kUserBranch).
|
||||
/// `master`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, `dev`, ...
|
||||
@override
|
||||
String get channel {
|
||||
final String channel = getBranchName(redactUnknownBranches: true);
|
||||
assert(kOfficialChannels.contains(channel) || kObsoleteBranches.containsKey(channel) || channel == kUserBranch, 'Potential PII leak in channel name: "$channel"');
|
||||
return channel;
|
||||
}
|
||||
|
||||
@override
|
||||
void ensureVersionFile() {
|
||||
final File legacyVersionFile = fs.file(fs.path.join(flutterRoot, 'version'));
|
||||
if (!legacyVersionFile.existsSync()) {
|
||||
legacyVersionFile.writeAsStringSync(frameworkVersion);
|
||||
}
|
||||
|
||||
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
|
||||
final File newVersionFile = FlutterVersion.getVersionFile(fs, flutterRoot);
|
||||
if (!newVersionFile.existsSync()) {
|
||||
newVersionFile.writeAsStringSync(encoder.convert(toJson()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -606,10 +827,14 @@ class VersionCheckError implements Exception {
|
||||
///
|
||||
/// If [lenient] is true and the command fails, returns an empty string.
|
||||
/// Otherwise, throws a [ToolExit] exception.
|
||||
String _runSync(List<String> command, { bool lenient = true }) {
|
||||
String _runSync(
|
||||
List<String> command, {
|
||||
bool lenient = true,
|
||||
required String? workingDirectory,
|
||||
}) {
|
||||
final ProcessResult results = globals.processManager.runSync(
|
||||
command,
|
||||
workingDirectory: Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
if (results.exitCode == 0) {
|
||||
@ -630,7 +855,7 @@ String _runSync(List<String> command, { bool lenient = true }) {
|
||||
String _runGit(String command, ProcessUtils processUtils, String? workingDirectory) {
|
||||
return processUtils.runSync(
|
||||
command.split(' '),
|
||||
workingDirectory: workingDirectory ?? Cache.flutterRoot,
|
||||
workingDirectory: workingDirectory,
|
||||
).stdout.trim();
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,10 @@ import 'base/context.dart';
|
||||
import 'base/io.dart' as io;
|
||||
import 'base/logger.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'cache.dart';
|
||||
import 'convert.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'ios/xcodeproj.dart';
|
||||
import 'project.dart';
|
||||
import 'version.dart';
|
||||
@ -244,7 +246,10 @@ Future<vm_service.VmService> setUpVmService({
|
||||
}
|
||||
|
||||
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();
|
||||
versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
|
||||
versionJson['engineRevisionShort'] = version.engineRevisionShort;
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:fake_async/fake_async.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_studio_validator.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/build_info.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/device.dart';
|
||||
import 'package:flutter_tools/src/doctor.dart';
|
||||
@ -32,17 +30,16 @@ import 'package:test/fake.dart';
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fakes.dart';
|
||||
import '../../src/test_flutter_command_runner.dart';
|
||||
|
||||
void main() {
|
||||
late FakeFlutterVersion flutterVersion;
|
||||
late BufferLogger logger;
|
||||
late FakeProcessManager fakeProcessManager;
|
||||
late MemoryFileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
flutterVersion = FakeFlutterVersion();
|
||||
logger = BufferLogger.test();
|
||||
fakeProcessManager = FakeProcessManager.empty();
|
||||
fs = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
testWithoutContext('ValidationMessage equality and hashCode includes contextUrl', () {
|
||||
@ -761,27 +758,55 @@ void main() {
|
||||
contains(isA<CustomDeviceWorkflow>()),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => fakeProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('Fetches tags to get the right version', () async {
|
||||
Cache.disableLocking();
|
||||
group('FlutterValidator', () {
|
||||
late FakeFlutterVersion initialVersion;
|
||||
late FakeFlutterVersion secondVersion;
|
||||
late TestFeatureFlags featureFlags;
|
||||
|
||||
final DoctorCommand doctorCommand = DoctorCommand();
|
||||
final CommandRunner<void> commandRunner = createTestCommandRunner(doctorCommand);
|
||||
|
||||
await commandRunner.run(<String>['doctor']);
|
||||
|
||||
expect(flutterVersion.didFetchTagsAndUpdate, true);
|
||||
Cache.enableLocking();
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
FlutterVersion: () => flutterVersion,
|
||||
Doctor: () => NoOpDoctor(),
|
||||
}, initializeFlutterRoot: false);
|
||||
setUp(() {
|
||||
secondVersion = FakeFlutterVersion(frameworkRevisionShort: '222');
|
||||
initialVersion = FakeFlutterVersion(
|
||||
frameworkRevisionShort: '111',
|
||||
nextFlutterVersion: secondVersion,
|
||||
);
|
||||
featureFlags = TestFeatureFlags();
|
||||
});
|
||||
|
||||
testUsingContext('FlutterValidator fetches tags and gets fresh version', () async {
|
||||
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>{
|
||||
Cache: () => Cache.test(
|
||||
rootOverride: fs.directory('/path/to/flutter'),
|
||||
fileSystem: fs,
|
||||
processManager: fakeProcessManager,
|
||||
),
|
||||
FileSystem: () => fs,
|
||||
FlutterVersion: () => initialVersion,
|
||||
Platform: () => FakePlatform(),
|
||||
ProcessManager: () => fakeProcessManager,
|
||||
TestFeatureFlags: () => featureFlags,
|
||||
});
|
||||
});
|
||||
testUsingContext('If android workflow is disabled, AndroidStudio validator is not included', () {
|
||||
final DoctorValidatorsProvider provider = DoctorValidatorsProvider.test(
|
||||
featureFlags: TestFeatureFlags(isAndroidEnabled: false),
|
||||
@ -826,6 +851,7 @@ class NoOpDoctor implements Doctor {
|
||||
bool showPii = true,
|
||||
List<ValidatorTask>? startedValidatorTasks,
|
||||
bool sendEvent = true,
|
||||
FlutterVersion? version,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
|
@ -37,7 +37,8 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
fakeCommandRunner = FakeUpgradeCommandRunner();
|
||||
realCommandRunner = UpgradeCommandRunner();
|
||||
realCommandRunner = UpgradeCommandRunner()
|
||||
..workingDirectory = Cache.flutterRoot;
|
||||
processManager = FakeProcessManager.empty();
|
||||
fakeCommandRunner.willHaveUncommittedChanges = false;
|
||||
fakePlatform = FakePlatform()..environment = Map<String, String>.unmodifiable(<String, String>{
|
||||
|
@ -41,11 +41,14 @@ void main() {
|
||||
group('analytics', () {
|
||||
late Directory tempDir;
|
||||
late Config testConfig;
|
||||
late FileSystem fs;
|
||||
const String flutterRoot = '/path/to/flutter';
|
||||
|
||||
setUp(() {
|
||||
Cache.flutterRoot = '../..';
|
||||
Cache.flutterRoot = flutterRoot;
|
||||
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_analytics_test.');
|
||||
testConfig = Config.test();
|
||||
fs = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@ -77,7 +80,7 @@ void main() {
|
||||
|
||||
expect(count, 0);
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(),
|
||||
FlutterVersion: () => FakeFlutterVersion(),
|
||||
Usage: () => Usage(
|
||||
configDirOverride: tempDir.path,
|
||||
logFile: tempDir.childFile('analytics.log').path,
|
||||
@ -101,7 +104,7 @@ void main() {
|
||||
|
||||
expect(count, 0);
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(),
|
||||
FlutterVersion: () => FakeFlutterVersion(),
|
||||
Usage: () => Usage(
|
||||
configDirOverride: tempDir.path,
|
||||
logFile: tempDir.childFile('analytics.log').path,
|
||||
@ -118,12 +121,12 @@ void main() {
|
||||
|
||||
expect(globals.fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web'));
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(),
|
||||
FlutterVersion: () => FakeFlutterVersion(),
|
||||
Config: () => testConfig,
|
||||
Platform: () => FakePlatform(environment: <String, String>{
|
||||
'FLUTTER_ANALYTICS_LOG_FILE': 'test',
|
||||
}),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
@ -141,12 +144,12 @@ void main() {
|
||||
contains('$featuresKey: enable-web,enable-linux-desktop,enable-macos-desktop'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(),
|
||||
FlutterVersion: () => FakeFlutterVersion(),
|
||||
Config: () => testConfig,
|
||||
Platform: () => FakePlatform(environment: <String, String>{
|
||||
'FLUTTER_ANALYTICS_LOG_FILE': 'test',
|
||||
}),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
});
|
||||
@ -384,6 +387,7 @@ class FakeDoctor extends Fake implements Doctor {
|
||||
bool showPii = true,
|
||||
List<ValidatorTask>? startedValidatorTasks,
|
||||
bool sendEvent = true,
|
||||
FlutterVersion? version,
|
||||
}) async {
|
||||
return diagnoseSucceeds;
|
||||
}
|
||||
|
@ -4,12 +4,13 @@
|
||||
|
||||
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/platform.dart';
|
||||
import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/base/time.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:test/fake.dart';
|
||||
|
||||
@ -18,7 +19,7 @@ import '../src/context.dart';
|
||||
import '../src/fake_process_manager.dart';
|
||||
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 _stampOutOfDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate * 2);
|
||||
|
||||
@ -49,7 +50,11 @@ void main() {
|
||||
}
|
||||
|
||||
group('$FlutterVersion for $channel', () {
|
||||
late FileSystem fs;
|
||||
const String flutterRoot = '/path/to/flutter';
|
||||
|
||||
setUpAll(() {
|
||||
fs = MemoryFileSystem.test();
|
||||
Cache.disableLocking();
|
||||
VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero;
|
||||
});
|
||||
@ -101,7 +106,7 @@ void main() {
|
||||
),
|
||||
]);
|
||||
|
||||
final FlutterVersion flutterVersion = globals.flutterVersion;
|
||||
final FlutterVersion flutterVersion = FlutterVersion(clock: _testClock, fs: fs, flutterRoot: flutterRoot);
|
||||
await flutterVersion.checkFlutterVersionFreshness();
|
||||
expect(flutterVersion.channel, channel);
|
||||
expect(flutterVersion.repositoryUrl, flutterUpstreamUrl);
|
||||
@ -124,7 +129,6 @@ void main() {
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(clock: _testClock),
|
||||
ProcessManager: () => processManager,
|
||||
Cache: () => cache,
|
||||
});
|
||||
@ -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.getVersionString(), 'feature-branch/1234abcd');
|
||||
expect(flutterVersion.getBranchName(), 'feature-branch');
|
||||
expect(flutterVersion.getVersionString(redactUnknownBranches: true), '[user-branch]/1234abcd');
|
||||
expect(flutterVersion.getBranchName(redactUnknownBranches: true), '[user-branch]');
|
||||
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
}, overrides: <Type, Generator>{
|
||||
FlutterVersion: () => FlutterVersion(clock: _testClock),
|
||||
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(versionFile.existsSync(), isTrue);
|
||||
expect(legacyVersionFile.existsSync(), isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
Cache: () => cache,
|
||||
});
|
||||
|
@ -158,7 +158,6 @@ void main() {
|
||||
expect(decoded['FlutterProject.isModule'], false);
|
||||
expect(decoded['FlutterProject.isPlugin'], false);
|
||||
expect(decoded['FlutterProject.manifest.appname'], 'test_project');
|
||||
expect(decoded['FlutterVersion.frameworkRevision'], '');
|
||||
|
||||
expect(decoded['Platform.isAndroid'], false);
|
||||
expect(decoded['Platform.isIOS'], false);
|
||||
|
@ -43,8 +43,6 @@ void main() {
|
||||
final Directory testDirectory = parentDirectory.childDirectory('flutter');
|
||||
testDirectory.createSync(recursive: true);
|
||||
|
||||
int exitCode = 0;
|
||||
|
||||
// Enable longpaths for windows integration test.
|
||||
await processManager.run(<String>[
|
||||
'git', 'config', '--system', 'core.longpaths', 'true',
|
||||
|
@ -24,14 +24,18 @@ void main() {
|
||||
);
|
||||
exampleAppDir = tempDir.childDirectory('bbb').childDirectory('example');
|
||||
|
||||
processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--template=plugin',
|
||||
'--platforms=android',
|
||||
'bbb',
|
||||
], workingDirectory: tempDir.path);
|
||||
processManager.runSync(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--template=plugin',
|
||||
'--platforms=android',
|
||||
'bbb',
|
||||
'-v',
|
||||
],
|
||||
workingDirectory: tempDir.path,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@ -48,14 +52,17 @@ void main() {
|
||||
// Ensure file is gone prior to configOnly running.
|
||||
await gradleFile.delete();
|
||||
|
||||
final ProcessResult result = processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'build',
|
||||
'apk',
|
||||
'--target-platform=android-arm',
|
||||
'--config-only',
|
||||
], workingDirectory: exampleAppDir.path);
|
||||
final ProcessResult result = processManager.runSync(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'build',
|
||||
'apk',
|
||||
'--target-platform=android-arm',
|
||||
'--config-only',
|
||||
],
|
||||
workingDirectory: exampleAppDir.path,
|
||||
);
|
||||
|
||||
expect(gradleFile, exists);
|
||||
expect(result.stdout, contains(RegExp(r'Config complete')));
|
||||
|
@ -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/logger.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/cache.dart';
|
||||
import 'package:flutter_tools/src/convert.dart';
|
||||
@ -337,6 +338,8 @@ class FakeFlutterVersion implements FlutterVersion {
|
||||
this.frameworkAge = '0 hours ago',
|
||||
this.frameworkCommitDate = '12/01/01',
|
||||
this.gitTagVersion = const GitTagVersion.unknown(),
|
||||
this.flutterRoot = '/path/to/flutter',
|
||||
this.nextFlutterVersion,
|
||||
});
|
||||
|
||||
final String branch;
|
||||
@ -344,6 +347,17 @@ class FakeFlutterVersion implements FlutterVersion {
|
||||
bool get didFetchTagsAndUpdate => _didFetchTagsAndUpdate;
|
||||
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 _didCheckFlutterVersionFreshness = false;
|
||||
|
||||
@ -355,6 +369,9 @@ class FakeFlutterVersion implements FlutterVersion {
|
||||
return kUserBranch;
|
||||
}
|
||||
|
||||
@override
|
||||
final String flutterRoot;
|
||||
|
||||
@override
|
||||
final String devToolsVersion;
|
||||
|
||||
@ -385,16 +402,11 @@ class FakeFlutterVersion implements FlutterVersion {
|
||||
@override
|
||||
final String frameworkCommitDate;
|
||||
|
||||
@override
|
||||
String get frameworkDate => frameworkCommitDate;
|
||||
|
||||
@override
|
||||
final GitTagVersion gitTagVersion;
|
||||
|
||||
@override
|
||||
void fetchTagsAndUpdate() {
|
||||
_didFetchTagsAndUpdate = true;
|
||||
}
|
||||
FileSystem get fs => throw UnimplementedError('FakeFlutterVersion.fs is not implemented');
|
||||
|
||||
@override
|
||||
Future<void> checkFlutterVersionFreshness() async {
|
||||
|
Loading…
Reference in New Issue
Block a user