Flutter 1.17.0.dev.3.2 cherrypicks (#55253)

* Update engine, includes Dart 2.8.0-20.11.beta

* git pull --ff-only (#54987)

* [flutter_tools] Reland: fix multiple dart defines (#54973)

* Fix `frameworkVersionFor` for flutter doctor and usage (#54217)

* update engine hash, use dart 2.8.0-20.10-beta (with old naming schema)

Co-authored-by: Jenn Magder <magder@google.com>
Co-authored-by: Jonah Williams <jonahwilliams@google.com>
Co-authored-by: Christopher Fujino <christopherfujino@gmail.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 6eb92cf..7b9898d 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-c9506cb8e93e5e8879152ff5c948b175abb5b997
+4c8c31f591882b3c668992d2e9da761118899f38
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index c0a7681..b6dea10 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -585,6 +585,18 @@
   await _runWebDebugTest('lib/stack_trace.dart');
   await _runWebDebugTest('lib/web_directory_loading.dart');
   await _runWebDebugTest('test/test.dart');
+  await _runWebDebugTest('lib/web_define_loading.dart',
+    additionalArguments: <String>[
+      '--dart-define=test.valueA=Example',
+      '--dart-define=test.valueB=Value',
+    ]
+  );
+  await _runWebReleaseTest('lib/web_define_loading.dart',
+    additionalArguments: <String>[
+      '--dart-define=test.valueA=Example',
+      '--dart-define=test.valueB=Value',
+    ]
+  );
 }
 
 Future<void> _runWebStackTraceTest(String buildMode) async {
@@ -627,10 +639,56 @@
   }
 }
 
+/// Run a web integration test in release mode.
+Future<void> _runWebReleaseTest(String target, {
+  List<String> additionalArguments = const<String>[],
+}) async {
+  final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
+  final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web');
+
+  // Build the app.
+  await runCommand(
+    flutter,
+    <String>[ 'clean' ],
+    workingDirectory: testAppDirectory,
+  );
+  await runCommand(
+    flutter,
+    <String>[
+      'build',
+      'web',
+      '--release',
+      ...additionalArguments,
+      '-t',
+      target,
+    ],
+    workingDirectory: testAppDirectory,
+    environment: <String, String>{
+      'FLUTTER_WEB': 'true',
+    },
+  );
+
+  // Run the app.
+  final String result = await evalTestAppInChrome(
+    appUrl: 'http://localhost:8080/index.html',
+    appDirectory: appBuildDirectory,
+  );
+
+  if (result.contains('--- TEST SUCCEEDED ---')) {
+    print('${green}Web release mode test passed.$reset');
+  } else {
+    print(result);
+    print('${red}Web release mode test failed.$reset');
+    exit(1);
+  }
+}
+
 /// Debug mode is special because `flutter build web` doesn't build in debug mode.
 ///
 /// Instead, we use `flutter run --debug` and sniff out the standard output.
-Future<void> _runWebDebugTest(String target) async {
+Future<void> _runWebDebugTest(String target, {
+  List<String> additionalArguments = const<String>[],
+}) async {
   final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
   final CapturedOutput output = CapturedOutput();
   bool success = false;
@@ -642,6 +700,7 @@
       '-d',
       'chrome',
       '--web-run-headless',
+      ...additionalArguments,
       '-t',
       target,
     ],
@@ -1120,7 +1179,8 @@
 /// Returns null if the contents are good. Returns a string if they are bad.
 /// The string is an error message.
 Future<String> verifyVersion(File file) async {
-  final RegExp pattern = RegExp(r'^\d+\.\d+\.\d+(\+hotfix\.\d+)?(-pre\.\d+)?$');
+  final RegExp pattern = RegExp(
+    r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
   final String version = await file.readAsString();
   if (!file.existsSync())
     return 'The version logic failed to create the Flutter version file.';
diff --git a/dev/bots/test/test_test.dart b/dev/bots/test/test_test.dart
index debf9ca..fe80aca 100644
--- a/dev/bots/test/test_test.dart
+++ b/dev/bots/test/test_test.dart
@@ -23,9 +23,9 @@
       const List<String> valid_versions = <String>[
         '1.2.3',
         '12.34.56',
-        '1.2.3-pre.1',
-        '1.2.3+hotfix.1',
-        '1.2.3+hotfix.12-pre.12',
+        '1.2.3.pre.1',
+        '1.2.3-4.5.pre',
+        '1.2.3-5.0.pre.12',
       ];
       for (final String version in valid_versions) {
         when(file.readAsString()).thenAnswer((Invocation invocation) => Future<String>.value(version));
@@ -41,8 +41,8 @@
       const List<String> invalid_versions = <String>[
         '1.2.3.4',
         '1.2.3.',
-        '1.2-pre.1',
-        '1.2.3-pre',
+        '1.2.pre.1',
+        '1.2.3-pre.1',
         '1.2.3-pre.1+hotfix.1',
         '  1.2.3',
         '1.2.3-hotfix.1',
diff --git a/dev/devicelab/lib/tasks/defines_task.dart b/dev/devicelab/lib/tasks/defines_task.dart
index 18ad1f0..afb3649 100644
--- a/dev/devicelab/lib/tasks/defines_task.dart
+++ b/dev/devicelab/lib/tasks/defines_task.dart
@@ -21,7 +21,8 @@
       '--verbose',
       '-d',
       deviceId,
-      '--dart-define=test.value=ExampleValue',
+      '--dart-define=test.valueA=Example',
+      '--dart-define=test.valueB=Value',
       'lib/defines.dart',
     ]);
   });
diff --git a/dev/integration_tests/ui/lib/defines.dart b/dev/integration_tests/ui/lib/defines.dart
index e94227d..db3d9b0 100644
--- a/dev/integration_tests/ui/lib/defines.dart
+++ b/dev/integration_tests/ui/lib/defines.dart
@@ -11,7 +11,7 @@
   runApp(
     const Center(
       child: Text(
-        String.fromEnvironment('test.value'),
+        String.fromEnvironment('test.valueA') + String.fromEnvironment('test.valueB'),
         textDirection: TextDirection.ltr,
       ),
     ),
diff --git a/dev/integration_tests/ui/test_driver/defines_test.dart b/dev/integration_tests/ui/test_driver/defines_test.dart
index 4440d2f..af049ec 100644
--- a/dev/integration_tests/ui/test_driver/defines_test.dart
+++ b/dev/integration_tests/ui/test_driver/defines_test.dart
@@ -16,7 +16,7 @@
     await driver.close();
   });
 
-  test('Can run with --dart-deinfe', () async {
+  test('Can run with --dart-define', () async {
     await driver.waitFor(find.text('ExampleValue'));
   });
 }
diff --git a/dev/integration_tests/web/lib/web_define_loading.dart b/dev/integration_tests/web/lib/web_define_loading.dart
new file mode 100644
index 0000000..32f771d
--- /dev/null
+++ b/dev/integration_tests/web/lib/web_define_loading.dart
@@ -0,0 +1,24 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:html' as html;
+
+Future<void> main() async {
+  final StringBuffer output = StringBuffer();
+  const String combined = String.fromEnvironment('test.valueA') +
+    String.fromEnvironment('test.valueB');
+  if (combined == 'ExampleValue') {
+    output.write('--- TEST SUCCEEDED ---');
+    print('--- TEST SUCCEEDED ---');
+  } else {
+    output.write('--- TEST FAILED ---');
+    print('--- TEST FAILED ---');
+  }
+
+  html.HttpRequest.request(
+    '/test-result',
+    method: 'POST',
+    sendData: '$output',
+  );
+}
diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh
index c220585..fbe0aaf 100755
--- a/packages/flutter_tools/bin/macos_assemble.sh
+++ b/packages/flutter_tools/bin/macos_assemble.sh
@@ -83,7 +83,7 @@
     -dTreeShakeIcons="${icon_tree_shaker_flag}"                             \
     -dDartObfuscation="${dart_obfuscation_flag}"                            \
     -dSplitDebugInfo="${SPLIT_DEBUG_INFO}"                                  \
-    -dDartDefines="${DART_DEFINES}"                                         \
+    --DartDefines="${DART_DEFINES}"                                         \
     -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}"                     \
     --build-inputs="${build_inputs_path}"                                   \
     --build-outputs="${build_outputs_path}"                                 \
diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index 47f408e..37806d5 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -186,7 +186,7 @@
     -dTrackWidgetCreation="${track_widget_creation_flag}"                 \
     -dDartObfuscation="${dart_obfuscation_flag}"                          \
     -dEnableBitcode="${bitcode_flag}"                                     \
-    -dDartDefines="${DART_DEFINES}"                                       \
+    --DartDefines="${DART_DEFINES}"                                       \
     -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}"                   \
     "${build_mode}_ios_bundle_flutter_assets"
 
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 3e26e45..393941a 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -914,7 +914,7 @@
                 args "-dDartObfuscation=true"
             }
             if (dartDefines != null) {
-                args "-dDartDefines=${dartDefines}"
+                args "--DartDefines=${dartDefines}"
             }
             if (extraGenSnapshotOptions != null) {
                 args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index a91159a..fca9ce7 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -19,7 +19,6 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../convert.dart';
 import '../flutter_manifest.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
@@ -331,7 +330,7 @@
     command.add('-Pshrink=true');
   }
   if (androidBuildInfo.buildInfo.dartDefines?.isNotEmpty ?? false) {
-    command.add('-Pdart-defines=${jsonEncode(androidBuildInfo.buildInfo.dartDefines)}');
+    command.add('-Pdart-defines=${androidBuildInfo.buildInfo.dartDefines.join(',')}');
   }
   if (shouldBuildPluginAsAar) {
     // Pass a system flag instead of a project flag, so this flag can be
diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart
index 30aa662..3e61d9a 100644
--- a/packages/flutter_tools/lib/src/aot.dart
+++ b/packages/flutter_tools/lib/src/aot.dart
@@ -14,7 +14,6 @@
 import 'build_system/targets/icon_tree_shaker.dart';
 import 'build_system/targets/ios.dart';
 import 'cache.dart';
-import 'convert.dart';
 import 'globals.dart' as globals;
 import 'ios/bitcode.dart';
 import 'project.dart';
@@ -87,7 +86,7 @@
         kBuildMode: getNameForBuildMode(buildInfo.mode),
         kTargetPlatform: getNameForTargetPlatform(platform),
         kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
-        kDartDefines: jsonEncode(buildInfo.dartDefines),
+        kDartDefines: buildInfo.dartDefines.join(','),
         kBitcodeFlag: bitcode.toString(),
         if (buildInfo?.extraGenSnapshotOptions?.isNotEmpty ?? false)
           kExtraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions.join(','),
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index 0ef9c0d..102f63a 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -7,7 +7,6 @@
 import '../../base/file_system.dart';
 import '../../build_info.dart';
 import '../../compile.dart';
-import '../../convert.dart';
 import '../../globals.dart' as globals;
 import '../../project.dart';
 import '../build_system.dart';
@@ -390,21 +389,10 @@
   }
 }
 
-/// Dart defines are encoded inside [Environment] as a JSON array.
+/// Dart defines are encoded inside [Environment] as a comma-separated list.
 List<String> parseDartDefines(Environment environment) {
   if (!environment.defines.containsKey(kDartDefines) || environment.defines[kDartDefines].isEmpty) {
     return const <String>[];
   }
-
-  final String dartDefinesJson = environment.defines[kDartDefines];
-  try {
-    final List<Object> parsedDefines = jsonDecode(dartDefinesJson) as List<Object>;
-    return parsedDefines.cast<String>();
-  } on FormatException {
-    throw Exception(
-      'The value of -D$kDartDefines is not formatted correctly.\n'
-      'The value must be a JSON-encoded list of strings but was:\n'
-      '$dartDefinesJson'
-    );
-  }
+  return environment.defines[kDartDefines].split(',');
 }
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
index 501cb3e..cbaa2a0 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -168,6 +168,8 @@
       '--libraries-spec=$specPath',
       '-o',
       outputKernel.path,
+      for (final String dartDefine in dartDefines)
+        '-D$dartDefine',
       '--packages=$packageFile',
       '--cfe-only',
       environment.buildDir.childFile('main.dart').path,
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart
index cfdcc0f..0413953 100644
--- a/packages/flutter_tools/lib/src/bundle.dart
+++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -17,7 +17,6 @@
 import 'build_system/targets/dart.dart';
 import 'build_system/targets/icon_tree_shaker.dart';
 import 'cache.dart';
-import 'convert.dart';
 import 'dart/package_map.dart';
 import 'devfs.dart';
 import 'globals.dart' as globals;
@@ -130,7 +129,7 @@
       kTrackWidgetCreation: trackWidgetCreation?.toString(),
       kIconTreeShakerFlag: treeShakeIcons ? 'true' : null,
       if (dartDefines != null && dartDefines.isNotEmpty)
-        kDartDefines: jsonEncode(dartDefines),
+        kDartDefines: dartDefines.join(','),
     },
     artifacts: globals.artifacts,
     fileSystem: globals.fs,
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index bf1d65a..a99e8d8 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -80,6 +80,7 @@
         'root of the current Flutter project.',
     );
     argParser.addOption(kExtraGenSnapshotOptions);
+    argParser.addOption(kDartDefines);
     argParser.addOption(
       'resource-pool-size',
       help: 'The maximum number of concurrent tasks the build system will run.',
@@ -174,6 +175,10 @@
     if (argResults.wasParsed(kExtraGenSnapshotOptions)) {
       results[kExtraGenSnapshotOptions] = argResults[kExtraGenSnapshotOptions] as String;
     }
+    // Workaround for dart-define formatting
+    if (argResults.wasParsed(kDartDefines)) {
+      results[kDartDefines] = argResults[kDartDefines] as String;
+    }
     return results;
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart
index 3d11dcb..7b51c3b 100644
--- a/packages/flutter_tools/lib/src/commands/upgrade.dart
+++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -236,7 +236,7 @@
   /// returns true, otherwise returns false.
   Future<bool> attemptFastForward(FlutterVersion oldFlutterVersion) async {
     final int code = await processUtils.stream(
-      <String>['git', 'pull', '--ff'],
+      <String>['git', 'pull', '--ff-only'],
       workingDirectory: workingDirectory,
       mapFunction: (String line) => matchesGitLine(line) ? null : line,
     );
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index b8e2432..0b94650 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -18,7 +18,6 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
-import '../convert.dart';
 import '../flutter_manifest.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
@@ -236,7 +235,7 @@
   }
 
   if (buildInfo.dartDefines?.isNotEmpty ?? false) {
-    xcodeBuildSettings.add('DART_DEFINES=${jsonEncode(buildInfo.dartDefines)}');
+    xcodeBuildSettings.add('DART_DEFINES=${buildInfo.dartDefines.join(',')}');
   }
 
   if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) {
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index fa899e1..bbd6cef 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -703,6 +703,7 @@
     this.devPatch,
     this.commits,
     this.hash,
+    this.gitTag,
   });
   const GitTagVersion.unknown()
     : x = null,
@@ -712,7 +713,8 @@
       commits = 0,
       devVersion = null,
       devPatch = null,
-      hash = '';
+      hash = '',
+      gitTag = '';
 
   /// The X in vX.Y.Z.
   final int x;
@@ -738,6 +740,9 @@
   /// The M in X.Y.Z-dev.N.M
   final int devPatch;
 
+  /// The git tag that is this version's closest ancestor.
+  final String gitTag;
+
   static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false}) {
     if (fetchTags) {
       final String channel = _runGit('git rev-parse --abbrev-ref HEAD', processUtils, workingDirectory);
@@ -752,35 +757,16 @@
   }
 
   // TODO(fujino): Deprecate this https://github.com/flutter/flutter/issues/53850
-  /// Check for the release tag format pre-v1.17.0
+  /// Check for the release tag format of the form x.y.z-dev.m.n
   static GitTagVersion parseLegacyVersion(String version) {
     final RegExp versionPattern = RegExp(
-      r'^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\+hotfix\.([0-9]+))?-([0-9]+)-g([a-f0-9]+)$');
-
-    final List<String> parts = versionPattern.matchAsPrefix(version)?.groups(<int>[1, 2, 3, 4, 5, 6]);
-    if (parts == null) {
-      return const GitTagVersion.unknown();
-    }
-    final List<int> parsedParts = parts.take(5).map<int>((String source) => source == null ? null : int.tryParse(source)).toList();
-    return GitTagVersion(
-      x: parsedParts[0],
-      y: parsedParts[1],
-      z: parsedParts[2],
-      hotfix: parsedParts[3],
-      commits: parsedParts[4],
-      hash: parts[5],
-    );
-  }
-
-  /// Check for the release tag format from v1.17.0 on
-  static GitTagVersion parseVersion(String version) {
-    final RegExp versionPattern = RegExp(
       r'^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev\.[0-9]+\.[0-9]+)?-([0-9]+)-g([a-f0-9]+)$');
     final List<String> parts = versionPattern.matchAsPrefix(version)?.groups(<int>[1, 2, 3, 4, 5, 6]);
     if (parts == null) {
       return const GitTagVersion.unknown();
     }
-    final List<int> parsedParts = parts.take(5).map<int>((String source) => source == null ? null : int.tryParse(source)).toList();
+    final List<int> parsedParts = parts.take(5).map<int>(
+      (String source) => source == null ? null : int.tryParse(source)).toList();
     List<int> devParts = <int>[null, null];
     if (parts[3] != null) {
       devParts = RegExp(r'^-dev\.(\d+)\.(\d+)')
@@ -798,6 +784,38 @@
       devPatch: devParts[1],
       commits: parsedParts[4],
       hash: parts[5],
+      gitTag: '${parts[0]}.${parts[1]}.${parts[2]}${parts[3] ?? ''}', // x.y.z-dev.m.n
+    );
+  }
+
+  /// Check for the release tag format of the form x.y.z-m.n.pre
+  static GitTagVersion parseVersion(String version) {
+    final RegExp versionPattern = RegExp(
+      r'^(\d+)\.(\d+)\.(\d+)(-\d+\.\d+\.pre)?-(\d+)-g([a-f0-9]+)$');
+    final List<String> parts = versionPattern.matchAsPrefix(version)?.groups(<int>[1, 2, 3, 4, 5, 6]);
+    if (parts == null) {
+      return const GitTagVersion.unknown();
+    }
+    final List<int> parsedParts = parts.take(5).map<int>(
+      (String source) => source == null ? null : int.tryParse(source)).toList();
+    List<int> devParts = <int>[null, null];
+    if (parts[3] != null) {
+      devParts = RegExp(r'^-(\d+)\.(\d+)\.pre')
+        .matchAsPrefix(parts[3])
+        ?.groups(<int>[1, 2])
+        ?.map<int>(
+          (String source) => source == null ? null : int.tryParse(source)
+        )?.toList() ?? <int>[null, null];
+    }
+    return GitTagVersion(
+      x: parsedParts[0],
+      y: parsedParts[1],
+      z: parsedParts[2],
+      devVersion: devParts[0],
+      devPatch: devParts[1],
+      commits: parsedParts[4],
+      hash: parts[5],
+      gitTag: '${parts[0]}.${parts[1]}.${parts[2]}${parts[3] ?? ''}', // x.y.z-m.n.pre
     );
   }
 
@@ -821,15 +839,16 @@
       return '0.0.0-unknown';
     }
     if (commits == 0) {
-      if (hotfix != null) {
-        return '$x.$y.$z+hotfix.$hotfix';
-      }
-      return '$x.$y.$z';
+      return gitTag;
     }
     if (hotfix != null) {
-      return '$x.$y.$z+hotfix.${hotfix + 1}-pre.$commits';
+      // This is an unexpected state where untagged commits exist past a hotfix
+      return '$x.$y.$z+hotfix.${hotfix + 1}.pre.$commits';
     }
-    return '$x.$y.${z + 1}-pre.$commits';
+    if (devPatch != null && devVersion != null) {
+      return '$x.$y.$z-${devVersion + 1}.0.pre.$commits';
+    }
+    return '$x.$y.${z + 1}.pre.$commits';
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index c80535b..8ee410f 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -13,7 +13,6 @@
 import '../build_system/targets/dart.dart';
 import '../build_system/targets/icon_tree_shaker.dart';
 import '../build_system/targets/web.dart';
-import '../convert.dart';
 import '../globals.dart' as globals;
 import '../platform_plugins.dart';
 import '../plugins.dart';
@@ -49,7 +48,7 @@
         kTargetFile: target,
         kInitializePlatform: initializePlatform.toString(),
         kHasWebPlugins: hasWebPlugins.toString(),
-        kDartDefines: jsonEncode(buildInfo.dartDefines),
+        kDartDefines: buildInfo.dartDefines.join(','),
         kCspMode: csp.toString(),
         kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
       },
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
index cdbed86..0365e10 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
@@ -239,7 +239,7 @@
       return ProcessResult(0, 0, 'v10.0.0\r\nv20.0.0\r\n30.0.0-dev.0.0', '');
     }
     if (command[0] == 'git' && command[1] == 'checkout') {
-      version = command[2] as String;
+      version = (command[2] as String).replaceFirst('v', '');
     }
     return ProcessResult(0, 0, '', '');
   }
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
index 6490f19..6a0380d 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
@@ -360,12 +360,14 @@
 
   test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async {
     environment.defines[kBuildMode] = 'release';
-    environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
+    environment.defines[kDartDefines] = 'FOO=bar,BAZ=qux';
     processManager.addCommand(FakeCommand(
       command: <String>[
         ...kDart2jsLinuxArgs,
         '-o',
         environment.buildDir.childFile('app.dill').absolute.path,
+        '-DFOO=bar',
+        '-DBAZ=qux',
          '--packages=${globals.fs.path.join('foo', '.packages')}',
         '--cfe-only',
         environment.buildDir.childFile('main.dart').absolute.path,
@@ -391,12 +393,14 @@
 
   test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async {
     environment.defines[kBuildMode] = 'profile';
-    environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]';
+    environment.defines[kDartDefines] = 'FOO=bar,BAZ=qux';
     processManager.addCommand(FakeCommand(
       command: <String>[
         ...kDart2jsLinuxArgs,
         '-o',
         environment.buildDir.childFile('app.dill').absolute.path,
+        '-DFOO=bar',
+        '-DBAZ=qux',
          '--packages=${globals.fs.path.join('foo', '.packages')}',
         '--cfe-only',
         environment.buildDir.childFile('main.dart').absolute.path,
@@ -421,27 +425,6 @@
     ProcessManager: () => processManager,
   }));
 
-  test('Dart2JSTarget throws developer-friendly exception on misformatted DartDefines', () => testbed.run(() async {
-    environment.defines[kBuildMode] = 'profile';
-    environment.defines[kDartDefines] = '[misformatted json';
-    try {
-      await const Dart2JSTarget().build(environment);
-      fail('Call to build() must not have succeeded.');
-    } on Exception catch(exception) {
-      expect(
-        '$exception',
-        'Exception: The value of -D$kDartDefines is not formatted correctly.\n'
-        'The value must be a JSON-encoded list of strings but was:\n'
-        '[misformatted json',
-      );
-    }
-
-    // Should not attempt to run any processes.
-    verifyNever(globals.processManager.run(any));
-  }, overrides: <Type, Generator>{
-    ProcessManager: () => MockProcessManager(),
-  }));
-
   test('Generated service worker correctly inlines file hashes', () {
     final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'});
 
diff --git a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart
index 87506e5..043c9df 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart
@@ -109,7 +109,8 @@
   });
 
   testUsingContext('build aot outputs timing info', () async {
-    globals.fs.file('.dart_tool/flutter_build/cce09742720db17ffec62331bd7e42d5/app.so')
+    globals.fs
+      .file('.dart_tool/flutter_build/0c21fd4ab3b8bde8b385ff01d08e0093/app.so')
       .createSync(recursive: true);
     when(globals.buildSystem.build(any, any))
       .thenAnswer((Invocation invocation) async {
diff --git a/packages/flutter_tools/test/general.shard/version_test.dart b/packages/flutter_tools/test/general.shard/version_test.dart
index 83e8cab..f47e5a2 100644
--- a/packages/flutter_tools/test/general.shard/version_test.dart
+++ b/packages/flutter_tools/test/general.shard/version_test.dart
@@ -394,29 +394,44 @@
     const String hash = 'abcdef';
     GitTagVersion gitTagVersion;
 
-    // legacy tag release format
-    gitTagVersion = GitTagVersion.parse('v1.2.3-4-g$hash');
-    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.4');
-    expect(gitTagVersion.devVersion, null);
-    expect(gitTagVersion.devPatch, null);
-
-    // new dev tag release format
-    gitTagVersion = GitTagVersion.parse('1.2.3-dev.4.5-13-g$hash');
-    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.13');
+    // legacy tag format (x.y.z-dev.m.n), master channel
+    gitTagVersion = GitTagVersion.parse('1.2.3-dev.4.5-4-g$hash');
+    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-5.0.pre.4');
+    expect(gitTagVersion.gitTag, '1.2.3-dev.4.5');
     expect(gitTagVersion.devVersion, 4);
     expect(gitTagVersion.devPatch, 5);
 
-    // new stable tag release format
+    // new tag release format, master channel
+    gitTagVersion = GitTagVersion.parse('1.2.3-4.5.pre-13-g$hash');
+    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-5.0.pre.13');
+    expect(gitTagVersion.gitTag, '1.2.3-4.5.pre');
+    expect(gitTagVersion.devVersion, 4);
+    expect(gitTagVersion.devPatch, 5);
+
     gitTagVersion = GitTagVersion.parse('1.2.3-13-g$hash');
-    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.13');
+    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4.pre.13');
+    expect(gitTagVersion.gitTag, '1.2.3');
     expect(gitTagVersion.devVersion, null);
     expect(gitTagVersion.devPatch, null);
 
-    expect(GitTagVersion.parse('98.76.54-32-g$hash').frameworkVersionFor(hash), '98.76.55-pre.32');
+    // new tag release format, dev channel
+    gitTagVersion = GitTagVersion.parse('1.2.3-4.5.pre-0-g$hash');
+    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.3-4.5.pre');
+    expect(gitTagVersion.gitTag, '1.2.3-4.5.pre');
+    expect(gitTagVersion.devVersion, 4);
+    expect(gitTagVersion.devPatch, 5);
+
+    // new tag release format, stable channel
+    gitTagVersion = GitTagVersion.parse('1.2.3-13-g$hash');
+    expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4.pre.13');
+    expect(gitTagVersion.gitTag, '1.2.3');
+    expect(gitTagVersion.devVersion, null);
+    expect(gitTagVersion.devPatch, null);
+
+    expect(GitTagVersion.parse('98.76.54-32-g$hash').frameworkVersionFor(hash), '98.76.55.pre.32');
     expect(GitTagVersion.parse('10.20.30-0-g$hash').frameworkVersionFor(hash), '10.20.30');
-    expect(GitTagVersion.parse('v1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash), '1.2.3+hotfix.2-pre.4');
     expect(testLogger.traceText, '');
-    expect(GitTagVersion.parse('1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
+    expect(GitTagVersion.parse('v1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
     expect(GitTagVersion.parse('x1.2.3-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
     expect(GitTagVersion.parse('1.0.0-unknown-0-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
     expect(GitTagVersion.parse('beta-1-g$hash').frameworkVersionFor(hash), '0.0.0-unknown');
@@ -425,7 +440,7 @@
     expect(testLogger.errorText, '');
     expect(
       testLogger.traceText,
-      'Could not interpret results of "git describe": 1.2.3+hotfix.1-4-gabcdef\n'
+      'Could not interpret results of "git describe": v1.2.3+hotfix.1-4-gabcdef\n'
       'Could not interpret results of "git describe": x1.2.3-4-gabcdef\n'
       'Could not interpret results of "git describe": 1.0.0-unknown-0-gabcdef\n'
       'Could not interpret results of "git describe": beta-1-gabcdef\n'