[ios] Update sample to build the app as a framework

To demonstrate fix for https://crbug.com/gn/337, update the sample
application to build the application as a framework with a shell
app that `dlopen()` the framework and call the entry point.

Fixed: 337
Change-Id: I939f0736856a45725549f62cca72e09a1f0e0f0e
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15461
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/examples/ios/app/BUILD.gn b/examples/ios/app/BUILD.gn
index fdb3d03..af3d7cc 100644
--- a/examples/ios/app/BUILD.gn
+++ b/examples/ios/app/BUILD.gn
@@ -3,13 +3,21 @@
 # found in the LICENSE file.
 
 import("//build/config/ios/templates/ios_app_bundle.gni")
+import("//build/config/ios/templates/ios_framework_bundle.gni")
 import("//build/config/ios/templates/storyboards.gni")
 
 ios_app_bundle("hello") {
   output_name = "Hello"
-
   info_plist = "resources/Info.plist"
 
+  sources = [ "main.m" ]
+
+  deps = [ ":hello_framework+bundle" ]
+}
+
+ios_framework_bundle("hello_framework") {
+  output_name = "HelloMain"
+
   sources = [
     "AppDelegate.h",
     "AppDelegate.m",
@@ -17,7 +25,7 @@
     "SceneDelegate.m",
     "ViewController.h",
     "ViewController.m",
-    "main.m",
+    "hello_main.m",
   ]
 
   frameworks = [
diff --git a/examples/ios/app/hello_main.m b/examples/ios/app/hello_main.m
new file mode 100644
index 0000000..feffc55
--- /dev/null
+++ b/examples/ios/app/hello_main.m
@@ -0,0 +1,16 @@
+// Copyright 2023 The Chromium 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 <UIKit/UIKit.h>
+
+#import "app/AppDelegate.h"
+
+int hello_main(int argc, char** argv) {
+  NSString* appDelegateClassName;
+  @autoreleasepool {
+    appDelegateClassName = NSStringFromClass([AppDelegate class]);
+  }
+
+  return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+}
diff --git a/examples/ios/app/main.m b/examples/ios/app/main.m
index b01cb7a..5c27b89 100644
--- a/examples/ios/app/main.m
+++ b/examples/ios/app/main.m
@@ -1,15 +1,65 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2023 The Chromium 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 <UIKit/UIKit.h>
-#import "app/AppDelegate.h"
+#include <dlfcn.h>
+#include <mach-o/dyld.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef int (*entry_point)(int, char**);
+
+static char kMainRelativePath[] = "/Frameworks/HelloMain.framework/HelloMain";
+
+static char* GetHelloMainPath(void) {
+  uint32_t buffer_size = 0;
+  _NSGetExecutablePath(NULL, &buffer_size);
+  if (buffer_size == 0)
+    return NULL;
+
+  uint32_t suffix_len = sizeof(kMainRelativePath) + 1;
+  if (buffer_size >= UINT32_MAX - suffix_len)
+    return NULL;
+
+  char* buffer = malloc(suffix_len + buffer_size);
+  if (!buffer)
+    return NULL;
+
+  if (_NSGetExecutablePath(buffer, &buffer_size) != 0)
+    return NULL;
+
+  char* last_sep = strrchr(buffer, '/');
+  if (!last_sep)
+    return NULL;
+
+  memcpy(last_sep, kMainRelativePath, suffix_len);
+  return buffer;
+}
 
 int main(int argc, char** argv) {
-  NSString* appDelegateClassName;
-  @autoreleasepool {
-    appDelegateClassName = NSStringFromClass([AppDelegate class]);
+  char* hello_main_path = GetHelloMainPath();
+  if (!hello_main_path)
+    return 1;
+
+  void* hello_main_handle = dlopen(hello_main_path, RTLD_NOW);
+  if (!hello_main_handle)
+    return 1;
+
+  free(hello_main_path);
+  hello_main_path = NULL;
+
+  entry_point hello_main_entry = dlsym(hello_main_handle, "hello_main");
+  if (!hello_main_entry)
+    return 1;
+
+  int retcode = hello_main_entry(argc, argv);
+  if (retcode) {
+    return retcode;
   }
 
-  return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+  if (!dlclose(hello_main_handle)) {
+    return 1;
+  }
+
+  return 0;
 }
diff --git a/examples/ios/build/config/ios/templates/ios_binary_bundle.gni b/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
index 43ec355..1e7b73d 100644
--- a/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
+++ b/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
@@ -94,6 +94,7 @@
   }
 
   bundle_data(_plist_bundle) {
+    product_type = invoker.product_type
     public_deps = [ ":$_plist_target" ]
     sources = [ "$target_out_dir/$_plist_target/Info.plist" ]
     outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
diff --git a/examples/ios/build/config/ios/templates/ios_framework_bundle.gni b/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
index 13db266..805060e 100644
--- a/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
+++ b/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
@@ -47,6 +47,7 @@
     _bundle_identifier_prefix = invoker.bundle_identifier_prefix
   }
 
+  _product_type = "com.apple.product-type.framework"
   _bundle_identifier = "$_bundle_identifier_prefix.$_output_name"
 
   shared_library(_dylib_target) {
@@ -79,6 +80,7 @@
   }
 
   bundle_data(_dylib_bundle) {
+    product_type = _product_type
     public_deps = [ ":$_dylib_target" ]
     sources = [ "$target_out_dir/$_dylib_target/$_output_name" ]
     outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
@@ -99,6 +101,7 @@
     _headers_bundle = target_name + "_headers_bundle"
 
     bundle_data(_headers_bundle) {
+      product_type = _product_type
       sources = invoker.public_headers + [ _umbrella_output ]
       outputs = [ "{{bundle_resources_dir}}/Headers/{{source_file_part}}" ]
       public_deps = [ ":$_umbrella_target" ]
@@ -127,7 +130,8 @@
                            ])
 
     output_name = _output_name
-    product_type = "com.apple.product-type.framework"
+    product_type = _product_type
+    transparent = true
 
     bundle_identifier = _bundle_identifier
     bundle_extension = "framework"