Output Dart SDK version in published metadata (#96302)

diff --git a/dev/bots/prepare_package.dart b/dev/bots/prepare_package.dart
index 433b7be..01a5625 100644
--- a/dev/bots/prepare_package.dart
+++ b/dev/bots/prepare_package.dart
@@ -24,6 +24,8 @@
 const String gsReleaseFolder = '$gsBase$releaseFolder';
 const String baseUrl = 'https://storage.googleapis.com/flutter_infra_release';
 const int shortCacheSeconds = 60;
+const String frameworkVersionTag = 'frameworkVersionFromGit';
+const String dartVersionTag = 'dartSdkVersion';
 
 /// Exception class for when a process fails to run, so we can catch
 /// it and provide something more readable than a stack trace.
@@ -251,7 +253,7 @@
   final HttpReader httpReader;
 
   late File _outputFile;
-  late String _version;
+  final Map<String, String> _version = <String, String>{};
   late String _flutter;
 
   /// Get the name of the channel as a string.
@@ -269,21 +271,24 @@
     // unpacking it!) So, we use .zip for Mac, and the files are about
     // 220MB larger than they need to be. :-(
     final String suffix = platform.isLinux ? 'tar.xz' : 'zip';
-    return 'flutter_${os}_$_version-$branchName.$suffix';
+    return 'flutter_${os}_${_version[frameworkVersionTag]}-$branchName.$suffix';
   }
 
   /// Checks out the flutter repo and prepares it for other operations.
   ///
-  /// Returns the version for this release, as obtained from the git tags.
-  Future<String> initializeRepo() async {
+  /// Returns the version for this release as obtained from the git tags, and
+  /// the dart version as obtained from `flutter --version`.
+  Future<Map<String, String>> initializeRepo() async {
     await _checkoutFlutter();
-    _version = await _getVersion();
+    if (_version.isEmpty) {
+      _version.addAll(await _getVersion());
+    }
     return _version;
   }
 
   /// Performs all of the steps needed to create an archive.
   Future<File> createArchive() async {
-    assert(_version != null, 'Must run initializeRepo before createArchive');
+    assert(_version.isNotEmpty, 'Must run initializeRepo before createArchive');
     _outputFile = File(path.join(outputDir.absolute.path, _archiveName));
     await _installMinGitIfNeeded();
     await _populateCaches();
@@ -328,8 +333,8 @@
     }
   }
 
-  /// Returns the version number of this release, according the to tags in the
-  /// repo.
+  /// Returns the version map of this release, according the to tags in the
+  /// repo and the output of `flutter --version --machine`.
   ///
   /// This looks for the tag attached to [revision] and, if it doesn't find one,
   /// git will give an error.
@@ -337,10 +342,15 @@
   /// If [strict] is true, the exact [revision] must be tagged to return the
   /// version.  If [strict] is not true, will look backwards in time starting at
   /// [revision] to find the most recent version tag.
-  Future<String> _getVersion() async {
+  ///
+  /// The version found as a git tag is added to the information given by
+  /// `flutter --version --machine` with the `frameworkVersionFromGit` tag, and
+  /// returned.
+  Future<Map<String, String>> _getVersion() async {
+    String gitVersion;
     if (strict) {
       try {
-        return _runGit(<String>['describe', '--tags', '--exact-match', revision]);
+        gitVersion = await _runGit(<String>['describe', '--tags', '--exact-match', revision]);
       } on PreparePackageException catch (exception) {
         throw PreparePackageException(
           'Git error when checking for a version tag attached to revision $revision.\n'
@@ -349,8 +359,18 @@
         );
       }
     } else {
-      return _runGit(<String>['describe', '--tags', '--abbrev=0', revision]);
+      gitVersion = await _runGit(<String>['describe', '--tags', '--abbrev=0', revision]);
     }
+    // Run flutter command twice, once to make sure the flutter command is built
+    // and ready (and thus won't output any junk on stdout the second time), and
+    // once to capture theJSON output. The second run should be fast.
+    await _runFlutter(<String>['--version', '--machine']);
+    final String versionJson = await _runFlutter(<String>['--version', '--machine']);
+    final Map<String, String> versionMap = <String, String>{};
+    final Map<String, dynamic> result = json.decode(versionJson) as Map<String, dynamic>;
+    result.forEach((String key, dynamic value) => versionMap[key] = value.toString());
+    versionMap[frameworkVersionTag] = gitVersion;
+    return versionMap;
   }
 
   /// Clone the Flutter repo and make sure that the git environment is sane
@@ -546,7 +566,7 @@
   final String metadataGsPath;
   final Branch branch;
   final String revision;
-  final String version;
+  final Map<String, String> version;
   final Directory tempDir;
   final File outputFile;
   final ProcessRunner _processRunner;
@@ -601,7 +621,8 @@
     final Map<String, dynamic> newEntry = <String, dynamic>{};
     newEntry['hash'] = revision;
     newEntry['channel'] = branchName;
-    newEntry['version'] = version;
+    newEntry['version'] = version[frameworkVersionTag];
+    newEntry['dart_sdk_version'] = version[dartVersionTag];
     newEntry['release_date'] = DateTime.now().toUtc().toIso8601String();
     newEntry['archive'] = destinationArchivePath;
     newEntry['sha256'] = await _getChecksum(outputFile);
@@ -630,24 +651,25 @@
       path.join(tempDir.absolute.path, getMetadataFilename(platform)),
     );
     await _runGsUtil(<String>['cp', gsPath, metadataFile.absolute.path]);
+    Map<String, dynamic> jsonData = <String, dynamic>{};
     if (!dryRun) {
       final String currentMetadata = metadataFile.readAsStringSync();
       if (currentMetadata.isEmpty) {
         throw PreparePackageException('Empty metadata received from server');
       }
-
-      Map<String, dynamic> jsonData;
       try {
         jsonData = json.decode(currentMetadata) as Map<String, dynamic>;
       } on FormatException catch (e) {
         throw PreparePackageException('Unable to parse JSON metadata received from cloud: $e');
       }
-
-      jsonData = await _addRelease(jsonData);
-
-      const JsonEncoder encoder = JsonEncoder.withIndent('  ');
-      metadataFile.writeAsStringSync(encoder.convert(jsonData));
     }
+    // Run _addRelease, even on a dry run, so we can inspect the metadata on a
+    // dry run. On a dry run, the only thing in the metadata file be the new
+    // release.
+    jsonData = await _addRelease(jsonData);
+
+    const JsonEncoder encoder = JsonEncoder.withIndent('  ');
+    metadataFile.writeAsStringSync(encoder.convert(jsonData));
     await _cloudCopy(
       src: metadataFile.absolute.path,
       dest: gsPath,
@@ -832,7 +854,7 @@
   int exitCode = 0;
   late String message;
   try {
-    final String version = await creator.initializeRepo();
+    final Map<String, String> version = await creator.initializeRepo();
     final File outputFile = await creator.createArchive();
     if (parsedArguments['publish'] as bool) {
       final ArchivePublisher publisher = ArchivePublisher(
diff --git a/dev/bots/test/prepare_package_test.dart b/dev/bots/test/prepare_package_test.dart
index d1f89ae..2c4e9cd 100644
--- a/dev/bots/test/prepare_package_test.dart
+++ b/dev/bots/test/prepare_package_test.dart
@@ -7,7 +7,7 @@
 import 'dart:typed_data';
 
 import 'package:path/path.dart' as path;
-import 'package:platform/platform.dart' show FakePlatform;
+import 'package:platform/platform.dart' show FakePlatform, Platform;
 
 import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
 import '../prepare_package.dart';
@@ -34,7 +34,7 @@
       )),
     );
   });
-  for (final String platformName in <String>['macos', 'linux', 'windows']) {
+  for (final String platformName in <String>[Platform.macOS, Platform.linux, Platform.windows]) {
     final FakePlatform platform = FakePlatform(
       operatingSystem: platformName,
       environment: <String, String>{
@@ -123,6 +123,10 @@
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
           'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
+          '$flutter --version --machine': <ProcessResult>[
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+          ],
           if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
           '$flutter doctor': null,
           '$flutter update-packages': null,
@@ -152,6 +156,10 @@
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
           'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
+          '$flutter --version --machine': <ProcessResult>[
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+          ],
           if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
           '$flutter doctor': null,
           '$flutter update-packages': null,
@@ -202,6 +210,10 @@
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
           'git describe --tags --abbrev=0 $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
+          '$flutter --version --machine': <ProcessResult>[
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+          ],
           if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
           '$flutter doctor': null,
           '$flutter update-packages': null,
@@ -244,6 +256,10 @@
           'git reset --hard $testRef': null,
           'git remote set-url origin https://github.com/flutter/flutter.git': null,
           'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
+          '$flutter --version --machine': <ProcessResult>[
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
+          ],
           if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
           '$flutter doctor': null,
           '$flutter update-packages': null,
@@ -362,7 +378,7 @@
           tempDir,
           testRef,
           Branch.stable,
-          'v1.2.3',
+          <String, String>{'frameworkVersionFromGit': 'v1.2.3', 'dartSdkVersion': '3.2.1'},
           outputFile,
           false,
           processManager: processManager,
@@ -458,7 +474,7 @@
           tempDir,
           testRef,
           Branch.stable,
-          'v1.2.3',
+          <String, String>{'frameworkVersionFromGit': 'v1.2.3', 'dartSdkVersion': '3.2.1'},
           outputFile,
           false,
           processManager: processManager,
@@ -482,7 +498,7 @@
           tempDir,
           testRef,
           Branch.stable,
-          'v1.2.3',
+          <String, String>{'frameworkVersionFromGit': 'v1.2.3', 'dartSdkVersion': '3.2.1'},
           outputFile,
           false,
           processManager: processManager,
@@ -504,7 +520,7 @@
           tempDir,
           testRef,
           Branch.stable,
-          'v1.2.3',
+          <String, String>{'frameworkVersionFromGit': 'v1.2.3', 'dartSdkVersion': '3.2.1'},
           outputFile,
           false,
           processManager: processManager,