[vm/kernel] Move functionality of Fuchsia's compiler.dart into pkg/vm

This includes:
* Selecting front-end target.
* Support for multi-root virtual file system.
* Not linking platform into resulting kernel file.
* Specifying input as URI (instead of file path) on command line.
* Automatically converting input script URI to package URI.
* Writing ninja dependencies file.
* Writing package-split kernel binaries.

After this change Fuchsia's compiler.dart will become a small wrapper
over pkg/vm, sharing most logic and even most command line options
with pkg/vm gen_kernel tool.

Also, this CL attempts to share some pieces of code between frontend
server and gen_kernel.

In addition, seperate bytecode generation for package-split binaries
is implemented (needed for https://dart-review.googlesource.com/c/sdk/+/85469).

Corresponding Fuchsia CL: https://fuchsia-review.googlesource.com/c/topaz/+/229964

Change-Id: I12d7b2f6401357b3c9df2e31bc736af5a9dc5fd2
Reviewed-on: https://dart-review.googlesource.com/c/85721
Reviewed-by: Alexander Aprelev <aam@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/bin/gen_kernel.dart b/pkg/vm/bin/gen_kernel.dart
index bdb5df6..eefe915 100644
--- a/pkg/vm/bin/gen_kernel.dart
+++ b/pkg/vm/bin/gen_kernel.dart
@@ -3,53 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
+import 'dart:io' show exit;
 
-import 'package:args/args.dart' show ArgParser, ArgResults;
-import 'package:front_end/src/api_unstable/vm.dart';
-import 'package:kernel/binary/ast_to_binary.dart';
-import 'package:kernel/src/tool/batch_util.dart' as batch_util;
-import 'package:kernel/target/targets.dart' show TargetFlags;
+import 'package:args/args.dart' show ArgParser;
 import 'package:kernel/text/ast_to_text.dart'
     show globalDebuggingNames, NameSystem;
+import 'package:kernel/src/tool/batch_util.dart' as batch_util;
 import 'package:vm/kernel_front_end.dart'
-    show compileToKernel, ErrorDetector, ErrorPrinter, parseCommandLineDefines;
-import 'package:vm/target/vm.dart' show VmTarget;
+    show
+        createCompilerArgParser,
+        runCompiler,
+        successExitCode,
+        compileTimeErrorExitCode,
+        badUsageExitCode;
 
-final ArgParser _argParser = new ArgParser(allowTrailingOptions: true)
-  ..addOption('platform',
-      help: 'Path to vm_platform_strong.dill file', defaultsTo: null)
-  ..addOption('packages', help: 'Path to .packages file', defaultsTo: null)
-  ..addOption('output',
-      abbr: 'o', help: 'Path to resulting dill file', defaultsTo: null)
-  ..addFlag('aot',
-      help:
-          'Produce kernel file for AOT compilation (enables global transformations).',
-      defaultsTo: false)
-  ..addFlag('sync-async',
-      help: 'Start `async` functions synchronously', defaultsTo: true)
-  ..addFlag('embed-sources',
-      help: 'Embed source files in the generated kernel component',
-      defaultsTo: true)
-  ..addFlag('tfa',
-      help:
-          'Enable global type flow analysis and related transformations in AOT mode.',
-      defaultsTo: true)
-  ..addMultiOption('define',
-      abbr: 'D',
-      help: 'The values for the environment constants (e.g. -Dkey=value).')
-  ..addFlag('enable-asserts',
-      help: 'Whether asserts will be enabled.', defaultsTo: false)
-  ..addFlag('enable-constant-evaluation',
-      help: 'Whether kernel constant evaluation will be enabled.',
-      defaultsTo: true)
-  ..addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false)
-  ..addFlag('emit-bytecode-source-positions',
-      help: 'Emit source positions in bytecode', defaultsTo: false)
-  ..addFlag('drop-ast',
-      help: 'Drop AST for members with bytecode', defaultsTo: false)
-  ..addFlag('use-future-bytecode-format',
-      help: 'Generate bytecode in the bleeding edge format', defaultsTo: false);
+final ArgParser _argParser = createCompilerArgParser();
 
 final String _usage = '''
 Usage: dart pkg/vm/bin/gen_kernel.dart --platform vm_platform_strong.dill [options] input.dart
@@ -59,9 +27,6 @@
 ${_argParser.usage}
 ''';
 
-const int _badUsageExitCode = 1;
-const int _compileTimeErrorExitCode = 254;
-
 main(List<String> arguments) async {
   if (arguments.isNotEmpty && arguments.last == '--batch') {
     await runBatchModeCompiler();
@@ -71,72 +36,7 @@
 }
 
 Future<int> compile(List<String> arguments) async {
-  final ArgResults options = _argParser.parse(arguments);
-  final String platformKernel = options['platform'];
-
-  if ((options.rest.length != 1) || (platformKernel == null)) {
-    print(_usage);
-    return _badUsageExitCode;
-  }
-
-  final String filename = options.rest.single;
-  final String kernelBinaryFilename = options['output'] ?? "$filename.dill";
-  final String packages = options['packages'];
-  final bool aot = options['aot'];
-  final bool tfa = options['tfa'];
-  final bool genBytecode = options['gen-bytecode'];
-  final bool emitBytecodeSourcePositions =
-      options['emit-bytecode-source-positions'];
-  final bool dropAST = options['drop-ast'];
-  final bool useFutureBytecodeFormat = options['use-future-bytecode-format'];
-  final bool enableAsserts = options['enable-asserts'];
-  final bool enableConstantEvaluation = options['enable-constant-evaluation'];
-  final Map<String, String> environmentDefines = {};
-
-  if (!parseCommandLineDefines(options['define'], environmentDefines, _usage)) {
-    return _badUsageExitCode;
-  }
-
-  final errorPrinter = new ErrorPrinter();
-  final errorDetector = new ErrorDetector(previousErrorHandler: errorPrinter);
-
-  final CompilerOptions compilerOptions = new CompilerOptions()
-    ..target = new VmTarget(new TargetFlags(syncAsync: true))
-    ..linkedDependencies = <Uri>[
-      Uri.base.resolveUri(new Uri.file(platformKernel))
-    ]
-    ..packagesFileUri =
-        packages != null ? Uri.base.resolveUri(new Uri.file(packages)) : null
-    ..onDiagnostic = (DiagnosticMessage m) {
-      errorDetector(m);
-    }
-    ..embedSourceText = options['embed-sources'];
-
-  final inputUri = new Uri.file(filename);
-  final component = await compileToKernel(
-      Uri.base.resolveUri(inputUri), compilerOptions,
-      aot: aot,
-      useGlobalTypeFlowAnalysis: tfa,
-      environmentDefines: environmentDefines,
-      genBytecode: genBytecode,
-      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
-      dropAST: dropAST,
-      useFutureBytecodeFormat: useFutureBytecodeFormat,
-      enableAsserts: enableAsserts,
-      enableConstantEvaluation: enableConstantEvaluation);
-
-  errorPrinter.printCompilationMessages(inputUri);
-
-  if (errorDetector.hasCompilationErrors || (component == null)) {
-    return _compileTimeErrorExitCode;
-  }
-
-  final IOSink sink = new File(kernelBinaryFilename).openWrite();
-  final BinaryPrinter printer = new BinaryPrinter(sink);
-  printer.writeComponentFile(component);
-  await sink.close();
-
-  return 0;
+  return runCompiler(_argParser.parse(arguments), _usage);
 }
 
 Future runBatchModeCompiler() async {
@@ -160,10 +60,10 @@
     globalDebuggingNames = new NameSystem();
 
     switch (exitCode) {
-      case 0:
+      case successExitCode:
         return batch_util.CompilerOutcome.Ok;
-      case _compileTimeErrorExitCode:
-      case _badUsageExitCode:
+      case compileTimeErrorExitCode:
+      case badUsageExitCode:
         return batch_util.CompilerOutcome.Fail;
       default:
         throw 'Could not obtain correct exit code from compiler.';
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index 9c49962..6dd7960 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -39,13 +39,16 @@
 // exception.
 const String symbolForTypeCast = ' in type cast';
 
-void generateBytecode(Component component,
-    {bool dropAST: false,
-    bool emitSourcePositions: false,
-    bool omitAssertSourcePositions: false,
-    bool useFutureBytecodeFormat: false,
-    Map<String, String> environmentDefines,
-    ErrorReporter errorReporter}) {
+void generateBytecode(
+  Component component, {
+  bool dropAST: false,
+  bool emitSourcePositions: false,
+  bool omitAssertSourcePositions: false,
+  bool useFutureBytecodeFormat: false,
+  Map<String, String> environmentDefines,
+  ErrorReporter errorReporter,
+  List<Library> libraries,
+}) {
   final coreTypes = new CoreTypes(component);
   void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {}
   final hierarchy = new ClassHierarchy(component,
@@ -55,19 +58,25 @@
   final constantsBackend =
       new VmConstantsBackend(environmentDefines, coreTypes);
   final errorReporter = new ForwardConstantEvaluationErrors(typeEnvironment);
-  new BytecodeGenerator(
-          component,
-          coreTypes,
-          hierarchy,
-          typeEnvironment,
-          constantsBackend,
-          emitSourcePositions,
-          omitAssertSourcePositions,
-          useFutureBytecodeFormat,
-          errorReporter)
-      .visitComponent(component);
+  libraries ??= component.libraries;
+  final bytecodeGenerator = new BytecodeGenerator(
+      component,
+      coreTypes,
+      hierarchy,
+      typeEnvironment,
+      constantsBackend,
+      emitSourcePositions,
+      omitAssertSourcePositions,
+      useFutureBytecodeFormat,
+      errorReporter);
+  for (var library in libraries) {
+    bytecodeGenerator.visitLibrary(library);
+  }
   if (dropAST) {
-    new DropAST().visitComponent(component);
+    final astRemover = new DropAST(component);
+    for (var library in libraries) {
+      astRemover.visitLibrary(library);
+    }
   }
 }
 
@@ -141,9 +150,6 @@
   }
 
   @override
-  visitComponent(Component node) => node.visitChildren(this);
-
-  @override
   visitLibrary(Library node) {
     if (node.isExternal) {
       return;
@@ -3116,14 +3122,8 @@
 class DropAST extends Transformer {
   BytecodeMetadataRepository metadata;
 
-  @override
-  TreeNode visitComponent(Component node) {
-    metadata = node.metadata[new BytecodeMetadataRepository().tag];
-    if (metadata != null) {
-      return super.visitComponent(node);
-    }
-    return node;
-  }
+  DropAST(Component component)
+      : metadata = component.metadata[new BytecodeMetadataRepository().tag];
 
   @override
   TreeNode defaultMember(Member node) {
@@ -3152,7 +3152,8 @@
     return node;
   }
 
-  bool _hasBytecode(Member node) => metadata.mapping.containsKey(node);
+  bool _hasBytecode(Member node) =>
+      metadata != null && metadata.mapping.containsKey(node);
 }
 
 typedef void GenerateContinuation();
diff --git a/pkg/vm/lib/frontend_server.dart b/pkg/vm/lib/frontend_server.dart
index fc88b69..99e6976 100644
--- a/pkg/vm/lib/frontend_server.dart
+++ b/pkg/vm/lib/frontend_server.dart
@@ -9,7 +9,6 @@
 import 'dart:io' hide FileSystemEntity;
 
 import 'package:args/args.dart';
-import 'package:build_integration/file_system/multi_root.dart';
 // front_end/src imports below that require lint `ignore_for_file`
 // are a temporary state of things until frontend team builds better api
 // that would replace api used below. This api was made private in
@@ -21,14 +20,18 @@
 import 'package:kernel/binary/limited_ast_to_binary.dart';
 import 'package:kernel/kernel.dart'
     show Component, loadComponentSourceFromBytes;
-import 'package:kernel/target/targets.dart';
 import 'package:path/path.dart' as path;
 import 'package:usage/uuid/uuid.dart';
 
 import 'package:vm/incremental_compiler.dart' show IncrementalCompiler;
 import 'package:vm/kernel_front_end.dart'
-    show compileToKernel, parseCommandLineDefines;
-import 'package:vm/target/install.dart' show installAdditionalTargets;
+    show
+        compileToKernel,
+        parseCommandLineDefines,
+        convertFileOrUriArgumentToUri,
+        createFrontEndTarget,
+        createFrontEndFileSystem,
+        writeDepfile;
 
 ArgParser argParser = new ArgParser(allowTrailingOptions: true)
   ..addFlag('train',
@@ -223,6 +226,7 @@
   bool unsafePackageSerialization;
 
   CompilerOptions _compilerOptions;
+  FileSystem _fileSystem;
   Uri _mainSource;
   ArgResults _options;
 
@@ -248,6 +252,8 @@
     IncrementalCompiler generator,
   }) async {
     _options = options;
+    _fileSystem = createFrontEndFileSystem(
+        options['filesystem-scheme'], options['filesystem-root']);
     setMainSourceFilename(filename);
     _kernelBinaryFilenameFull = _options['output-dill'] ?? '$filename.dill';
     _kernelBinaryFilenameIncremental = _options['output-incremental-dill'] ??
@@ -264,6 +270,7 @@
         options['platform'] ?? 'platform_strong.dill';
     final CompilerOptions compilerOptions = new CompilerOptions()
       ..sdkRoot = sdkRoot
+      ..fileSystem = _fileSystem
       ..packagesFileUri = _getFileOrUri(_options['packages'])
       ..sdkSummary = sdkRoot.resolve(platformKernelDill)
       ..verbose = options['verbose']
@@ -288,14 +295,8 @@
           printDiagnosticMessage(message, _outputStream.writeln);
         }
       };
-    if (options.wasParsed('filesystem-root')) {
-      List<Uri> rootUris = <Uri>[];
-      for (String root in options['filesystem-root']) {
-        rootUris.add(Uri.base.resolveUri(new Uri.file(root)));
-      }
-      compilerOptions.fileSystem = new MultiRootFileSystem(
-          options['filesystem-scheme'], rootUris, compilerOptions.fileSystem);
 
+    if (options.wasParsed('filesystem-root')) {
       if (_options['output-dill'] == null) {
         print('When --filesystem-root is specified it is required to specify'
             ' --output-dill option that points to physical file system location'
@@ -310,11 +311,7 @@
       return false;
     }
 
-    // Ensure that Flutter and VM targets are added to targets dictionary.
-    installAdditionalTargets();
-
-    final TargetFlags targetFlags = new TargetFlags(syncAsync: true);
-    compilerOptions.target = getTarget(options['target'], targetFlags);
+    compilerOptions.target = createFrontEndTarget(options['target']);
     if (compilerOptions.target == null) {
       print('Failed to create front-end target ${options['target']}.');
       return false;
@@ -360,7 +357,8 @@
           .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
       final String depfile = options['depfile'];
       if (depfile != null) {
-        await _writeDepfile(component, _kernelBinaryFilename, depfile);
+        await writeDepfile(compilerOptions.fileSystem, component,
+            _kernelBinaryFilename, depfile);
       }
 
       _kernelBinaryFilename = _kernelBinaryFilenameIncremental;
@@ -614,23 +612,8 @@
     _kernelBinaryFilename = _kernelBinaryFilenameFull;
   }
 
-  Uri _getFileOrUri(String fileOrUri) {
-    if (fileOrUri == null) {
-      return null;
-    }
-    if (_options.wasParsed('filesystem-root')) {
-      // This is a hack.
-      // Only expect uri when filesystem-root option is specified. It has to
-      // be uri for filesystem-root use case because mapping is done on
-      // scheme-basis.
-      // This is so that we don't deal with Windows files paths that can not
-      // be processed as uris.
-      return Uri.base.resolve(fileOrUri);
-    }
-    Uri uri = Uri.parse(fileOrUri);
-    if (uri.scheme == 'package') return uri;
-    return Uri.base.resolveUri(new Uri.file(fileOrUri));
-  }
+  Uri _getFileOrUri(String fileOrUri) =>
+      convertFileOrUriArgumentToUri(_fileSystem, fileOrUri);
 
   IncrementalCompiler _createGenerator(Uri initializeFromDillUri) {
     return new IncrementalCompiler(_compilerOptions, _mainSource,
@@ -666,25 +649,6 @@
   void close() {}
 }
 
-String _escapePath(String path) {
-  return path.replaceAll(r'\', r'\\').replaceAll(r' ', r'\ ');
-}
-
-// https://ninja-build.org/manual.html#_depfile
-_writeDepfile(Component component, String output, String depfile) async {
-  final IOSink file = new File(depfile).openWrite();
-  file.write(_escapePath(output));
-  file.write(':');
-  for (Uri dep in component.uriToSource.keys) {
-    // Skip empty or corelib dependencies.
-    if (dep == null || dep.scheme == 'org-dartlang-sdk') continue;
-    file.write(' ');
-    file.write(_escapePath(dep.toFilePath()));
-  }
-  file.write('\n');
-  await file.close();
-}
-
 class _CompileExpressionRequest {
   String expression;
   // Note that FE will reject a compileExpression command by returning a null
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index be0184f..ffb74bf 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -6,6 +6,12 @@
 library vm.kernel_front_end;
 
 import 'dart:async';
+import 'dart:io' show File, IOSink, IOException;
+
+import 'package:args/args.dart' show ArgParser, ArgResults;
+
+import 'package:build_integration/file_system/multi_root.dart'
+    show MultiRootFileSystem, MultiRootFileSystemEntity;
 
 import 'package:front_end/src/api_unstable/vm.dart'
     show
@@ -13,22 +19,31 @@
         CompilerOptions,
         DiagnosticMessage,
         DiagnosticMessageHandler,
+        FileSystem,
+        FileSystemEntity,
         ProcessedOptions,
         Severity,
+        StandardFileSystem,
         getMessageUri,
         kernelForProgram,
         printDiagnosticMessage;
 
 import 'package:kernel/type_environment.dart' show TypeEnvironment;
 import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
-import 'package:kernel/ast.dart' show Component, Field, StaticGet;
+import 'package:kernel/ast.dart'
+    show Component, Field, Library, Reference, StaticGet;
+import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
+import 'package:kernel/binary/limited_ast_to_binary.dart'
+    show LimitedBinaryPrinter;
 import 'package:kernel/core_types.dart' show CoreTypes;
+import 'package:kernel/target/targets.dart' show Target, TargetFlags, getTarget;
 import 'package:kernel/transformations/constants.dart' as constants;
 import 'package:kernel/vm/constants_native_effects.dart' as vm_constants;
 
 import 'bytecode/gen_bytecode.dart' show generateBytecode;
 
 import 'constants_error_reporter.dart' show ForwardConstantEvaluationErrors;
+import 'target/install.dart' show installAdditionalTargets;
 import 'transformations/devirtualization.dart' as devirtualization
     show transformComponent;
 import 'transformations/mixin_deduplication.dart' as mixin_deduplication
@@ -40,6 +55,183 @@
 import 'transformations/obfuscation_prohibitions_annotator.dart'
     as obfuscationProhibitions;
 
+/// Declare options consumed by [runCompiler].
+void declareCompilerOptions(ArgParser args) {
+  args.addOption('platform',
+      help: 'Path to vm_platform_strong.dill file', defaultsTo: null);
+  args.addOption('packages', help: 'Path to .packages file', defaultsTo: null);
+  args.addOption('output',
+      abbr: 'o', help: 'Path to resulting dill file', defaultsTo: null);
+  args.addFlag('aot',
+      help:
+          'Produce kernel file for AOT compilation (enables global transformations).',
+      defaultsTo: false);
+  args.addOption('depfile', help: 'Path to output Ninja depfile');
+  args.addFlag('link-platform',
+      help: 'Include platform into resulting kernel file.', defaultsTo: true);
+  args.addFlag('sync-async',
+      help: 'Start `async` functions synchronously', defaultsTo: true);
+  args.addFlag('embed-sources',
+      help: 'Embed source files in the generated kernel component',
+      defaultsTo: true);
+  args.addMultiOption('filesystem-root',
+      help: 'A base path for the multi-root virtual file system.'
+          ' If multi-root file system is used, the input script should be specified using URI.');
+  args.addOption('filesystem-scheme',
+      help: 'The URI scheme for the multi-root virtual filesystem.');
+  args.addOption('target',
+      help: 'Target model that determines what core libraries are available',
+      allowed: <String>['vm', 'flutter', 'flutter_runner', 'dart_runner'],
+      defaultsTo: 'vm');
+  args.addFlag('tfa',
+      help:
+          'Enable global type flow analysis and related transformations in AOT mode.',
+      defaultsTo: true);
+  args.addMultiOption('define',
+      abbr: 'D',
+      help: 'The values for the environment constants (e.g. -Dkey=value).');
+  args.addFlag('enable-asserts',
+      help: 'Whether asserts will be enabled.', defaultsTo: false);
+  args.addFlag('enable-constant-evaluation',
+      help: 'Whether kernel constant evaluation will be enabled.',
+      defaultsTo: true);
+  args.addFlag('split-output-by-packages',
+      help:
+          'Split resulting kernel file into multiple files (one per package).',
+      defaultsTo: false);
+  args.addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false);
+  args.addFlag('emit-bytecode-source-positions',
+      help: 'Emit source positions in bytecode', defaultsTo: false);
+  args.addFlag('drop-ast',
+      help: 'Drop AST for members with bytecode', defaultsTo: false);
+  args.addFlag('use-future-bytecode-format',
+      help: 'Generate bytecode in the bleeding edge format', defaultsTo: false);
+}
+
+/// Create ArgParser and populate it with options consumed by [runCompiler].
+ArgParser createCompilerArgParser() {
+  final ArgParser argParser = new ArgParser(allowTrailingOptions: true);
+  declareCompilerOptions(argParser);
+  return argParser;
+}
+
+const int successExitCode = 0;
+const int badUsageExitCode = 1;
+const int compileTimeErrorExitCode = 254;
+
+/// Run kernel compiler tool with given [options] and [usage]
+/// and return exit code.
+Future<int> runCompiler(ArgResults options, String usage) async {
+  final String platformKernel = options['platform'];
+
+  if ((options.rest.length != 1) || (platformKernel == null)) {
+    print(usage);
+    return badUsageExitCode;
+  }
+
+  final String input = options.rest.single;
+  final String outputFileName = options['output'] ?? "$input.dill";
+  final String packages = options['packages'];
+  final String targetName = options['target'];
+  final String fileSystemScheme = options['filesystem-scheme'];
+  final String depfile = options['depfile'];
+  final List<String> fileSystemRoots = options['filesystem-root'];
+  final bool aot = options['aot'];
+  final bool tfa = options['tfa'];
+  final bool linkPlatform = options['link-platform'];
+  final bool genBytecode = options['gen-bytecode'];
+  final bool emitBytecodeSourcePositions =
+      options['emit-bytecode-source-positions'];
+  final bool dropAST = options['drop-ast'];
+  final bool useFutureBytecodeFormat = options['use-future-bytecode-format'];
+  final bool enableAsserts = options['enable-asserts'];
+  final bool enableConstantEvaluation = options['enable-constant-evaluation'];
+  final bool splitOutputByPackages = options['split-output-by-packages'];
+  final Map<String, String> environmentDefines = {};
+
+  if (!parseCommandLineDefines(options['define'], environmentDefines, usage)) {
+    return badUsageExitCode;
+  }
+
+  final target = createFrontEndTarget(targetName);
+  if (target == null) {
+    print('Failed to create front-end target $targetName.');
+    return badUsageExitCode;
+  }
+
+  final fileSystem =
+      createFrontEndFileSystem(fileSystemScheme, fileSystemRoots);
+
+  final Uri packagesUri =
+      packages != null ? Uri.base.resolveUri(new Uri.file(packages)) : null;
+
+  final platformKernelUri = Uri.base.resolveUri(new Uri.file(platformKernel));
+  final List<Uri> linkedDependencies = <Uri>[];
+  if (aot || linkPlatform) {
+    linkedDependencies.add(platformKernelUri);
+  }
+
+  Uri mainUri = convertFileOrUriArgumentToUri(fileSystem, input);
+  if (packagesUri != null) {
+    mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri);
+  }
+
+  final errorPrinter = new ErrorPrinter();
+  final errorDetector = new ErrorDetector(previousErrorHandler: errorPrinter);
+
+  final CompilerOptions compilerOptions = new CompilerOptions()
+    ..sdkSummary = platformKernelUri
+    ..target = target
+    ..fileSystem = fileSystem
+    ..linkedDependencies = linkedDependencies
+    ..packagesFileUri = packagesUri
+    ..onDiagnostic = (DiagnosticMessage m) {
+      errorDetector(m);
+    }
+    ..embedSourceText = options['embed-sources'];
+
+  final component = await compileToKernel(mainUri, compilerOptions,
+      aot: aot,
+      useGlobalTypeFlowAnalysis: tfa,
+      environmentDefines: environmentDefines,
+      genBytecode: genBytecode,
+      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
+      dropAST: dropAST && !splitOutputByPackages,
+      useFutureBytecodeFormat: useFutureBytecodeFormat,
+      enableAsserts: enableAsserts,
+      enableConstantEvaluation: enableConstantEvaluation);
+
+  errorPrinter.printCompilationMessages();
+
+  if (errorDetector.hasCompilationErrors || (component == null)) {
+    return compileTimeErrorExitCode;
+  }
+
+  final IOSink sink = new File(outputFileName).openWrite();
+  final BinaryPrinter printer = new BinaryPrinter(sink);
+  printer.writeComponentFile(component);
+  await sink.close();
+
+  if (depfile != null) {
+    await writeDepfile(fileSystem, component, outputFileName, depfile);
+  }
+
+  if (splitOutputByPackages) {
+    await writeOutputSplitByPackages(
+      mainUri,
+      compilerOptions,
+      component,
+      outputFileName,
+      environmentDefines: environmentDefines,
+      genBytecode: genBytecode,
+      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
+      dropAST: dropAST,
+    );
+  }
+
+  return successExitCode;
+}
+
 /// Generates a kernel representation of the program whose main library is in
 /// the given [source]. Intended for whole program (non-modular) compilation.
 ///
@@ -235,7 +427,7 @@
     previousErrorHandler?.call(message);
   }
 
-  void printCompilationMessages(Uri baseUri) {
+  void printCompilationMessages() {
     final sortedUris = compilationMessages.keys.toList()
       ..sort((a, b) => '$a'.compareTo('$b'));
     for (final Uri sourceUri in sortedUris) {
@@ -264,3 +456,205 @@
   }
   return true;
 }
+
+/// Create front-end target with given name.
+Target createFrontEndTarget(String targetName) {
+  // Make sure VM-specific targets are available.
+  installAdditionalTargets();
+
+  final TargetFlags targetFlags = new TargetFlags(syncAsync: true);
+  return getTarget(targetName, targetFlags);
+}
+
+/// Create a front-end file system.
+/// If requested, create a virtual mutli-root file system.
+FileSystem createFrontEndFileSystem(
+    String multiRootFileSystemScheme, List<String> multiRootFileSystemRoots) {
+  FileSystem fileSystem = StandardFileSystem.instance;
+  if (multiRootFileSystemRoots != null &&
+      multiRootFileSystemRoots.isNotEmpty &&
+      multiRootFileSystemScheme != null) {
+    final rootUris = <Uri>[];
+    for (String root in multiRootFileSystemRoots) {
+      rootUris.add(Uri.base.resolveUri(new Uri.file(root)));
+    }
+    fileSystem = new MultiRootFileSystem(
+        multiRootFileSystemScheme, rootUris, fileSystem);
+  }
+  return fileSystem;
+}
+
+/// Convert command line argument [input] which is a file or URI to an
+/// absolute URI.
+///
+/// If virtual multi-root file system is used, or [input] can be parsed to a
+/// URI with 'package' scheme, then [input] is interpreted as URI.
+/// Otherwise [input] is interpreted as a file path.
+Uri convertFileOrUriArgumentToUri(FileSystem fileSystem, String input) {
+  if (input == null) {
+    return null;
+  }
+  // If using virtual multi-root file system, input source argument should be
+  // specified as URI.
+  if (fileSystem is MultiRootFileSystem) {
+    return Uri.base.resolve(input);
+  }
+  try {
+    Uri uri = Uri.parse(input);
+    if (uri.scheme == 'package') {
+      return uri;
+    }
+  } on FormatException {
+    // Ignore, treat input argument as file path.
+  }
+  return Uri.base.resolveUri(new Uri.file(input));
+}
+
+/// Convert a URI which may use virtual file system schema to a real file URI.
+Future<Uri> asFileUri(FileSystem fileSystem, Uri uri) async {
+  FileSystemEntity fse = fileSystem.entityForUri(uri);
+  if (fse is MultiRootFileSystemEntity) {
+    fse = await (fse as MultiRootFileSystemEntity).delegate;
+  }
+  return fse.uri;
+}
+
+/// Convert URI to a package URI if it is inside one of the packages.
+Future<Uri> convertToPackageUri(
+    FileSystem fileSystem, Uri uri, Uri packagesUri) async {
+  // Convert virtual URI to a real file URI.
+  String uriString = (await asFileUri(fileSystem, uri)).toString();
+  List<String> packages;
+  try {
+    packages = await new File(packagesUri.toFilePath()).readAsLines();
+  } on IOException {
+    // Can't read packages file - silently give up.
+    return uri;
+  }
+  // file:///a/b/x/y/main.dart -> package:x.y/main.dart
+  for (var line in packages) {
+    final colon = line.indexOf(':');
+    if (colon == -1) {
+      continue;
+    }
+    final packageName = line.substring(0, colon);
+    final packagePath = line.substring(colon + 1);
+    if (uriString.startsWith(packagePath)) {
+      return Uri.parse(
+          'package:$packageName/${uriString.substring(packagePath.length)}');
+    }
+  }
+  return uri;
+}
+
+/// Write a separate kernel binary for each package. The name of the
+/// output kernel binary is '[outputFileName]-$package.dilp'.
+/// The list of package names is written into a file '[outputFileName]-packages'.
+///
+/// Generates bytecode for each package if requested.
+Future writeOutputSplitByPackages(
+  Uri source,
+  CompilerOptions compilerOptions,
+  Component component,
+  String outputFileName, {
+  Map<String, String> environmentDefines,
+  bool genBytecode: false,
+  bool emitBytecodeSourcePositions: false,
+  bool dropAST: false,
+  bool useFutureBytecodeFormat: false,
+}) async {
+  // Package sharing: make the encoding not depend on the order in which parts
+  // of a package are loaded.
+  component.libraries.sort((Library a, Library b) {
+    return a.importUri.toString().compareTo(b.importUri.toString());
+  });
+  component.computeCanonicalNames();
+  for (Library lib in component.libraries) {
+    lib.additionalExports.sort((Reference a, Reference b) {
+      return a.canonicalName.toString().compareTo(b.canonicalName.toString());
+    });
+  }
+
+  final packagesSet = new Set<String>();
+  for (Library lib in component.libraries) {
+    packagesSet.add(packageFor(lib));
+  }
+  packagesSet.remove('main');
+  packagesSet.remove(null);
+
+  final List<String> packages = packagesSet.toList();
+  packages.add('main'); // Make sure main package is last.
+
+  await runWithFrontEndCompilerContext(source, compilerOptions, component,
+      () async {
+    for (String package in packages) {
+      final String filename = '$outputFileName-$package.dilp';
+      final IOSink sink = new File(filename).openWrite();
+
+      final main = component.mainMethod;
+      if (package != 'main') {
+        component.mainMethod = null;
+      }
+
+      if (genBytecode) {
+        final List<Library> libraries = component.libraries
+            .where((lib) => packageFor(lib) == package)
+            .toList();
+        generateBytecode(component,
+            libraries: libraries,
+            dropAST: dropAST,
+            emitSourcePositions: emitBytecodeSourcePositions,
+            useFutureBytecodeFormat: useFutureBytecodeFormat,
+            environmentDefines: environmentDefines);
+      }
+
+      final BinaryPrinter printer = new LimitedBinaryPrinter(sink,
+          (lib) => packageFor(lib) == package, false /* excludeUriToSource */);
+      printer.writeComponentFile(component);
+      component.mainMethod = main;
+
+      await sink.close();
+    }
+  });
+
+  final IOSink packagesList = new File('$outputFileName-packages').openWrite();
+  for (String package in packages) {
+    packagesList.writeln(package);
+  }
+  await packagesList.close();
+}
+
+String packageFor(Library lib) {
+  // Core libraries are not written into any package kernel binaries.
+  if (lib.isExternal) return null;
+
+  // Packages are written into their own kernel binaries.
+  Uri uri = lib.importUri;
+  if (uri.scheme == 'package') return uri.pathSegments.first;
+
+  // Everything else (e.g., file: or data: imports) is lumped into the main
+  // kernel binary.
+  return 'main';
+}
+
+String _escapePath(String path) {
+  return path.replaceAll('\\', '\\\\').replaceAll(' ', '\\ ');
+}
+
+/// Create ninja dependencies file, as described in
+/// https://ninja-build.org/manual.html#_depfile
+Future<void> writeDepfile(FileSystem fileSystem, Component component,
+    String output, String depfile) async {
+  final IOSink file = new File(depfile).openWrite();
+  file.write(_escapePath(output));
+  file.write(':');
+  for (Uri dep in component.uriToSource.keys) {
+    // Skip empty or corelib dependencies.
+    if (dep == null || dep.scheme == 'org-dartlang-sdk') continue;
+    Uri uri = await asFileUri(fileSystem, dep);
+    file.write(' ');
+    file.write(_escapePath(uri.toFilePath()));
+  }
+  file.write('\n');
+  await file.close();
+}
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index bd548ba..699874f 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -150,6 +150,7 @@
       "tests/standalone/",
       "tests/standalone_2/",
       "pkg/async_helper/",
+      "pkg/build_integration/",
       "pkg/dart_internal/",
       "pkg/expect/",
       "pkg/front_end/",