resources:  optionally link them into our binary

To enable, set skia_embed_resources=true in args.gn.

Also add *-EmbededResouces bots.

Change-Id: Ia69b26e926a3ad4676a4fa021894432ea2104538
Reviewed-on: https://skia-review.googlesource.com/82626
Commit-Queue: Hal Canary <halcanary@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index f4740cf..c9e77bd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -30,6 +30,7 @@
   skia_use_zlib = true
   skia_use_metal = false
   skia_use_libheif = is_skia_dev_build
+  skia_embed_resources = false
 
   skia_android_serial = ""
   skia_enable_discrete_gpu = true
@@ -903,6 +904,17 @@
       include_dirs += [ "tools/gpu/vk" ]
     }
   }
+  action("binary_resources.cpp") {
+    script = "gn/generate_binary_asset.py"
+    args = [
+      rebase_path("resources"),
+      "gResources",
+      rebase_path("$target_gen_dir/binary_resources.cpp", root_build_dir),
+    ]
+    outputs = [
+      "$target_gen_dir/binary_resources.cpp",
+    ]
+  }
   action("skia.h") {
     public_configs = [ ":skia.h_config" ]
     skia_h = "$target_gen_dir/skia.h"
@@ -1251,6 +1263,11 @@
     public_deps = [
       "//third_party/jsoncpp",
     ]
+    if (skia_embed_resources) {
+      defines = [ "SK_EMBED_RESOURCES" ]
+      sources += [ "$target_gen_dir/binary_resources.cpp" ]
+      deps += [ ":binary_resources.cpp" ]
+    }
   }
 
   import("gn/gm.gni")
diff --git a/gn/generate_binary_asset.py b/gn/generate_binary_asset.py
new file mode 100755
index 0000000..671231b
--- /dev/null
+++ b/gn/generate_binary_asset.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python2
+# Copyright 2017 Google Inc.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+
+def get_resources(rdir):
+  for root, _, files in os.walk(rdir):
+    for filepath in files:
+      fullpath = os.path.join(root, filepath)
+      if os.path.isfile(fullpath):
+        yield os.path.relpath(fullpath, rdir)
+
+def main(resource_dir, array_name, filename):
+  with open(filename, 'w') as o:
+    o.write('//generated file\n#include "BinaryAsset.h"\n\n');
+    names = []
+    for n in sorted(get_resources(resource_dir)):
+      o.write('static const unsigned char x%d[] = {\n' % len(names))
+      with open(os.path.join(resource_dir, n), 'rb')  as f:
+        while True:
+          buf = f.read(20)
+          if len(buf) == 0:
+            break
+          o.write(''.join('%d,' % ord(x) for x in buf) + '\n')
+      o.write('};\n')
+      names.append(n)
+    o.write('\nBinaryAsset %s[] = {\n' % array_name)
+    for i, n in enumerate(names):
+      o.write('    {"%s", x%d, sizeof(x%d)},\n' % (n, i, i))
+    o.write('    {nullptr, nullptr, 0}\n};\n')
+
+if __name__ == '__main__':
+  if len(sys.argv) < 4:
+    msg = 'usage:\n  %s SOURCE_DIRECTORY ARRAY_IDENTIFIER OUTPUT_PATH.cpp\n\n'
+    sys.stderr.write(msg % sys.argv[0])
+    exit(1)
+  main(*sys.argv[1:4])
+
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index 44bc4ef..f2d79e0 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -42,8 +42,10 @@
   "Build-Debian9-GCC-x86-Debug",
   "Build-Debian9-GCC-x86-Release",
   "Build-Debian9-GCC-x86_64-Debug",
+  "Build-Debian9-GCC-x86_64-Debug-EmbededResouces",
   "Build-Debian9-GCC-x86_64-Debug-NoGPU",
   "Build-Debian9-GCC-x86_64-Release",
+  "Build-Debian9-GCC-x86_64-Release-EmbededResouces",
   "Build-Debian9-GCC-x86_64-Release-Flutter_Android",
   "Build-Debian9-GCC-x86_64-Release-NoGPU",
   "Build-Debian9-GCC-x86_64-Release-PDFium",
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-GCC-x86_64-Debug-EmbededResouces.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-GCC-x86_64-Debug-EmbededResouces.json
new file mode 100644
index 0000000..206b359
--- /dev/null
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Build-Debian9-GCC-x86_64-Debug-EmbededResouces.json
@@ -0,0 +1,56 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[CUSTOM_/_B_WORK]/skia/bin/fetch-gn"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "Debug",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-GCC-x86_64-Debug-EmbededResouces"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "[CUSTOM_/_B_WORK]/skia/bin/gn",
+      "gen",
+      "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-GCC-x86_64-Debug-EmbededResouces/Debug",
+      "--args=cc=\"gcc\" cxx=\"g++\" extra_cflags=[\"-O1\"] skia_embed_resoucres=true target_cpu=\"x86_64\""
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "Debug",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-GCC-x86_64-Debug-EmbededResouces"
+    },
+    "name": "gn gen"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-k",
+      "0",
+      "-C",
+      "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-GCC-x86_64-Debug-EmbededResouces/Debug"
+    ],
+    "cwd": "[CUSTOM_/_B_WORK]/skia",
+    "env": {
+      "BUILDTYPE": "Debug",
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
+      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Debian9-GCC-x86_64-Debug-EmbededResouces"
+    },
+    "name": "ninja"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/flavor/examples/full.py b/infra/bots/recipe_modules/flavor/examples/full.py
index 4ec7e8f..d1630da 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.py
+++ b/infra/bots/recipe_modules/flavor/examples/full.py
@@ -65,6 +65,7 @@
   'Build-Debian9-Clang-x86_64-Release-Mini',
   'Build-Debian9-Clang-x86_64-Release-Vulkan',
   'Build-Debian9-EMCC-wasm-Release',
+  'Build-Debian9-GCC-x86_64-Debug-EmbededResouces',
   'Build-Debian9-GCC-x86_64-Release-ANGLE',
   'Build-Debian9-GCC-x86_64-Release-Flutter_Android',
   'Build-Debian9-GCC-x86_64-Release-NoGPU',
diff --git a/infra/bots/recipe_modules/flavor/gn_flavor.py b/infra/bots/recipe_modules/flavor/gn_flavor.py
index 9e8e22b..a5aad9e 100644
--- a/infra/bots/recipe_modules/flavor/gn_flavor.py
+++ b/infra/bots/recipe_modules/flavor/gn_flavor.py
@@ -141,6 +141,8 @@
       })
     if extra_config == 'NoGPU':
       args['skia_enable_gpu'] = 'false'
+    if extra_config == 'EmbededResouces':
+      args['skia_embed_resoucres'] = 'true'
     if extra_config == 'Shared':
       args['is_component_build'] = 'true'
     if 'Vulkan' in extra_config and not 'Android' in extra_config:
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index af33679..8f0a7c7 100644
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -258,6 +258,12 @@
         "Build-Debian9-GCC-x86_64-Debug"
       ]
     },
+    "Build-Debian9-GCC-x86_64-Debug-EmbededResouces": {
+      "priority": 0.8,
+      "tasks": [
+        "Build-Debian9-GCC-x86_64-Debug-EmbededResouces"
+      ]
+    },
     "Build-Debian9-GCC-x86_64-Debug-NoGPU": {
       "priority": 0.8,
       "tasks": [
@@ -270,6 +276,12 @@
         "Build-Debian9-GCC-x86_64-Release"
       ]
     },
+    "Build-Debian9-GCC-x86_64-Release-EmbededResouces": {
+      "priority": 0.8,
+      "tasks": [
+        "Build-Debian9-GCC-x86_64-Release-EmbededResouces"
+      ]
+    },
     "Build-Debian9-GCC-x86_64-Release-Flutter_Android": {
       "priority": 0.8,
       "tasks": [
@@ -4232,6 +4244,29 @@
       "isolate": "compile_skia.isolate",
       "priority": 0.8
     },
+    "Build-Debian9-GCC-x86_64-Debug-EmbededResouces": {
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "os:Debian-9.2",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "compile",
+        "repository=<(REPO)",
+        "buildername=Build-Debian9-GCC-x86_64-Debug-EmbededResouces",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_repo=<(PATCH_REPO)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "compile_skia.isolate",
+      "priority": 0.8
+    },
     "Build-Debian9-GCC-x86_64-Debug-NoGPU": {
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -4278,6 +4313,29 @@
       "isolate": "compile_skia.isolate",
       "priority": 0.8
     },
+    "Build-Debian9-GCC-x86_64-Release-EmbededResouces": {
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "os:Debian-9.2",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "compile",
+        "repository=<(REPO)",
+        "buildername=Build-Debian9-GCC-x86_64-Release-EmbededResouces",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_repo=<(PATCH_REPO)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "compile_skia.isolate",
+      "priority": 0.8
+    },
     "Build-Debian9-GCC-x86_64-Release-Flutter_Android": {
       "dependencies": [
         "Housekeeper-PerCommit-IsolateAndroidNDKLinux"
diff --git a/tests/FontMgrAndroidParserTest.cpp b/tests/FontMgrAndroidParserTest.cpp
index cd10d04..6af6637 100644
--- a/tests/FontMgrAndroidParserTest.cpp
+++ b/tests/FontMgrAndroidParserTest.cpp
@@ -10,6 +10,7 @@
 #include "SkFixed.h"
 #include "SkFontMgr_android.h"
 #include "SkFontMgr_android_parser.h"
+#include "SkOSFile.h"
 #include "SkTypeface.h"
 #include "Test.h"
 
@@ -224,8 +225,14 @@
 }
 
 DEF_TEST(FontMgrAndroidLegacyMakeTypeface, reporter) {
+    constexpr char fontsXmlFilename[] = "fonts/fonts.xml";
     SkString basePath = GetResourcePath("fonts/");
-    SkString fontsXml = GetResourcePath("fonts/fonts.xml");
+    SkString fontsXml = GetResourcePath(fontsXmlFilename);
+
+    if (!sk_exists(fontsXml.c_str())) {
+        ERRORF(reporter, "file missing: %s\n", fontsXmlFilename);
+        return;
+    }
 
     SkFontMgr_Android_CustomFonts custom;
     custom.fSystemFontUse = SkFontMgr_Android_CustomFonts::kOnlyCustom;
diff --git a/tests/StreamTest.cpp b/tests/StreamTest.cpp
index 5112312..24e74c6 100644
--- a/tests/StreamTest.cpp
+++ b/tests/StreamTest.cpp
@@ -287,7 +287,13 @@
     test_fully_peekable_stream(reporter, &memStream, memStream.getLength());
 
     // Test an arbitrary file stream. file streams do not support peeking.
-    SkFILEStream fileStream(GetResourcePath("images/baby_tux.webp").c_str());
+    constexpr char filename[] = "images/baby_tux.webp";
+    SkString path = GetResourcePath(filename);
+    if (!sk_exists(path.c_str())) {
+        ERRORF(reporter, "file missing: %s\n", filename);
+        return;
+    }
+    SkFILEStream fileStream(path.c_str());
     REPORTER_ASSERT(reporter, fileStream.isValid());
     if (!fileStream.isValid()) {
         return;
diff --git a/tools/BinaryAsset.h b/tools/BinaryAsset.h
new file mode 100644
index 0000000..6fb7157
--- /dev/null
+++ b/tools/BinaryAsset.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef BinaryAsset_DEFINED
+#define BinaryAsset_DEFINED
+
+#include <cstddef>
+
+struct BinaryAsset {
+    const char* name;
+    const void* data;
+    size_t len;
+};
+
+#endif  // BinaryAsset_DEFINED
diff --git a/tools/Resources.cpp b/tools/Resources.cpp
index 6436a0d..9d54026 100644
--- a/tools/Resources.cpp
+++ b/tools/Resources.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "BinaryAsset.h"
 #include "Resources.h"
 #include "SkBitmap.h"
 #include "SkCommandLineFlags.h"
@@ -40,6 +41,19 @@
                 : nullptr;
 }
 
+#ifdef SK_EMBED_RESOURCES
+extern BinaryAsset gResources[];
+sk_sp<SkData> GetResourceAsData(const char* resource) {
+    for (const BinaryAsset* ptr = gResources; ptr->name; ++ptr) {
+        if (0 == strcmp(resource, ptr->name)) {
+            return SkData::MakeWithoutCopy(ptr->data, ptr->len);
+        }
+    }
+    SkDebugf("Resource \"%s\" not found.\n", resource);
+    SK_ABORT("missing resource");
+    return nullptr;
+}
+#else
 sk_sp<SkData> GetResourceAsData(const char* resource) {
     auto data = SkData::MakeFromFileName(GetResourcePath(resource).c_str());
     if (!data) {
@@ -47,6 +61,7 @@
     }
     return data;
 }
+#endif
 
 sk_sp<SkTypeface> MakeResourceAsTypeface(const char* resource) {
     std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream(resource));