Bundle resources and skps directories into iOS app.

Will bundle resources/ for viewer (and skps/ if
that directory exists in the main Skia directory).
Also updates file code on iOS to fall back to bundle directory.

Docs-Preview: https://skia.org/?cl=76803
Bug: skia:7339
Change-Id: I244f67559c866451a6d02c3f1c4948d89457ec84
Reviewed-on: https://skia-review.googlesource.com/76803
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Mike Klein <mtklein@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index f37f4c6..416a42d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -983,6 +983,37 @@
         ]
       }
 
+      bundle_ios_data =
+          defined(invoker.bundle_ios_data) && invoker.bundle_ios_data
+
+      if (bundle_ios_data) {
+        has_skps =
+            "True" == exec_script("//gn/checkdir.py",
+                                  [ rebase_path("skps", root_build_dir) ],
+                                  "trim string")
+        bundle_data("${app_name}_bundle_resources") {
+          sources = [
+            "resources",
+          ]
+          outputs = [
+            # iOS reserves the folders 'Resources' and 'resources' so store one level deeper
+            "{{bundle_resources_dir}}/data/resources",
+          ]
+        }
+
+        if (has_skps) {
+          bundle_data("${app_name}_bundle_skps") {
+            sources = [
+              "skps",
+            ]
+            outputs = [
+              # Store in same folder as resources
+              "{{bundle_resources_dir}}/data/skps",
+            ]
+          }
+        }
+      }
+
       executable("${app_name}_generate_executable") {
         forward_variables_from(invoker,
                                "*",
@@ -1022,6 +1053,12 @@
           ":${app_name}_bundle_executable",
           ":${app_name}_bundle_info_plist",
         ]
+        if (bundle_ios_data) {
+          deps += [ ":${app_name}_bundle_resources" ]
+          if (has_skps) {
+            deps += [ ":${app_name}_bundle_skps" ]
+          }
+        }
 
         # should only code sign when running on a device, not the simulator
         if (target_cpu != "x64") {
@@ -1747,6 +1784,9 @@
   if (skia_enable_gpu) {
     test_app("viewer") {
       is_shared_library = is_android
+      if (is_ios) {
+        bundle_ios_data = true
+      }
       sources = [
         "tools/viewer/GMSlide.cpp",
         "tools/viewer/ImageSlide.cpp",
diff --git a/gn/checkdir.py b/gn/checkdir.py
new file mode 100644
index 0000000..4de7e80
--- /dev/null
+++ b/gn/checkdir.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+#
+# 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
+
+dirpath, = sys.argv[1:]
+
+print os.path.isdir(dirpath)
+
+
diff --git a/site/user/sample/viewer.md b/site/user/sample/viewer.md
index 340c3e1..f30888d 100644
--- a/site/user/sample/viewer.md
+++ b/site/user/sample/viewer.md
@@ -49,4 +49,4 @@
 
 iOS
 ---
-The viewer is not yet fully supported on iOS, but can be used to display individual slides on a device by launching via `ios-deploy` with the `--match` or `--slide` command-line options.
+The viewer is not yet fully supported on iOS, but can be used to display individual slides on a device by launching via `ios-deploy` with the `--match` or `--slide` command-line options. The viewer will automatically bundle the `resources` directory in the top-level Skia directory, and will bundle an `skps` directory if also placed in the Skia directory.
diff --git a/src/ports/SkOSFile_ios.h b/src/ports/SkOSFile_ios.h
new file mode 100644
index 0000000..d74aa20
--- /dev/null
+++ b/src/ports/SkOSFile_ios.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkString.h"
+
+#ifdef SK_BUILD_FOR_IOS
+#import <CoreFoundation/CoreFoundation.h>
+
+static bool ios_get_path_in_bundle(const char path[], SkString* result) {
+    // Get a reference to the main bundle
+    CFBundleRef mainBundle = CFBundleGetMainBundle();
+
+    // Get a reference to the file's URL
+    CFStringRef pathRef = CFStringCreateWithCString(nullptr, path, kCFStringEncodingUTF8);
+    // We use "data" as our subdirectory to match {{bundle_resources_dir}}/data in GN
+    // Unfortunately "resources" is not a valid top-level name in iOS, so we push it one level down
+    CFURLRef imageURL = CFBundleCopyResourceURL(mainBundle, pathRef, nullptr, CFSTR("data"));
+    CFRelease(pathRef);
+    if (!imageURL) {
+        return false;
+    }
+    if (!result) {
+        return true;
+    }
+
+    // Convert the URL reference into a string reference
+    CFStringRef imagePath = CFURLCopyFileSystemPath(imageURL, kCFURLPOSIXPathStyle);
+    CFRelease(imageURL);
+
+    // Get the system encoding method
+    CFStringEncoding encodingMethod = CFStringGetSystemEncoding();
+
+    // Convert the string reference into an SkString
+    result->set(CFStringGetCStringPtr(imagePath, encodingMethod));
+    return true;
+}
+#endif
diff --git a/src/ports/SkOSFile_posix.cpp b/src/ports/SkOSFile_posix.cpp
index 448a5c8..1985607 100644
--- a/src/ports/SkOSFile_posix.cpp
+++ b/src/ports/SkOSFile_posix.cpp
@@ -19,6 +19,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#ifdef SK_BUILD_FOR_IOS
+#include "SkOSFile_ios.h"
+#endif
+
 bool sk_exists(const char *path, SkFILE_Flags flags) {
     int mode = F_OK;
     if (flags & kRead_SkFILE_Flag) {
@@ -27,7 +31,16 @@
     if (flags & kWrite_SkFILE_Flag) {
         mode |= W_OK;
     }
+#ifdef SK_BUILD_FOR_IOS
+    // if the default path fails, check the bundle (but only if read-only)
+    if (0 == access(path, mode)) {
+        return true;
+    } else {
+        return (kRead_SkFILE_Flag == flags && ios_get_path_in_bundle(path, nullptr));
+    }
+#else
     return (0 == access(path, mode));
+#endif
 }
 
 typedef struct {
@@ -137,10 +150,16 @@
         ::closedir(self.fDIR);
         self.fDIR = nullptr;
     }
-
     self.fPath.set(path);
+
     if (path) {
         self.fDIR = ::opendir(path);
+#ifdef SK_BUILD_FOR_IOS
+        // check bundle for directory
+        if (!self.fDIR && ios_get_path_in_bundle(path, &self.fPath)) {
+            self.fDIR = ::opendir(path);
+        }
+#endif
         self.fSuffix.set(suffix);
     } else {
         self.fSuffix.reset();
diff --git a/src/ports/SkOSFile_stdio.cpp b/src/ports/SkOSFile_stdio.cpp
index 501d275..7cdc549 100644
--- a/src/ports/SkOSFile_stdio.cpp
+++ b/src/ports/SkOSFile_stdio.cpp
@@ -22,36 +22,9 @@
 #endif
 
 #ifdef SK_BUILD_FOR_IOS
-#import <CoreFoundation/CoreFoundation.h>
-
-static FILE* ios_open_from_bundle(const char path[], const char* perm) {
-    // Get a reference to the main bundle
-    CFBundleRef mainBundle = CFBundleGetMainBundle();
-
-    // Get a reference to the file's URL
-    CFStringRef pathRef = CFStringCreateWithCString(nullptr, path, kCFStringEncodingUTF8);
-    CFURLRef imageURL = CFBundleCopyResourceURL(mainBundle, pathRef, nullptr, nullptr);
-    CFRelease(pathRef);
-    if (!imageURL) {
-        return nullptr;
-    }
-
-    // Convert the URL reference into a string reference
-    CFStringRef imagePath = CFURLCopyFileSystemPath(imageURL, kCFURLPOSIXPathStyle);
-    CFRelease(imageURL);
-
-    // Get the system encoding method
-    CFStringEncoding encodingMethod = CFStringGetSystemEncoding();
-
-    // Convert the string reference into a C string
-    const char *finalPath = CFStringGetCStringPtr(imagePath, encodingMethod);
-    FILE* fileHandle = fopen(finalPath, perm);
-    CFRelease(imagePath);
-    return fileHandle;
-}
+#include "SkOSFile_ios.h"
 #endif
 
-
 FILE* sk_fopen(const char path[], SkFILE_Flags flags) {
     char    perm[4];
     char*   p = perm;
@@ -68,18 +41,17 @@
     //TODO: on Windows fopen is just ASCII or the current code page,
     //convert to utf16 and use _wfopen
     FILE* file = nullptr;
+    file = fopen(path, perm);
 #ifdef SK_BUILD_FOR_IOS
-    // if read-only, try to open from bundle first
-    if (kRead_SkFILE_Flag == flags) {
-        file = ios_open_from_bundle(path, perm);
-    }
-    // otherwise just read from the Documents directory (default)
-    if (!file) {
-#endif
-        file = fopen(path, perm);
-#ifdef SK_BUILD_FOR_IOS
+    // if not found in default path and read-only, try to open from bundle
+    if (!file && kRead_SkFILE_Flag == flags) {
+        SkString bundlePath;
+        if (ios_get_path_in_bundle(path, &bundlePath)) {
+            file = fopen(bundlePath.c_str(), perm);
+        }
     }
 #endif
+
     if (nullptr == file && (flags & kWrite_SkFILE_Flag)) {
         SkDEBUGF(("sk_fopen: fopen(\"%s\", \"%s\") returned nullptr (errno:%d): %s\n",
                   path, perm, errno, strerror(errno)));
@@ -140,7 +112,17 @@
 bool sk_isdir(const char *path) {
     struct stat status;
     if (0 != stat(path, &status)) {
+#ifdef SK_BUILD_FOR_IOS
+        // check the bundle directory if not in default path
+        SkString bundlePath;
+        if (ios_get_path_in_bundle(path, &bundlePath)) {
+            if (0 != stat(bundlePath.c_str(), &status)) {
+                return false;
+            }
+        }
+#else
         return false;
+#endif
     }
     return SkToBool(status.st_mode & S_IFDIR);
 }