Extract KernelCompiler class (#16937)

Wraps the compile function in a class injected via the global context,
which makes it easier to mock in unit tests -- specifically tests for
AOT snapshotting, which already require pretty significant amounts of
mock inputs.
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart
index f1da29b..771d8f0 100644
--- a/packages/flutter_tools/lib/src/bundle.dart
+++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -106,7 +106,7 @@
     String kernelBinaryFilename;
     if (needBuild) {
       ensureDirectoryExists(applicationKernelFilePath);
-      final CompilerOutput compilerOutput = await compile(
+      final CompilerOutput compilerOutput = await kernelCompiler.compile(
         sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
         incrementalCompilerByteStorePath: fs.path.absolute(getIncrementalCompilerByteStoreDirectory()),
         mainPath: fs.file(mainPath).absolute.path,
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index 716255f..ddf7039 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -366,7 +366,7 @@
   }
 
   if (previewDart2) {
-    final CompilerOutput compilerOutput = await compile(
+    final CompilerOutput compilerOutput = await kernelCompiler.compile(
       sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
       mainPath: mainPath,
       outputFilePath: kApplicationKernelPath,
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 4e3d3ba..dfab8d3 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -9,10 +9,13 @@
 
 import 'artifacts.dart';
 import 'base/common.dart';
+import 'base/context.dart';
 import 'base/io.dart';
 import 'base/process_manager.dart';
 import 'globals.dart';
 
+KernelCompiler get kernelCompiler => context[KernelCompiler];
+
 typedef void CompilerMessageConsumer(String message);
 
 class CompilerOutput {
@@ -59,7 +62,10 @@
   }
 }
 
-Future<CompilerOutput> compile(
+class KernelCompiler {
+  const KernelCompiler();
+
+  Future<CompilerOutput> compile(
     {String sdkRoot,
     String mainPath,
     String outputFilePath,
@@ -73,80 +79,81 @@
     String packagesPath,
     List<String> fileSystemRoots,
     String fileSystemScheme}) async {
-  final String frontendServer = artifacts.getArtifactPath(
-    Artifact.frontendServerSnapshotForEngineDartSdk
-  );
+    final String frontendServer = artifacts.getArtifactPath(
+      Artifact.frontendServerSnapshotForEngineDartSdk
+    );
 
-  // This is a URI, not a file path, so the forward slash is correct even on Windows.
-  if (!sdkRoot.endsWith('/'))
-    sdkRoot = '$sdkRoot/';
-  final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
-  if (!processManager.canRun(engineDartPath)) {
-    throwToolExit('Unable to find Dart binary at $engineDartPath');
-  }
-  final List<String> command = <String>[
-    engineDartPath,
-    frontendServer,
-    '--sdk-root',
-    sdkRoot,
-    '--strong',
-    '--target=flutter',
-  ];
-  if (trackWidgetCreation)
-    command.add('--track-widget-creation');
-  if (!linkPlatformKernelIn)
-    command.add('--no-link-platform');
-  if (aot) {
-    command.add('--aot');
-    command.add('--tfa');
-  }
-  if (entryPointsJsonFiles != null) {
-    for (String entryPointsJson in entryPointsJsonFiles) {
-      command.addAll(<String>['--entry-points', entryPointsJson]);
+    // This is a URI, not a file path, so the forward slash is correct even on Windows.
+    if (!sdkRoot.endsWith('/'))
+      sdkRoot = '$sdkRoot/';
+    final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
+    if (!processManager.canRun(engineDartPath)) {
+      throwToolExit('Unable to find Dart binary at $engineDartPath');
     }
-  }
-  if (incrementalCompilerByteStorePath != null) {
-    command.add('--incremental');
-  }
-  if (packagesPath != null) {
-    command.addAll(<String>['--packages', packagesPath]);
-  }
-  if (outputFilePath != null) {
-    command.addAll(<String>['--output-dill', outputFilePath]);
-  }
-  if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) {
-    command.addAll(<String>['--depfile', depFilePath]);
-  }
-  if (fileSystemRoots != null) {
-    for (String root in fileSystemRoots) {
-      command.addAll(<String>['--filesystem-root', root]);
+    final List<String> command = <String>[
+      engineDartPath,
+      frontendServer,
+      '--sdk-root',
+      sdkRoot,
+      '--strong',
+      '--target=flutter',
+    ];
+    if (trackWidgetCreation)
+      command.add('--track-widget-creation');
+    if (!linkPlatformKernelIn)
+      command.add('--no-link-platform');
+    if (aot) {
+      command.add('--aot');
+      command.add('--tfa');
     }
+    if (entryPointsJsonFiles != null) {
+      for (String entryPointsJson in entryPointsJsonFiles) {
+        command.addAll(<String>['--entry-points', entryPointsJson]);
+      }
+    }
+    if (incrementalCompilerByteStorePath != null) {
+      command.add('--incremental');
+    }
+    if (packagesPath != null) {
+      command.addAll(<String>['--packages', packagesPath]);
+    }
+    if (outputFilePath != null) {
+      command.addAll(<String>['--output-dill', outputFilePath]);
+    }
+    if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) {
+      command.addAll(<String>['--depfile', depFilePath]);
+    }
+    if (fileSystemRoots != null) {
+      for (String root in fileSystemRoots) {
+        command.addAll(<String>['--filesystem-root', root]);
+      }
+    }
+    if (fileSystemScheme != null) {
+      command.addAll(<String>['--filesystem-scheme', fileSystemScheme]);
+    }
+
+    if (extraFrontEndOptions != null)
+      command.addAll(extraFrontEndOptions);
+    command.add(mainPath);
+    printTrace(command.join(' '));
+    final Process server = await processManager
+        .start(command)
+        .catchError((dynamic error, StackTrace stack) {
+      printError('Failed to start frontend server $error, $stack');
+    });
+
+    final _StdoutHandler stdoutHandler = new _StdoutHandler();
+
+    server.stderr
+      .transform(utf8.decoder)
+      .listen((String s) { printError('compiler message: $s'); });
+    server.stdout
+      .transform(utf8.decoder)
+      .transform(const LineSplitter())
+      .listen(stdoutHandler.handler);
+    final int exitCode = await server.exitCode;
+    return exitCode == 0 ? stdoutHandler.compilerOutput.future : null;
   }
-  if (fileSystemScheme != null) {
-    command.addAll(<String>['--filesystem-scheme', fileSystemScheme]);
-  }
-
-  if (extraFrontEndOptions != null)
-    command.addAll(extraFrontEndOptions);
-  command.add(mainPath);
-  printTrace(command.join(' '));
-  final Process server = await processManager
-      .start(command)
-      .catchError((dynamic error, StackTrace stack) {
-    printError('Failed to start frontend server $error, $stack');
-  });
-
-  final _StdoutHandler stdoutHandler = new _StdoutHandler();
-
-  server.stderr
-    .transform(utf8.decoder)
-    .listen((String s) { printError('compiler message: $s'); });
-  server.stdout
-    .transform(utf8.decoder)
-    .transform(const LineSplitter())
-    .listen(stdoutHandler.handler);
-  final int exitCode = await server.exitCode;
-  return exitCode == 0 ? stdoutHandler.compilerOutput.future : null;
 }
 
 /// Wrapper around incremental frontend server compiler, that communicates with
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index ed3f48b..8b41406 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -22,6 +22,7 @@
 import 'base/port_scanner.dart';
 import 'base/utils.dart';
 import 'cache.dart';
+import 'compile.dart';
 import 'devfs.dart';
 import 'device.dart';
 import 'doctor.dart';
@@ -63,6 +64,7 @@
       IMobileDevice: () => const IMobileDevice(),
       IOSSimulatorUtils: () => new IOSSimulatorUtils(),
       IOSWorkflow: () => const IOSWorkflow(),
+      KernelCompiler: () => const KernelCompiler(),
       Logger: () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger(),
       OperatingSystemUtils: () => new OperatingSystemUtils(),
       PortScanner: () => const HostPortScanner(),
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index f5931e0..2fe63a8 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -46,7 +46,7 @@
               'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0'
             ))
           ));
-      final CompilerOutput output = await compile(sdkRoot: '/path/to/sdkroot',
+      final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot',
         mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
@@ -66,7 +66,7 @@
             ))
           ));
 
-      final CompilerOutput output = await compile(sdkRoot: '/path/to/sdkroot',
+      final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot',
         mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
@@ -88,7 +88,7 @@
           ))
       ));
 
-      final CompilerOutput output = await compile(sdkRoot: '/path/to/sdkroot',
+      final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot',
           mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);