Add FlutterVersion.engineCommitDate, helps signal engine artifact SHA issues (#163652)

Closes #163644.

Before this change:
```sh
flutter-dev --version                                                                         
Flutter 3.30.0-1.0.pre.215 • channel [user-branch] • https://github.com/matanlurey/flutter
Framework • revision cead517e4e (79 seconds ago) • 2025-02-19 13:00:11 -0800
Engine • revision 39b4951f8f
Tools • Dart 3.8.0 (build 3.8.0-92.0.dev) • DevTools 2.43.0
```

After this change:
```sh
flutter-dev --version                                                                         
Flutter 3.30.0-1.0.pre.215 • channel [user-branch] • https://github.com/matanlurey/flutter
Framework • revision cead517e4e (79 seconds ago) • 2025-02-19 13:00:11 -0800
Engine • revision 39b4951f8f (79 seconds ago) • 2025-02-18 13:42:53 -0800
Tools • Dart 3.8.0 (build 3.8.0-92.0.dev) • DevTools 2.43.0
```

/cc @jtmcdole as this could be helpful for sleuthing artifact mismatch.
This commit is contained in:
Matan Lurey 2025-02-20 08:31:29 -08:00 committed by GitHub
parent 281612e648
commit 7df3f8fc06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 188 additions and 13 deletions

View File

@ -168,16 +168,26 @@ abstract class FlutterVersion {
/// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ... /// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ...
String get channel; String get channel;
/// The SHA describing the commit being used for the SDK and tools provide in `flutter/flutter`.
///
/// The _exception_ is the _engine artifacts_, which are downloaded separately as [engineRevision].
String get frameworkRevision; String get frameworkRevision;
String get frameworkRevisionShort => _shortGitRevision(frameworkRevision);
/// The shorter Git commit SHA of [frameworkRevion].
String get frameworkRevisionShort => _shortGitRevision(frameworkRevision);
String get frameworkVersion; String get frameworkVersion;
String get devToolsVersion; String get devToolsVersion;
String get dartSdkVersion; String get dartSdkVersion;
/// The SHA describing the commit being used for the engine artifacts, which are compiled from the `engine/` sub-directory.
///
/// When using a standard release build, or master channel, [engineRevision] will be identical to [frameworkRevision] since
/// the monorepository merge (as of 2025); however when modifying the framework (or engine) locally, or using a flag such
/// as `FLUTTER_PREBUILT_ENGINE_VERSION=...`, the engine SHA will be _different_ than the [frameworkRevision].
String get engineRevision; String get engineRevision;
/// The shorter Git commit SHA of [engineRevision].
String get engineRevisionShort => _shortGitRevision(engineRevision); String get engineRevisionShort => _shortGitRevision(engineRevision);
// This is static as it is called from a constructor. // This is static as it is called from a constructor.
@ -187,18 +197,24 @@ abstract class FlutterVersion {
final String flutterRoot; final String flutterRoot;
String? _frameworkAge; String _getTimeSinceCommit({String? revision}) {
return _runGit(
// TODO(fujino): calculate this relative to frameworkCommitDate for FlutterVersion.gitLog(<String>[
// _FlutterVersionFromFile so we don't need a git call. '-n',
String get frameworkAge { '1',
return _frameworkAge ??= _runGit( '--pretty=format:%ar',
FlutterVersion.gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '), if (revision != null) revision,
]).join(' '),
globals.processUtils, globals.processUtils,
flutterRoot, flutterRoot,
); );
} }
// TODO(fujino): calculate this relative to frameworkCommitDate for
// _FlutterVersionFromFile so we don't need a git call.
late final String frameworkAge = _getTimeSinceCommit();
late final String engineAge = _getTimeSinceCommit(revision: engineRevision);
void ensureVersionFile(); void ensureVersionFile();
@override @override
@ -209,12 +225,16 @@ abstract class FlutterVersion {
'Flutter$versionText • channel $channel${repositoryUrl ?? 'unknown source'}'; 'Flutter$versionText • channel $channel${repositoryUrl ?? 'unknown source'}';
final String frameworkText = final String frameworkText =
'Framework • revision $frameworkRevisionShort ($frameworkAge) • $frameworkCommitDate'; 'Framework • revision $frameworkRevisionShort ($frameworkAge) • $frameworkCommitDate';
final String engineText = 'Engine • revision $engineRevisionShort'; String engineText = 'Engine • revision $engineRevisionShort ($engineAge)';
if (engineCommitDate != null) {
engineText = '$engineText$engineCommitDate';
}
final String toolsText = 'Tools • Dart $dartSdkVersion • DevTools $devToolsVersion'; final String toolsText = 'Tools • Dart $dartSdkVersion • DevTools $devToolsVersion';
// Flutter 1.10.2-pre.69 channel master https://github.com/flutter/flutter.git // Flutter 1.10.2-pre.69 channel master https://github.com/flutter/flutter.git
// Framework revision 340c158f32 (85 minutes ago) 2018-10-26 11:27:22 -0400 // Framework revision 340c158f32 (85 minutes ago) 2018-10-26 11:27:22 -0400
// Engine revision 9c46333e14 // Engine revision 9c46333e14 (96 minutes ago) 2018-10-26 11:16:22 -0400
// Tools Dart 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1) // Tools Dart 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1)
return '$flutterText\n$frameworkText\n$engineText\n$toolsText'; return '$flutterText\n$frameworkText\n$engineText\n$toolsText';
@ -227,16 +247,25 @@ abstract class FlutterVersion {
'frameworkRevision': frameworkRevision, 'frameworkRevision': frameworkRevision,
'frameworkCommitDate': frameworkCommitDate, 'frameworkCommitDate': frameworkCommitDate,
'engineRevision': engineRevision, 'engineRevision': engineRevision,
if (engineCommitDate != null) 'engineCommitDate': engineCommitDate!,
'dartSdkVersion': dartSdkVersion, 'dartSdkVersion': dartSdkVersion,
'devToolsVersion': devToolsVersion, 'devToolsVersion': devToolsVersion,
'flutterVersion': frameworkVersion, 'flutterVersion': frameworkVersion,
}; };
/// A date String describing the last framework commit. /// A date String describing the [frameworkRevision] commit.
/// ///
/// If a git command fails, this will return a placeholder date. /// If a git command fails, this will return a placeholder date.
String get frameworkCommitDate; String get frameworkCommitDate;
/// A date String describing the [engineRevision] commit.
///
/// If a git command fails, this will return a placeholder date.
///
/// If no date was recorded ([engineCommitDate] is a newly stored field),
/// the date is omitted, and left `null`.
String? get engineCommitDate;
/// Checks if the currently installed version of Flutter is up-to-date, and /// Checks if the currently installed version of Flutter is up-to-date, and
/// warns the user if it isn't. /// warns the user if it isn't.
/// ///
@ -439,6 +468,7 @@ class _FlutterVersionFromFile extends FlutterVersion {
required this.frameworkRevision, required this.frameworkRevision,
required this.frameworkCommitDate, required this.frameworkCommitDate,
required this.engineRevision, required this.engineRevision,
required this.engineCommitDate,
required this.dartSdkVersion, required this.dartSdkVersion,
required this.devToolsVersion, required this.devToolsVersion,
required this.gitTagVersion, required this.gitTagVersion,
@ -463,6 +493,7 @@ class _FlutterVersionFromFile extends FlutterVersion {
frameworkRevision: manifest['frameworkRevision']! as String, frameworkRevision: manifest['frameworkRevision']! as String,
frameworkCommitDate: manifest['frameworkCommitDate']! as String, frameworkCommitDate: manifest['frameworkCommitDate']! as String,
engineRevision: manifest['engineRevision']! as String, engineRevision: manifest['engineRevision']! as String,
engineCommitDate: manifest['engineCommitDate'] as String?,
dartSdkVersion: manifest['dartSdkVersion']! as String, dartSdkVersion: manifest['dartSdkVersion']! as String,
devToolsVersion: manifest['devToolsVersion']! as String, devToolsVersion: manifest['devToolsVersion']! as String,
gitTagVersion: GitTagVersion.parse(manifest['flutterVersion']! as String), gitTagVersion: GitTagVersion.parse(manifest['flutterVersion']! as String),
@ -503,6 +534,9 @@ class _FlutterVersionFromFile extends FlutterVersion {
@override @override
final String frameworkCommitDate; final String frameworkCommitDate;
@override
final String? engineCommitDate;
@override @override
final String engineRevision; final String engineRevision;
@ -537,6 +571,16 @@ class _FlutterVersionGit extends FlutterVersion {
@override @override
String get frameworkCommitDate => _gitCommitDate(lenient: true, workingDirectory: flutterRoot); String get frameworkCommitDate => _gitCommitDate(lenient: true, workingDirectory: flutterRoot);
// This uses 'late final' instead of 'String get' because unlike frameworkCommitDate, it is
// operating based on a 'gitRef: ...', which we can assume to be immutable in the context of
// this invocation (possibly HEAD could change, but gitRef should not).
@override
late final String engineCommitDate = _gitCommitDate(
gitRef: engineRevision,
lenient: true,
workingDirectory: flutterRoot,
);
String? _repositoryUrl; String? _repositoryUrl;
@override @override
String? get repositoryUrl { String? get repositoryUrl {

View File

@ -168,6 +168,33 @@ void main() {
], ],
stdout: getChannelUpToDateVersion().toString(), stdout: getChannelUpToDateVersion().toString(),
), ),
const FakeCommand(
command: <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'-n',
'1',
'--pretty=format:%ar',
'abcdefg',
],
stdout: '2 seconds ago',
),
FakeCommand(
command: const <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'abcdefg',
'-n',
'1',
'--pretty=format:%ad',
'--date=iso',
],
stdout: getChannelUpToDateVersion().toString(),
),
]); ]);
final FlutterVersion flutterVersion = FlutterVersion( final FlutterVersion flutterVersion = FlutterVersion(
@ -185,7 +212,7 @@ void main() {
flutterVersion.toString(), flutterVersion.toString(),
'Flutter • channel $channel$flutterUpstreamUrl\n' 'Flutter • channel $channel$flutterUpstreamUrl\n'
'Framework • revision 1234abcd (1 second ago) • ${getChannelUpToDateVersion()}\n' 'Framework • revision 1234abcd (1 second ago) • ${getChannelUpToDateVersion()}\n'
'Engine • revision abcdefg\n' 'Engine • revision abcdefg (2 seconds ago) • ${getChannelUpToDateVersion()}\n'
'Tools • Dart 2.12.0 • DevTools 2.8.0', 'Tools • Dart 2.12.0 • DevTools 2.8.0',
); );
expect(flutterVersion.frameworkAge, '1 second ago'); expect(flutterVersion.frameworkAge, '1 second ago');
@ -689,6 +716,23 @@ void main() {
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2) .ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
.toString(), .toString(),
), ),
FakeCommand(
command: const <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'abcdefg',
'-n',
'1',
'--pretty=format:%ad',
'--date=iso',
],
stdout:
_testClock
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
.toString(),
),
]); ]);
final MemoryFileSystem fs = MemoryFileSystem.test(); final MemoryFileSystem fs = MemoryFileSystem.test();
@ -713,6 +757,7 @@ void main() {
"frameworkRevision": "1234abcd", "frameworkRevision": "1234abcd",
"frameworkCommitDate": "2014-10-02 00:00:00.000Z", "frameworkCommitDate": "2014-10-02 00:00:00.000Z",
"engineRevision": "abcdefg", "engineRevision": "abcdefg",
"engineCommitDate": "2014-10-02 00:00:00.000Z",
"dartSdkVersion": "2.12.0", "dartSdkVersion": "2.12.0",
"devToolsVersion": "2.8.0", "devToolsVersion": "2.8.0",
"flutterVersion": "0.0.0-unknown" "flutterVersion": "0.0.0-unknown"
@ -793,6 +838,67 @@ void main() {
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache}, overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
); );
testUsingContext(
'_FlutterVersionFromFile ignores engineCommitDate if historically omitted',
() 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 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': '0000000',
'flutterVersion': 'foo',
};
cacheDir.childFile('flutter.version.json').writeAsStringSync(jsonEncode(versionJson));
processManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'-n',
'1',
'--pretty=format:%ar',
],
stdout: '1 second ago',
),
const FakeCommand(
command: <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'-n',
'1',
'--pretty=format:%ar',
'deadbeef',
],
stdout: '1 second ago',
),
]);
final FlutterVersion flutterVersion = FlutterVersion(
clock: _testClock,
fs: fs,
flutterRoot: flutterRoot.path,
);
expect(flutterVersion.engineCommitDate, isNull);
expect(flutterVersion.toJson(), isNot(contains('engineCommitDate')));
expect(flutterVersion.toString(), contains('Engine • revision deadbeef (1 second ago)\n'));
},
overrides: <Type, Generator>{ProcessManager: () => processManager, Cache: () => cache},
);
testUsingContext( testUsingContext(
'FlutterVersion() falls back to git if .version.json is malformed', 'FlutterVersion() falls back to git if .version.json is malformed',
() async { () async {
@ -846,6 +952,23 @@ void main() {
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2) .ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
.toString(), .toString(),
), ),
FakeCommand(
command: const <String>[
'git',
'-c',
'log.showSignature=false',
'log',
'abcdefg',
'-n',
'1',
'--pretty=format:%ad',
'--date=iso',
],
stdout:
_testClock
.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2)
.toString(),
),
]); ]);
// version file exists in a malformed state // version file exists in a malformed state

View File

@ -366,6 +366,8 @@ class FakeFlutterVersion implements FlutterVersion {
this.devToolsVersion = '2.8.0', this.devToolsVersion = '2.8.0',
this.engineRevision = 'abcdefghijklmnopqrstuvwxyz', this.engineRevision = 'abcdefghijklmnopqrstuvwxyz',
this.engineRevisionShort = 'abcde', this.engineRevisionShort = 'abcde',
this.engineAge = '0 hours ago',
this.engineCommitDate = '12/01/01',
this.repositoryUrl = 'https://github.com/flutter/flutter.git', this.repositoryUrl = 'https://github.com/flutter/flutter.git',
this.frameworkVersion = '0.0.0', this.frameworkVersion = '0.0.0',
this.frameworkRevision = '11111111111111111111', this.frameworkRevision = '11111111111111111111',
@ -417,6 +419,12 @@ class FakeFlutterVersion implements FlutterVersion {
@override @override
final String engineRevisionShort; final String engineRevisionShort;
@override
final String? engineCommitDate;
@override
final String engineAge;
@override @override
final String? repositoryUrl; final String? repositoryUrl;