[standalone] Record dependencies in DFE and include them in snapshot depfiles.

Bug: https://github.com/dart-lang/sdk/issues/33390
Change-Id: Ib1ce6078ccff1b8727c26a8ff7b24e9fb99fd086
Reviewed-on: https://dart-review.googlesource.com/60020
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart
index 8bd4fe4..20ed01c 100644
--- a/pkg/vm/bin/kernel_service.dart
+++ b/pkg/vm/bin/kernel_service.dart
@@ -21,6 +21,7 @@
 library runtime.tools.kernel_service;
 
 import 'dart:async' show Future, ZoneSpecification, runZoned;
+import 'dart:convert' show utf8;
 import 'dart:io' show Platform, stderr hide FileSystemEntity;
 import 'dart:isolate';
 import 'dart:typed_data' show Uint8List;
@@ -38,7 +39,7 @@
 import 'package:kernel/target/vm.dart' show VmTarget;
 import 'package:vm/incremental_compiler.dart';
 
-const bool verbose = const bool.fromEnvironment('DFE_VERBOSE');
+final bool verbose = new bool.fromEnvironment('DFE_VERBOSE');
 const String platformKernelFile = 'virtual_platform_kernel.dill';
 
 // NOTE: Any changes to these tags need to be reflected in kernel_isolate.cc
@@ -51,11 +52,13 @@
 //   3 - APP JIT snapshot training run for kernel_service.
 //   4 - Compile an individual expression in some context (for debugging
 //       purposes).
+//   5 - List program dependencies (for creating depfiles)
 const int kCompileTag = 0;
 const int kUpdateSourcesTag = 1;
 const int kAcceptTag = 2;
 const int kTrainTag = 3;
 const int kCompileExpressionTag = 4;
+const int kListDependenciesTag = 5;
 
 bool allowDartInternalImport = false;
 
@@ -182,8 +185,10 @@
   }
 }
 
+// TODO(33428): This state is leaked on isolate shutdown.
 final Map<int, IncrementalCompilerWrapper> isolateCompilers =
     new Map<int, IncrementalCompilerWrapper>();
+final Map<int, List<Uri>> isolateDependencies = new Map<int, List<Uri>>();
 
 IncrementalCompilerWrapper lookupIncrementalCompiler(int isolateId) {
   return isolateCompilers[isolateId];
@@ -294,6 +299,54 @@
   port.send(result.toResponse());
 }
 
+void _recordDependencies(
+    int isolateId, Component component, String packageConfig) {
+  final dependencies = isolateDependencies[isolateId] ??= new List<Uri>();
+
+  for (var lib in component.libraries) {
+    if (lib.importUri.scheme == "dart") continue;
+
+    dependencies.add(lib.fileUri);
+    for (var part in lib.parts) {
+      final fileUri = lib.fileUri.resolve(part.partUri);
+      if (fileUri.scheme != "" && fileUri.scheme != "file") {
+        // E.g. part 'package:foo/foo.dart';
+        // Maybe the front end should resolve this?
+        continue;
+      }
+      dependencies.add(fileUri);
+    }
+  }
+
+  if (packageConfig != null) {
+    dependencies.add(Uri.parse(packageConfig));
+  }
+}
+
+String _escapeDependency(Uri uri) {
+  return uri.toFilePath().replaceAll("\\", "\\\\").replaceAll(" ", "\\ ");
+}
+
+List<int> _serializeDependencies(List<Uri> uris) {
+  return utf8.encode(uris.map(_escapeDependency).join(" "));
+}
+
+Future _processListDependenciesRequest(request) async {
+  final SendPort port = request[1];
+  final int isolateId = request[6];
+
+  final List<Uri> dependencies = isolateDependencies[isolateId] ?? <Uri>[];
+
+  CompilationResult result;
+  try {
+    result = new CompilationResult.ok(_serializeDependencies(dependencies));
+  } catch (error, stack) {
+    result = new CompilationResult.crash(error, stack);
+  }
+
+  port.send(result.toResponse());
+}
+
 Future _processLoadRequest(request) async {
   if (verbose) print("DFE: request: $request");
 
@@ -304,6 +357,11 @@
     return;
   }
 
+  if (tag == kListDependenciesTag) {
+    await _processListDependenciesRequest(request);
+    return;
+  }
+
   final SendPort port = request[1];
   final String inputFileUri = request[2];
   final Uri script =
@@ -387,6 +445,7 @@
     }
 
     Component component = await compiler.compile(script);
+    _recordDependencies(isolateId, component, packageConfig);
 
     if (compiler.errors.isNotEmpty) {
       result = new CompilationResult.errors(compiler.errors,
diff --git a/runtime/bin/gen_snapshot.cc b/runtime/bin/gen_snapshot.cc
index 67d681b..4bddd36a 100644
--- a/runtime/bin/gen_snapshot.cc
+++ b/runtime/bin/gen_snapshot.cc
@@ -1528,12 +1528,19 @@
   if (app_script_name != NULL) {
     dfe.ReadScript(app_script_name, &kernel_buffer, &kernel_buffer_size);
   }
-  if (kernel_buffer != NULL && !SnapshotKindAllowedFromKernel()) {
-    // TODO(sivachandra): Add check for the kernel program format (incremental
-    // vs batch).
-    Log::PrintErr(
-        "Can only generate core or aot snapshots from a kernel file.\n");
-    return kErrorExitCode;
+  if (kernel_buffer != NULL) {
+    if (!SnapshotKindAllowedFromKernel()) {
+      // TODO(sivachandra): Add check for the kernel program format (incremental
+      // vs batch).
+      Log::PrintErr(
+          "Can only generate core or aot snapshots from a kernel file.\n");
+      return kErrorExitCode;
+    }
+    if ((dependencies_filename != NULL) || print_dependencies ||
+        dependencies_only) {
+      Log::PrintErr("Depfiles are not supported in Dart 2.\n");
+      return kErrorExitCode;
+    }
   }
 
   if (!Platform::Initialize()) {
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 01e11b7..b3c71de 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -193,6 +193,17 @@
     success &= file->Print("%s ", dep);
     free(dep);
   }
+  if (Options::preview_dart_2()) {
+    Dart_KernelCompilationResult result = Dart_KernelListDependencies();
+    if (result.status != Dart_KernelCompilationStatus_Ok) {
+      ErrorExit(
+          kErrorExitCode,
+          "Error: Failed to fetch dependencies from kernel service: %s\n\n",
+          result.error);
+    }
+    success &= file->WriteFully(result.kernel, result.kernel_size);
+    free(result.kernel);
+  }
   success &= file->Print("\n");
   if (!success) {
     ErrorExit(kErrorExitCode, "Error: Unable to write snapshot depfile: %s\n\n",
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index b5bdd1a..8fc3f17 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -3199,6 +3199,8 @@
  *
  */
 
+// TODO(33433): Remove kernel service from the embedding API.
+
 typedef enum {
   Dart_KernelCompilationStatus_Unknown = -1,
   Dart_KernelCompilationStatus_Ok = 0,
@@ -3237,6 +3239,8 @@
                             bool incremental_compile,
                             const char* package_config);
 
+DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies();
+
 #define DART_KERNEL_ISOLATE_NAME "kernel-service"
 
 /*
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 201b5b9..b51735f 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -5905,7 +5905,6 @@
 #if defined(DART_PRECOMPILED_RUNTIME)
   result.status = Dart_KernelCompilationStatus_Unknown;
   result.error = strdup("Dart_CompileToKernel is unsupported.");
-  return result;
 #else
   result = KernelIsolate::CompileToKernel(script_uri, platform_kernel,
                                           platform_kernel_size, 0, NULL,
@@ -5918,8 +5917,8 @@
           " compilation results.");
     }
   }
-  return result;
 #endif
+  return result;
 }
 
 DART_EXPORT Dart_KernelCompilationResult
@@ -5934,7 +5933,6 @@
 #if defined(DART_PRECOMPILED_RUNTIME)
   result.status = Dart_KernelCompilationStatus_Unknown;
   result.error = strdup("Dart_CompileSourcesToKernel is unsupported.");
-  return result;
 #else
   result = KernelIsolate::CompileToKernel(
       script_uri, platform_kernel, platform_kernel_size, source_files_count,
@@ -5947,9 +5945,19 @@
           " compilation results.");
     }
   }
-  return result;
-
 #endif
+  return result;
+}
+
+DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies() {
+  Dart_KernelCompilationResult result;
+#if defined(DART_PRECOMPILED_RUNTIME)
+  result.status = Dart_KernelCompilationStatus_Unknown;
+  result.error = strdup("Dart_KernelListDependencies is unsupported.");
+#else
+  result = KernelIsolate::ListDependencies();
+#endif
+  return result;
 }
 
 // --- Service support ---
diff --git a/runtime/vm/kernel_isolate.cc b/runtime/vm/kernel_isolate.cc
index f0b47d5..64b6982 100644
--- a/runtime/vm/kernel_isolate.cc
+++ b/runtime/vm/kernel_isolate.cc
@@ -53,6 +53,7 @@
 const int KernelIsolate::kAcceptTag = 2;
 const int KernelIsolate::kTrainTag = 3;
 const int KernelIsolate::kCompileExpressionTag = 4;
+const int KernelIsolate::kListDependenciesTag = 5;
 
 Dart_IsolateCreateCallback KernelIsolate::create_callback_ = NULL;
 Monitor* KernelIsolate::monitor_ = new Monitor();
@@ -716,6 +717,20 @@
                                         incremental_compile, package_config);
 }
 
+Dart_KernelCompilationResult KernelIsolate::ListDependencies() {
+  Dart_Port kernel_port = WaitForKernelPort();
+  if (kernel_port == ILLEGAL_PORT) {
+    Dart_KernelCompilationResult result;
+    result.status = Dart_KernelCompilationStatus_Unknown;
+    result.error = strdup("Error while initializing Kernel isolate");
+    return result;
+  }
+
+  KernelCompilationRequest request;
+  return request.SendAndWaitForResponse(kListDependenciesTag, kernel_port, NULL,
+                                        NULL, 0, 0, NULL, false, NULL);
+}
+
 Dart_KernelCompilationResult KernelIsolate::AcceptCompilation() {
   // This must be the main script to be loaded. Wait for Kernel isolate
   // to finish initialization.
diff --git a/runtime/vm/kernel_isolate.h b/runtime/vm/kernel_isolate.h
index 2e45b52..d888630 100644
--- a/runtime/vm/kernel_isolate.h
+++ b/runtime/vm/kernel_isolate.h
@@ -16,6 +16,8 @@
 
 namespace dart {
 
+// TODO(33433): The kernel service does not belong in the VM.
+
 class KernelIsolate : public AllStatic {
  public:
   static const char* kName;
@@ -24,6 +26,7 @@
   static const int kAcceptTag;
   static const int kTrainTag;
   static const int kCompileExpressionTag;
+  static const int kListDependenciesTag;
 
   static void Run();
 
@@ -56,6 +59,8 @@
       const char* klass,
       bool is_static);
 
+  static Dart_KernelCompilationResult ListDependencies();
+
  protected:
   static Monitor* monitor_;
   static Dart_IsolateCreateCallback create_callback_;