[flutter_runner] Optionally create the framework snapshot from a prebuilt Flutter program.

Add required analysis_options.yaml and silence the resulting bad advice.

Change-Id: Ia793a0dab06aa99c6f0a5f8a5b2263877e5f8c61
diff --git a/runtime/flutter_runner/BUILD.gn b/runtime/flutter_runner/BUILD.gn
index 83e837d..34ff1bb 100644
--- a/runtime/flutter_runner/BUILD.gn
+++ b/runtime/flutter_runner/BUILD.gn
@@ -4,10 +4,12 @@
 
 assert(is_fuchsia)
 
+import("//build/package.gni")
 import("//build/test/test_package.gni")
 import("//build/testing/environments.gni")
 import("//build/vulkan/config.gni")
 import("//garnet/bin/ui/scenic/config.gni")
+import("//topaz/runtime/flutter_runner/prebuilt_framework.gni")
 import("$flutter_root/shell/gpu/gpu.gni")
 
 shell_gpu_configuration("fuchsia_gpu_configuration") {
@@ -166,9 +168,11 @@
       "-fno-omit-frame-pointer",
     ]
 
-    # This flag is needed so that the call to dladdr() in Dart's native symbol
-    # resolver can report good symbol information for the CPU profiler.
-    ldflags = [ "-rdynamic" ]
+    if (!invoker.product) {
+      # This flag is needed so that the call to dladdr() in Dart's native symbol
+      # resolver can report good symbol information for the CPU profiler.
+      ldflags = [ "-rdynamic" ]
+    }
   }
 }
 
@@ -210,8 +214,6 @@
   ]
 }
 
-import("//build/package.gni")
-
 observatory_target =
     "//third_party/dart/runtime/observatory:observatory_archive"
 observatory_archive_dir = get_label_info(observatory_target, "target_gen_dir")
@@ -276,7 +278,6 @@
 
     deps = [
       ":jit${product_suffix}",
-      "//topaz/runtime/flutter_runner/kernel:framework_shim_kernel",
       snapshot_framework_label,
       snapshot_label,
     ]
@@ -329,16 +330,32 @@
         dest = "framework_isolate_core_snapshot_instructions.bin"
       },
       {
-        path = rebase_path(
-                "$snapshot_gen_dir/framework_shim.dilpmanifest.frameworkversion")
-        dest = "runner.frameworkversion"
-      },
-      {
         path = rebase_path("//third_party/icu/common/icudtl.dat")
         dest = "icudtl.dat"
       },
     ]
 
+    if (prebuilt_framework_path == "") {
+      deps += [ "//topaz/runtime/flutter_runner/kernel:framework_shim_kernel" ]
+      resources += [
+        {
+          path = rebase_path(
+                  "$snapshot_gen_dir/framework_shim.dilpmanifest.frameworkversion")
+          dest = "runner.frameworkversion"
+        },
+      ]
+    } else {
+      deps +=
+          [ "//topaz/runtime/flutter_runner/kernel:extract_prebuilt_framework" ]
+      resources += [
+        {
+          path = rebase_path(
+                  "$snapshot_gen_dir/data/$prebuilt_framework_name/app.frameworkversion")
+          dest = "runner.frameworkversion"
+        },
+      ]
+    }
+
     if (!product) {
       resources += [
         {
diff --git a/runtime/flutter_runner/kernel/BUILD.gn b/runtime/flutter_runner/kernel/BUILD.gn
index b040b32..671a8e3 100644
--- a/runtime/flutter_runner/kernel/BUILD.gn
+++ b/runtime/flutter_runner/kernel/BUILD.gn
@@ -3,8 +3,10 @@
 # found in the LICENSE file.
 
 import("//build/dart/dart_tool.gni")
+import("//build/host.gni")
 import("//third_party/dart/utils/compile_platform.gni")
 import("//topaz/runtime/dart/dart_kernel.gni")
+import("//topaz/runtime/flutter_runner/prebuilt_framework.gni")
 
 compile_platform("kernel_platform_files") {
   single_root_scheme = "org-dartlang-sdk"
@@ -29,7 +31,8 @@
 
 dart_kernel("framework_shim") {
   platform_name = "flutter_runner"
-  platform_deps = [ "//topaz/runtime/flutter_runner/kernel:kernel_platform_files" ]
+  platform_deps =
+      [ "//topaz/runtime/flutter_runner/kernel:kernel_platform_files" ]
   platform_path = "$root_out_dir/flutter_runner_patched_sdk"
   disable_analysis = true
   gen_bytecode = true
@@ -59,23 +62,21 @@
   action(target_name) {
     deps = gen_snapshot_deps + [ ":kernel_platform_files" ]
 
-    platform_dill = "$root_out_dir/flutter_runner_patched_sdk/platform_strong.dill"
+    platform_dill =
+        "$root_out_dir/flutter_runner_patched_sdk/platform_strong.dill"
     compilation_trace = "//topaz/runtime/flutter_runner/compilation_trace.txt"
     inputs = [
       platform_dill,
       compilation_trace,
     ]
 
-    vm_snapshot_data =
-        "$target_gen_dir/vm_isolate_snapshot${suffix}.bin"
+    vm_snapshot_data = "$target_gen_dir/vm_isolate_snapshot${suffix}.bin"
     vm_snapshot_instructions =
         "$target_gen_dir/vm_snapshot_instructions${suffix}.bin"
-    isolate_snapshot_data =
-        "$target_gen_dir/isolate_snapshot${suffix}.bin"
+    isolate_snapshot_data = "$target_gen_dir/isolate_snapshot${suffix}.bin"
     isolate_snapshot_instructions =
         "$target_gen_dir/isolate_snapshot_instructions${suffix}.bin"
-    snapshot_profile =
-        "$target_gen_dir/snapshot_profile${suffix}.json"
+    snapshot_profile = "$target_gen_dir/snapshot_profile${suffix}.json"
     outputs = [
       vm_snapshot_data,
       vm_snapshot_instructions,
@@ -115,9 +116,19 @@
     args += [ rebase_path(platform_dill) ]
 
     if (invoker.framework) {
-      deps += [ ":framework_shim_kernel" ]
-      inputs += [ "$target_gen_dir/framework_shim_kernel.dil" ]
-      args += [ rebase_path("$target_gen_dir/framework_shim_kernel.dil") ]
+      if (prebuilt_framework_path == "") {
+        deps += [ ":framework_shim_kernel" ]
+        inputs += [ "$target_gen_dir/framework_shim_kernel.dil" ]
+        args += [ rebase_path("$target_gen_dir/framework_shim_kernel.dil") ]
+      } else {
+        deps += [ ":extract_prebuilt_framework" ]
+        foreach(package, framework_packages) {
+          args += [ rebase_path(
+                  "$target_gen_dir/data/$prebuilt_framework_name/$package.dilp") ]
+          inputs +=
+              [ "$target_gen_dir/data/$prebuilt_framework_name/$package.dilp" ]
+        }
+      }
     }
   }
 }
@@ -141,3 +152,52 @@
   product = true
   framework = true
 }
+
+dart_tool("extract_far") {
+  main_dart = "extract_far.dart"
+
+  force_prebuilt_dart = true
+
+  source_dir = "."
+  sources = [
+    "extract_far.dart",
+  ]
+
+  deps = [
+    "//third_party/dart-pkg/pub/args",
+  ]
+}
+
+if (prebuilt_framework_path != "") {
+  action("extract_prebuilt_framework") {
+    deps = [
+      ":extract_far",
+      "//garnet/bin/far:host",
+    ]
+
+    inputs = [
+      prebuilt_framework_path,
+    ]
+
+    script = get_label_info(":extract_far", "root_out_dir") +
+             "/dart-tools/extract_far"
+    args = [
+      "--far-tool",
+      rebase_path("$host_tools_dir/far"),
+      "--archive",
+      rebase_path(prebuilt_framework_path),
+      "--out-dir",
+      rebase_path(target_gen_dir),
+    ]
+
+    outputs = []
+    foreach(package, framework_packages) {
+      args += [ "data/$prebuilt_framework_name/$package.dilp" ]
+      outputs +=
+          [ "$target_gen_dir/data/$prebuilt_framework_name/$package.dilp" ]
+    }
+    args += [ "data/$prebuilt_framework_name/app.frameworkversion" ]
+    outputs +=
+        [ "$target_gen_dir/data/$prebuilt_framework_name/app.frameworkversion" ]
+  }
+}
diff --git a/runtime/flutter_runner/kernel/analysis_options.yaml b/runtime/flutter_runner/kernel/analysis_options.yaml
new file mode 100644
index 0000000..ef6b539
--- /dev/null
+++ b/runtime/flutter_runner/kernel/analysis_options.yaml
@@ -0,0 +1,12 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include: ../../../tools/analysis_options.yaml
+analyzer:
+  errors:
+    avoid_catches_without_on_clauses: ignore
+    cascade_invocations: ignore
+    only_throw_errors: ignore # adds noise to error messages
+    prefer_foreach: ignore # for-in is preferable to closurization
+    prefer_single_quotes: ignore
diff --git a/runtime/flutter_runner/kernel/extract_far.dart b/runtime/flutter_runner/kernel/extract_far.dart
new file mode 100644
index 0000000..e9aa2b2
--- /dev/null
+++ b/runtime/flutter_runner/kernel/extract_far.dart
@@ -0,0 +1,83 @@
+// Copyright 2019 The Fuchsia 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:io";
+
+import "package:args/args.dart";
+
+Future<void> main(List<String> args) async {
+  final parser = new ArgParser();
+  parser.addOption("far-tool", help: "Path to `far` tool");
+  parser.addOption("archive", help: "Path to the far archive to extract from");
+  parser.addOption("out-dir", help: "Path to output directory");
+  final usage = """
+Usage: extract_far.dart [options] {paths}
+
+Options:
+${parser.usage};
+""";
+
+  ArgResults options;
+  try {
+    options = parser.parse(args);
+    if (options["far-tool"] == null) {
+      throw "Must specify --far-tool";
+    }
+    if (options["archive"] == null) {
+      throw "Must specify --archive";
+    }
+    if (options["out-dir"] == null) {
+      throw "Must specify --out-dir";
+    }
+  } catch (e) {
+    print("ERROR: $e\n");
+    print(usage);
+    exitCode = 1;
+    return;
+  }
+
+  await run(options);
+}
+
+Future<void> run(ArgResults options) async {
+  final far = options["far-tool"];
+
+  Future<void> extract(String archive, String file, String output) async {
+    await new File(output).parent.create(recursive: true);
+    final args = ["extract-file", "--archive=$archive", "--file=$file", "--output=$output"];
+    final result = await Process.run(far, args);
+    if (result.exitCode != 0) {
+      print(result.stdout);
+      print(result.stderr);
+      throw "Command failed: $far $args";
+    }
+  }
+
+  final outerArchive = options["archive"];
+  final outDir = options["out-dir"];
+
+  final innerArchive = "$outDir/meta.far";
+  await extract(outerArchive, "meta.far", innerArchive);
+
+  final manifest = "$outDir/meta/contents";
+  await extract(innerArchive, "meta/contents", manifest);
+
+  final blobNames = <String, String>{};
+  for (final line in await new File(manifest).readAsLines()) {
+    final pivot = line.lastIndexOf("=");
+    blobNames[line.substring(0, pivot)] = line.substring(pivot + 1, line.length);
+  }
+
+  for (final path in options.rest) {
+    final blobName = blobNames[path];
+    if (blobName == null) {
+      print("Archive contents: ");
+      for (final key in blobNames.keys) {
+        print(key);
+      }
+      throw "$outerArchive does not contain $path";
+    }
+    await extract(outerArchive, blobName, "$outDir/$path");
+  }
+}
diff --git a/runtime/flutter_runner/prebuilt_framework.gni b/runtime/flutter_runner/prebuilt_framework.gni
new file mode 100644
index 0000000..edc389c
--- /dev/null
+++ b/runtime/flutter_runner/prebuilt_framework.gni
@@ -0,0 +1,15 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  prebuilt_framework_path = ""
+  prebuilt_framework_name = ""
+  framework_packages = [
+    "collection",
+    "flutter",
+    "meta",
+    "typed_data",
+    "vector_math",
+  ]
+}