Add hidden options --extra-front-end-options and --extra-gen-snapshot-options to flutter tool (#12219)

This CL introduces 2 hidden options to 'flutter build aot' and 'flutter run' for passing arbitrary arguments to front-end server and to gen_snapshot tool when building and running flutter app in --profile or --release modes.

The ability to pass arbitrary options simplifies various experiments, as it removes the need to change defaults and rebuild flutter engine for every tested configuration.
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index db4acac..6a867c6 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -291,6 +291,10 @@
   }
   if (buildInfo.previewDart2)
     command.add('-Ppreview-dart-2=true');
+  if (buildInfo.extraFrontEndOptions != null)
+    command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
+  if (buildInfo.extraGenSnapshotOptions != null)
+    command.add('-Pextra-gen-snapshot-options=${buildInfo.extraGenSnapshotOptions}');
   command.add(assembleTask);
   final int exitCode = await runCommandAndStreamOutput(
       command,
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index b4be334..672c43b 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -10,7 +10,10 @@
 
 /// Information about a build to be performed or used.
 class BuildInfo {
-  const BuildInfo(this.mode, this.flavor, { this.previewDart2 });
+  const BuildInfo(this.mode, this.flavor,
+      {this.previewDart2,
+      this.extraFrontEndOptions,
+      this.extraGenSnapshotOptions});
 
   final BuildMode mode;
   /// Represents a custom Android product flavor or an Xcode scheme, null for
@@ -24,6 +27,12 @@
   // Whether build should be done using Dart2 Frontend parser.
   final bool previewDart2;
 
+  /// Extra command-line options for front-end.
+  final String extraFrontEndOptions;
+
+  /// Extra command-line options for gen_snapshot.
+  final String extraGenSnapshotOptions;
+
   static const BuildInfo debug = const BuildInfo(BuildMode.debug, null);
   static const BuildInfo profile = const BuildInfo(BuildMode.profile, null);
   static const BuildInfo release = const BuildInfo(BuildMode.release, null);
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index 01f1ef6..0e30bd4 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -17,6 +17,7 @@
 import '../dart/package_map.dart';
 import '../globals.dart';
 import '../resident_runner.dart';
+import '../runner/flutter_command.dart';
 import 'build.dart';
 
 // Files generated by the ahead-of-time snapshot builder.
@@ -37,7 +38,17 @@
       )
       ..addFlag('interpreter')
       ..addFlag('quiet', defaultsTo: false)
-      ..addFlag('preview-dart-2', negatable: false);
+      ..addFlag('preview-dart-2', negatable: false)
+      ..addOption(FlutterOptions.kExtraFrontEndOptions,
+        allowMultiple: true,
+        splitCommas: true,
+        hide: true,
+      )
+      ..addOption(FlutterOptions.kExtraGenSnapshotOptions,
+        allowMultiple: true,
+        splitCommas: true,
+        hide: true,
+      );
   }
 
   @override
@@ -67,6 +78,8 @@
       outputPath: argResults['output-dir'],
       interpreter: argResults['interpreter'],
       previewDart2: argResults['preview-dart-2'],
+      extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
+      extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
     );
     status?.stop();
 
@@ -95,6 +108,8 @@
   String outputPath,
   bool interpreter: false,
   bool previewDart2: false,
+  List<String> extraFrontEndOptions,
+  List<String> extraGenSnapshotOptions,
 }) async {
   outputPath ??= getAotBuildDirectory();
   try {
@@ -105,6 +120,8 @@
       outputPath: outputPath,
       interpreter: interpreter,
       previewDart2: previewDart2,
+      extraFrontEndOptions: extraFrontEndOptions,
+      extraGenSnapshotOptions: extraGenSnapshotOptions,
     );
   } on String catch (error) {
     // Catch the String exceptions thrown from the `runCheckedSync` methods below.
@@ -121,6 +138,8 @@
   String outputPath,
   bool interpreter: false,
   bool previewDart2: false,
+  List<String> extraFrontEndOptions,
+  List<String> extraGenSnapshotOptions,
 }) async {
   outputPath ??= getAotBuildDirectory();
   if (!isAotBuildMode(buildMode) && !interpreter) {
@@ -220,6 +239,14 @@
     '--causal_async_stacks',
   ];
 
+  if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty)
+    printTrace("Extra front-end options: $extraFrontEndOptions");
+
+  if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) {
+    printTrace("Extra gen-snapshot options: $extraGenSnapshotOptions");
+    genSnapshotCmd.addAll(extraGenSnapshotOptions);
+  }
+
   if (!interpreter) {
     genSnapshotCmd.add('--embedder_entry_points_manifest=$vmEntryPoints');
     genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints');
@@ -280,6 +307,7 @@
     mainPath = await compile(
       sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
       mainPath: mainPath,
+      extraFrontEndOptions: extraFrontEndOptions,
     );
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 6afefa9..9d2cf36 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -155,6 +155,9 @@
             'measure the startup time and the app restart time, write the\n'
             'results out to "refresh_benchmark.json", and exit. This flag is\n'
             'intended for use in generating automated flutter benchmarks.');
+
+    argParser.addOption(FlutterOptions.kExtraFrontEndOptions, hide: true);
+    argParser.addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true);
   }
 
   List<Device> devices;
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 961c5a6..b927f5a 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -56,7 +56,10 @@
   }
 }
 
-Future<String> compile({String sdkRoot, String mainPath}) async {
+Future<String> compile(
+    {String sdkRoot,
+    String mainPath,
+    List<String> extraFrontEndOptions}) async {
   final String frontendServer = artifacts.getArtifactPath(
     Artifact.frontendServerSnapshotForEngineDartSdk
   );
@@ -64,13 +67,18 @@
   // This is a URI, not a file path, so the forward slash is correct even on Windows.
   if (!sdkRoot.endsWith('/'))
     sdkRoot = '$sdkRoot/';
-  final Process server = await processManager.start(<String>[
+  final List<String> command = <String>[
     _dartExecutable(),
     frontendServer,
     '--sdk-root',
     sdkRoot,
-    mainPath
-  ]).catchError((dynamic error, StackTrace stack) {
+  ];
+  if (extraFrontEndOptions != null)
+    command.addAll(extraFrontEndOptions);
+  command.add(mainPath);
+  final Process server = await processManager
+      .start(command)
+      .catchError((dynamic error, StackTrace stack) {
     printTrace('Failed to start frontend server $error, $stack');
   });
 
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 1a98e5a..5a42245 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -54,6 +54,12 @@
   final DateTime endTimeOverride;
 }
 
+/// Common flutter command line options.
+class FlutterOptions {
+  static const String kExtraFrontEndOptions = 'extra-front-end-options';
+  static const String kExtraGenSnapshotOptions = 'extra-gen-snapshot-options';
+}
+
 abstract class FlutterCommand extends Command<Null> {
   @override
   FlutterCommandRunner get runner => super.runner;
@@ -149,7 +155,13 @@
         : null,
       previewDart2: argParser.options.containsKey('preview-dart-2')
         ? argResults['preview-dart-2']
-        : false);
+        : false,
+      extraFrontEndOptions: argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions)
+          ? argResults[FlutterOptions.kExtraFrontEndOptions]
+          : null,
+      extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions)
+          ? argResults[FlutterOptions.kExtraGenSnapshotOptions]
+          : null);
   }
 
   void setupApplicationPackages() {