Add a sample project that allow to target iOS

This sample project allows building app bundles, framework bundles
and to build target on the host (e.g. to build a tool to preprocess
some file on the host before compiling them for iOS).

This sample does not support code-signature of the bundles (but it
will still embeds the necessary entitlements into the application
to allow debugging when targetting a simulator build).

This sample targets iOS 13.0+ SDK but can be adapted to target
older SDKs (only the demo application depends on a recent version
of the SDK).

Bug: 21
Change-Id: I80dff2c8eba989f84073bc3dd35326939b307080
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6661
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/examples/ios/.gitignore b/examples/ios/.gitignore
new file mode 100644
index 0000000..6a3417b
--- /dev/null
+++ b/examples/ios/.gitignore
@@ -0,0 +1 @@
+/out/
diff --git a/examples/ios/.gn b/examples/ios/.gn
new file mode 100644
index 0000000..e5b6d4a
--- /dev/null
+++ b/examples/ios/.gn
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/BUILDCONFIG.gn"
diff --git a/examples/ios/BUILD.gn b/examples/ios/BUILD.gn
new file mode 100644
index 0000000..bd71b4f
--- /dev/null
+++ b/examples/ios/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2019 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.
+
+group("all") {
+  deps = [
+    "//app:hello",
+    "//host:username",
+  ]
+}
diff --git a/examples/ios/app/AppDelegate.h b/examples/ios/app/AppDelegate.h
new file mode 100644
index 0000000..54d31eb
--- /dev/null
+++ b/examples/ios/app/AppDelegate.h
@@ -0,0 +1,9 @@
+// Copyright 2019 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>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@end
diff --git a/examples/ios/app/AppDelegate.m b/examples/ios/app/AppDelegate.m
new file mode 100644
index 0000000..32b3d44
--- /dev/null
+++ b/examples/ios/app/AppDelegate.m
@@ -0,0 +1,29 @@
+// Copyright 2019 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 "app/AppDelegate.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  return YES;
+}
+
+#pragma mark - UISceneSession lifecycle
+
+- (UISceneConfiguration*)application:(UIApplication*)application
+    configurationForConnectingSceneSession:
+        (UISceneSession*)connectingSceneSession
+                                   options:(UISceneConnectionOptions*)options {
+  return
+      [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
+                                     sessionRole:connectingSceneSession.role];
+}
+
+- (void)application:(UIApplication*)application
+    didDiscardSceneSessions:(NSSet<UISceneSession*>*)sceneSessions {
+}
+
+@end
diff --git a/examples/ios/app/BUILD.gn b/examples/ios/app/BUILD.gn
new file mode 100644
index 0000000..eb4b8a9
--- /dev/null
+++ b/examples/ios/app/BUILD.gn
@@ -0,0 +1,41 @@
+# Copyright 2019 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("//build/config/ios/templates/ios_app_bundle.gni")
+import("//build/config/ios/templates/storyboards.gni")
+
+ios_app_bundle("hello") {
+  output_name = "Hello"
+
+  info_plist = "resources/Info.plist"
+
+  sources = [
+    "AppDelegate.h",
+    "AppDelegate.m",
+    "SceneDelegate.h",
+    "SceneDelegate.m",
+    "ViewController.h",
+    "ViewController.m",
+    "main.m",
+  ]
+
+  frameworks = [
+    "CoreGraphics.framework",
+    "Foundation.framework",
+    "UIKit.framework",
+  ]
+
+  deps = [
+    ":storyboards",
+    "//shared:hello_framework",
+    "//shared:hello_framework+bundle",
+  ]
+}
+
+storyboards("storyboards") {
+  sources = [
+    "resources/LaunchScreen.storyboard",
+    "resources/Main.storyboard",
+  ]
+}
diff --git a/examples/ios/app/SceneDelegate.h b/examples/ios/app/SceneDelegate.h
new file mode 100644
index 0000000..ef1888f
--- /dev/null
+++ b/examples/ios/app/SceneDelegate.h
@@ -0,0 +1,11 @@
+// Copyright 2019 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>
+
+@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
+
+@property(strong, nonatomic) UIWindow* window;
+
+@end
diff --git a/examples/ios/app/SceneDelegate.m b/examples/ios/app/SceneDelegate.m
new file mode 100644
index 0000000..5e20b9e
--- /dev/null
+++ b/examples/ios/app/SceneDelegate.m
@@ -0,0 +1,29 @@
+// Copyright 2019 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 "app/SceneDelegate.h"
+
+@implementation SceneDelegate
+
+- (void)scene:(UIScene*)scene
+    willConnectToSession:(UISceneSession*)session
+                 options:(UISceneConnectionOptions*)connectionOptions {
+}
+
+- (void)sceneDidDisconnect:(UIScene*)scene {
+}
+
+- (void)sceneDidBecomeActive:(UIScene*)scene {
+}
+
+- (void)sceneWillResignActive:(UIScene*)scene {
+}
+
+- (void)sceneWillEnterForeground:(UIScene*)scene {
+}
+
+- (void)sceneDidEnterBackground:(UIScene*)scene {
+}
+
+@end
diff --git a/examples/ios/app/ViewController.h b/examples/ios/app/ViewController.h
new file mode 100644
index 0000000..4076e40
--- /dev/null
+++ b/examples/ios/app/ViewController.h
@@ -0,0 +1,9 @@
+// Copyright 2019 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>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/examples/ios/app/ViewController.m b/examples/ios/app/ViewController.m
new file mode 100644
index 0000000..e0a80cb
--- /dev/null
+++ b/examples/ios/app/ViewController.m
@@ -0,0 +1,31 @@
+// Copyright 2019 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 <HelloShared/HelloShared.h>
+
+#import "app/ViewController.h"
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  UILabel* label = [self labelWithText:[Greetings greet]];
+  [self addCenteredView:label toParentView:self.view];
+}
+
+- (UILabel*)labelWithText:(NSString*)text {
+  UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero];
+  label.text = text;
+  [label sizeToFit];
+  return label;
+}
+
+- (void)addCenteredView:(UIView*)view toParentView:(UIView*)parentView {
+  view.center = [parentView convertPoint:parentView.center
+                                fromView:parentView.superview];
+  [parentView addSubview:view];
+}
+
+@end
diff --git a/examples/ios/app/main.m b/examples/ios/app/main.m
new file mode 100644
index 0000000..b01cb7a
--- /dev/null
+++ b/examples/ios/app/main.m
@@ -0,0 +1,15 @@
+// Copyright 2019 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 main(int argc, char** argv) {
+  NSString* appDelegateClassName;
+  @autoreleasepool {
+    appDelegateClassName = NSStringFromClass([AppDelegate class]);
+  }
+
+  return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+}
diff --git a/examples/ios/app/resources/Info.plist b/examples/ios/app/resources/Info.plist
new file mode 100644
index 0000000..7b6037c
--- /dev/null
+++ b/examples/ios/app/resources/Info.plist
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UIApplicationSceneManifest</key>
+	<dict>
+		<key>UIApplicationSupportsMultipleScenes</key>
+		<false/>
+		<key>UISceneConfigurations</key>
+		<dict>
+			<key>UIWindowSceneSessionRoleApplication</key>
+			<array>
+				<dict>
+					<key>UISceneConfigurationName</key>
+					<string>Default Configuration</string>
+					<key>UISceneDelegateClassName</key>
+					<string>SceneDelegate</string>
+					<key>UISceneStoryboardFile</key>
+					<string>Main</string>
+				</dict>
+			</array>
+		</dict>
+	</dict>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>
diff --git a/examples/ios/app/resources/LaunchScreen.storyboard b/examples/ios/app/resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/examples/ios/app/resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/examples/ios/app/resources/Main.storyboard b/examples/ios/app/resources/Main.storyboard
new file mode 100644
index 0000000..808a21c
--- /dev/null
+++ b/examples/ios/app/resources/Main.storyboard
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/examples/ios/build/BUILD.gn b/examples/ios/build/BUILD.gn
new file mode 100644
index 0000000..aa51efa
--- /dev/null
+++ b/examples/ios/build/BUILD.gn
@@ -0,0 +1,68 @@
+# Copyright 2019 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.
+
+config("compiler") {
+  configs = [
+    ":include_dirs",
+    ":cpp_standard",
+    ":objc_use_arc",
+    ":objc_abi_version",
+  ]
+}
+
+config("shared_binary") {
+  if (current_os == "ios" || current_os == "mac") {
+    configs = [ ":rpath_config" ]
+  }
+}
+
+config("objc_abi_version") {
+  cflags_objc = [ "-fobjc-abi-version=2" ]
+  cflags_objcc = cflags_objc
+  ldflags = [
+    "-Xlinker",
+    "-objc_abi_version",
+    "-Xlinker",
+    "2",
+  ]
+}
+
+config("include_dirs") {
+  include_dirs = [
+    "//",
+    root_out_dir,
+  ]
+}
+
+config("objc_use_arc") {
+  cflags_objc = [
+    "-fobjc-arc",
+    "-fobjc-weak",
+  ]
+  cflags_objcc = cflags_objc
+}
+
+config("cpp_standard") {
+  cflags_c = [ "--std=c11" ]
+  cflags_cc = [
+    "--std=c++17",
+    "--stdlib=libc++",
+  ]
+  ldflags = [ "--stdlib=libc++" ]
+}
+
+if (current_os == "ios" || current_os == "mac") {
+  config("rpath_config") {
+    ldflags = [
+      "-Xlinker",
+      "-rpath",
+      "-Xlinker",
+      "@executable_path/Frameworks",
+      "-Xlinker",
+      "-rpath",
+      "-Xlinker",
+      "@loader_path/Frameworks",
+    ]
+  }
+}
diff --git a/examples/ios/build/BUILDCONFIG.gn b/examples/ios/build/BUILDCONFIG.gn
new file mode 100644
index 0000000..53ee3d9
--- /dev/null
+++ b/examples/ios/build/BUILDCONFIG.gn
@@ -0,0 +1,43 @@
+# Copyright 2019 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.
+
+if (target_os == "") {
+  target_os = "ios"
+}
+if (target_cpu == "") {
+  target_cpu = host_cpu
+}
+if (current_cpu == "") {
+  current_cpu = target_cpu
+}
+if (current_os == "") {
+  current_os = target_os
+}
+
+# All binary targets will get this list of configs by default.
+_shared_binary_target_configs = [ "//build:compiler" ]
+
+# Apply that default list to the binary target types.
+set_defaults("executable") {
+  configs = _shared_binary_target_configs
+  configs += [ "//build:shared_binary" ]
+}
+set_defaults("static_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("shared_library") {
+  configs = _shared_binary_target_configs
+  configs += [ "//build:shared_binary" ]
+}
+set_defaults("source_set") {
+  configs = _shared_binary_target_configs
+}
+
+set_default_toolchain("//build/toolchain/$target_os:clang_$target_cpu")
+
+if (target_os == "ios") {
+  host_toolchain = "//build/toolchain/$host_os:clang_$host_cpu"
+} else {
+  host_toolchain = default_toolchain
+}
diff --git a/examples/ios/build/config/ios/BUILD.gn b/examples/ios/build/config/ios/BUILD.gn
new file mode 100644
index 0000000..47902c9
--- /dev/null
+++ b/examples/ios/build/config/ios/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 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("//build/config/ios/sdk_info.gni")
+import("//build/config/ios/templates/merge_plist.gni")
+
+merge_plist("compiler_plist") {
+  substitutions = {
+    COMPILER_NAME = sdk_info.compiler
+    MACOS_BUILD = sdk_info.macos_build
+    PLATFORM_BUILD = sdk_info.sdk_build
+    PLATFORM_DISPLAY_NAME = sdk_info.platform_name
+    PLATFORM_NAME = sdk_info.platform
+    PLATFORM_VERSION = sdk_info.sdk_version
+    SDK_BUILD = sdk_info.sdk_build
+    SDK_NAME = sdk_info.sdk
+    XCODE_BUILD = sdk_info.xcode_build
+    XCODE_VERSION = sdk_info.xcode_version
+  }
+
+  output = "$target_out_dir/compiler_plist/Info.plist"
+  plists = [ "//build/config/ios/resources/compiler-Info.plist" ]
+}
diff --git a/examples/ios/build/config/ios/bundle_identifier_prefix.gni b/examples/ios/build/config/ios/bundle_identifier_prefix.gni
new file mode 100644
index 0000000..97c47cc
--- /dev/null
+++ b/examples/ios/build/config/ios/bundle_identifier_prefix.gni
@@ -0,0 +1,8 @@
+# Copyright 2019 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.
+
+declare_args() {
+  # Default bundle identifier prefix.
+  default_bundle_identifier_prefix = "com.google"
+}
diff --git a/examples/ios/build/config/ios/deployment_target.gni b/examples/ios/build/config/ios/deployment_target.gni
new file mode 100644
index 0000000..7180488
--- /dev/null
+++ b/examples/ios/build/config/ios/deployment_target.gni
@@ -0,0 +1,9 @@
+# Copyright 2019 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.
+
+declare_args() {
+  # Maximum deployment target. Automatically detected by sdk_info.py but
+  # needs to be specified to a version < 11.0 if targetting 32-bit archs.
+  ios_deployment_target = "13.0"
+}
diff --git a/examples/ios/build/config/ios/resources/Entitlements-Simulated.plist b/examples/ios/build/config/ios/resources/Entitlements-Simulated.plist
new file mode 100644
index 0000000..0a4badf
--- /dev/null
+++ b/examples/ios/build/config/ios/resources/Entitlements-Simulated.plist
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>application-identifier</key>
+  <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+  <key>keychain-access-groups</key>
+  <array>
+    <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+  </array>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/resources/Info.plist b/examples/ios/build/config/ios/resources/Info.plist
new file mode 100644
index 0000000..9bcb244
--- /dev/null
+++ b/examples/ios/build/config/ios/resources/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/resources/compiler-Info.plist b/examples/ios/build/config/ios/resources/compiler-Info.plist
new file mode 100644
index 0000000..0a02517
--- /dev/null
+++ b/examples/ios/build/config/ios/resources/compiler-Info.plist
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>DTSDKName</key>
+	<string>$(SDK_NAME)</string>
+	<key>DTXcode</key>
+	<string>$(XCODE_VERSION)</string>
+	<key>DTSDKBuild</key>
+	<string>$(SDK_BUILD)</string>
+	<key>BuildMachineOSBuild</key>
+	<string>$(MACOS_BUILD)</string>
+	<key>DTPlatformName</key>
+	<string>$(PLATFORM_NAME)</string>
+	<key>DTCompiler</key>
+	<string>$(COMPILER_NAME)</string>
+	<key>DTPlatformVersion</key>
+	<string>$(PLATFORM_VERSION)</string>
+	<key>DTXcodeBuild</key>
+	<string>$(XCODE_BUILD)</string>
+	<key>DTPlatformBuild</key>
+	<string>$(PLATFORM_BUILD)</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>$(PLATFORM_DISPLAY_NAME)</string>
+	</array>
+	<key>UIDeviceFamily</key>
+	<array>
+		<string>1</string>
+		<string>2</string>
+	</array>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/scripts/compile_storyboard.py b/examples/ios/build/config/ios/scripts/compile_storyboard.py
new file mode 100644
index 0000000..2fa1d20
--- /dev/null
+++ b/examples/ios/build/config/ios/scripts/compile_storyboard.py
@@ -0,0 +1,53 @@
+# Copyright 2019 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.
+
+"""
+Compiles a .storyboard file.
+"""
+
+import argparse
+import os
+import subprocess
+import sys
+
+
+def CompileStoryboard(storyboard, out, ios_deployment_target):
+  """Compiles |storyboard| storyboard to |out| for |ios_deployment_target|."""
+  subprocess.check_call([
+      'ibtool', '--target-device', 'iphone', '--target-device', 'ipad',
+      '--auto-activate-custom-fonts', '--minimum-deployment-target',
+      ios_deployment_target, '--compilation-directory', out,
+      storyboard,
+  ])
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      'input',
+      help='path to the .storyboard file to compile')
+  parser.add_argument(
+      '-o', '--output', required=True,
+      help='path to the result')
+  parser.add_argument(
+      '-t', '--minimum-deployment-target', required=True,
+      help='iOS deployment target')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  CompileStoryboard(
+      os.path.abspath(args.input),
+      os.path.dirname(os.path.abspath(args.output)),
+      args.minimum_deployment_target)
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py b/examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py
new file mode 100644
index 0000000..cf38b10
--- /dev/null
+++ b/examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py
@@ -0,0 +1,119 @@
+# Copyright 2019 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.
+
+"""
+Finds the team identifier to use for code signing bundle given its
+bundle identifier.
+"""
+
+import argparse
+import fnmatch
+import glob
+import json
+import os
+import plistlib
+import subprocess
+import sys
+
+
+class ProvisioningProfile(object):
+
+  def __init__(self, mobileprovision_path):
+    self._path = mobileprovision_path
+    self._data = plistlib.readPlistFromString(
+        subprocess.check_output(
+            ['security', 'cms', '-D', '-i', mobileprovision_path]))
+
+  @property
+  def application_identifier_pattern(self):
+    return self._data.get('Entitlements', {}).get('application-identifier', '')
+
+  @property
+  def app_identifier_prefix(self):
+    return self._data.get('ApplicationIdentifierPrefix', [''])[0]
+
+  def ValidToSignBundle(self, bundle_identifier):
+    """Returns whether the provisioning profile can sign |bundle_identifier|."""
+    return fnmatch.fnmatch(
+        self.app_identifier_prefix + '.' + bundle_identifier,
+        self.application_identifier_pattern)
+
+
+def GetProvisioningProfilesDir():
+  """Returns the location of the locally installed provisioning profiles."""
+  return os.path.join(
+      os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles')
+
+
+def ListProvisioningProfiles():
+  """Returns a list of all installed provisioning profiles."""
+  return glob.glob(
+      os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision'))
+
+
+def LoadProvisioningProfile(mobileprovision_path):
+  """Loads the Apple Property List embedded in |mobileprovision_path|."""
+  return ProvisioningProfile(mobileprovision_path)
+
+
+def ListValidProvisioningProfiles(bundle_identifier):
+  """Returns a list of provisioning profile valid for |bundle_identifier|."""
+  result = []
+  for mobileprovision_path in ListProvisioningProfiles():
+    mobileprovision = LoadProvisioningProfile(mobileprovision_path)
+    if mobileprovision.ValidToSignBundle(bundle_identifier):
+      result.append(mobileprovision)
+  return result
+
+
+def FindProvisioningProfile(bundle_identifier):
+  """Returns the path to the provisioning profile for |bundle_identifier|."""
+  return max(
+      ListValidProvisioningProfiles(bundle_identifier),
+      key=lambda p: len(p.application_identifier_pattern))
+
+
+def GenerateSubsitutions(bundle_identifier, mobileprovision):
+  if mobileprovision:
+    app_identifier_prefix = mobileprovision.app_identifier_prefix + '.'
+  else:
+    app_identifier_prefix = '*.'
+
+  return {
+      'CFBundleIdentifier': bundle_identifier,
+      'AppIdentifierPrefix': app_identifier_prefix
+  }
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-b', '--bundle-identifier', required=True,
+      help='bundle identifier for the application')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path to the result; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  mobileprovision = FindProvisioningProfile(args.bundle_identifier)
+  substitutions = GenerateSubsitutions(args.bundle_identifier, mobileprovision)
+
+  if args.output == '-':
+    sys.stdout.write(json.dumps(substitutions))
+  else:
+    with open(args.output, 'w') as output:
+      output.write(json.dumps(substitutions))
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/generate_umbrella_header.py b/examples/ios/build/config/ios/scripts/generate_umbrella_header.py
new file mode 100644
index 0000000..1137376
--- /dev/null
+++ b/examples/ios/build/config/ios/scripts/generate_umbrella_header.py
@@ -0,0 +1,54 @@
+# Copyright 2019 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.
+
+"""
+Generates an umbrella header file that #import all public header of a
+binary framework.
+"""
+
+import argparse
+import os
+import sys
+
+
+def GenerateImport(header):
+  """Returns a string for importing |header|."""
+  return '#import "%s"\n' % os.path.basename(header)
+
+
+def GenerateUmbrellaHeader(headers):
+  """Returns a string with the content of the umbrella header."""
+  return ''.join([ GenerateImport(header) for header in headers ])
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      'headers', nargs='+',
+      help='path to the public heeaders')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path of the output file to create; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  content = GenerateUmbrellaHeader(args.headers)
+
+  if args.output == '-':
+    sys.stdout.write(content)
+  else:
+    with open(args.output, 'w') as output:
+      output.write(content)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/merge_plist.py b/examples/ios/build/config/ios/scripts/merge_plist.py
new file mode 100644
index 0000000..452cf53
--- /dev/null
+++ b/examples/ios/build/config/ios/scripts/merge_plist.py
@@ -0,0 +1,134 @@
+# Copyright 2019 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.
+
+"""
+Merges multiple Apple Property List files (.plist) and perform variables
+substitutions $(VARIABLE) in the Property List string values.
+"""
+
+import argparse
+import json
+import re
+import subprocess
+import sys
+
+
+# Pattern representing a variable to substitue in a string value.
+VARIABLE_PATTERN = re.compile(r'\$\(([^)]*)\)')
+
+
+def LoadPlist(plist_path):
+  """Loads Apple Property List file at |plist_path|."""
+  return json.loads(
+      subprocess.check_output(
+          ['plutil', '-convert', 'json', '-o', '-', plist_path]))
+
+
+def SavePlist(plist_path, content, format):
+  """Saves |content| as Apple Property List in |format| at |plist_path|."""
+  proc = subprocess.Popen(
+      ['plutil', '-convert', format, '-o', plist_path, '-'],
+      stdin=subprocess.PIPE)
+  output, _ = proc.communicate(json.dumps(content))
+  if proc.returncode:
+    raise subprocess.CalledProcessError(
+        proc.returncode,
+        ['plutil', '-convert', format, '-o', plist_path, '-'],
+        output)
+
+
+def MergeObjects(obj1, obj2):
+  """Merges two objects (either dictionary, list, string or numbers)."""
+  if type(obj1) != type(obj2):
+    return obj2
+
+  if isinstance(obj2, dict):
+    result = dict(obj1)
+    for key in obj2:
+      value1 = obj1.get(key, None)
+      value2 = obj2.get(key, None)
+      result[key] = MergeObjects(value1, value2)
+    return result
+
+  if isinstance(obj2, list):
+    return obj1 + obj2
+
+  return obj2
+
+
+def MergePlists(plist_paths):
+  """Loads and merges all Apple Property List files at |plist_paths|."""
+  plist = {}
+  for plist_path in plist_paths:
+    plist = MergeObjects(plist, LoadPlist(plist_path))
+  return plist
+
+
+def PerformSubstitutions(plist, substitutions):
+  """Performs variables substitutions in |plist| given by |substitutions|."""
+  if isinstance(plist, dict):
+    result = dict(plist)
+    for key in plist:
+      result[key] = PerformSubstitutions(plist[key], substitutions)
+    return result
+
+  if isinstance(plist, list):
+    return [ PerformSubstitutions(item, substitutions) for item in plist ]
+
+  if isinstance(plist, (str, unicode)):
+    result = plist
+    while True:
+      match = VARIABLE_PATTERN.search(result)
+      if not match:
+        break
+
+      extent = match.span()
+      expand = substitutions[match.group(1)]
+      result = result[:extent[0]] + expand + result[extent[1]:]
+    return result
+
+  return plist
+
+
+def PerformSubstitutionsFrom(plist, substitutions_path):
+  """Performs variable substitutions in |plist| from |substitutions_path|."""
+  with open(substitutions_path) as substitutions_file:
+    return PerformSubstitutions(plist, json.load(substitutions_file))
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-s', '--substitutions',
+      help='path to a JSON file containing variable substitutions')
+  parser.add_argument(
+      '-f', '--format', default='json', choices=('json', 'binary1', 'xml1'),
+      help='format of the generated file')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path to the result; - means stdout')
+  parser.add_argument(
+      'inputs', nargs='+',
+      help='path of the input files to merge')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  data = MergePlists(args.inputs)
+  if args.substitutions:
+    data = PerformSubstitutionsFrom(
+        data, args.substitutions)
+
+  SavePlist(args.output, data, args.format)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/sdk_info.py b/examples/ios/build/config/ios/scripts/sdk_info.py
new file mode 100644
index 0000000..827aee0
--- /dev/null
+++ b/examples/ios/build/config/ios/scripts/sdk_info.py
@@ -0,0 +1,147 @@
+# Copyright 2019 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.
+
+"""Collects information about the SDK and return them as JSON file."""
+
+import argparse
+import json
+import re
+import subprocess
+import sys
+
+# Patterns used to extract the Xcode version and build version.
+XCODE_VERSION_PATTERN = re.compile(r'Xcode (\d+)\.(\d+)')
+XCODE_BUILD_PATTERN = re.compile(r'Build version (.*)')
+
+
+def GetAppleCpuName(target_cpu):
+  """Returns the name of the |target_cpu| using Apple's convention."""
+  return {
+      'x64': 'x86_64',
+      'arm': 'armv7',
+      'x86': 'i386'
+  }.get(target_cpu, target_cpu)
+
+
+def IsSimulator(target_cpu):
+  """Returns whether the |target_cpu| corresponds to a simulator build."""
+  return not target_cpu.startswith('arm')
+
+
+def GetPlatform(target_cpu):
+  """Returns the platform for the |target_cpu|."""
+  if IsSimulator(target_cpu):
+    return 'iphonesimulator'
+  else:
+    return 'iphoneos'
+
+
+def GetPlaformDisplayName(target_cpu):
+  """Returns the platform display name for the |target_cpu|."""
+  if IsSimulator(target_cpu):
+    return 'iPhoneSimulator'
+  else:
+    return 'iPhoneOS'
+
+
+def ExtractOSVersion():
+  """Extract the version of macOS of the current machine."""
+  return subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+
+
+def ExtractXcodeInfo():
+  """Extract Xcode version and build version."""
+  version, build = None, None
+  for line in subprocess.check_output(['xcodebuild', '-version']).splitlines():
+    match = XCODE_VERSION_PATTERN.search(line)
+    if match:
+      major, minor = match.group(1), match.group(2)
+      version = major.rjust(2, '0') + minor.ljust(2, '0')
+      continue
+
+    match = XCODE_BUILD_PATTERN.search(line)
+    if match:
+      build = match.group(1)
+      continue
+
+  assert version is not None and build is not None
+  return version, build
+
+
+def ExtractSDKInfo(info, sdk):
+  """Extract information about the SDK."""
+  return subprocess.check_output(
+      ['xcrun', '--sdk', sdk, '--show-sdk-' + info]).strip()
+
+
+def GetSDKInfoForCpu(target_cpu, sdk_version, deployment_target):
+  """Returns a dictionary with information about the SDK."""
+  platform = GetPlatform(target_cpu)
+  sdk_version = sdk_version or ExtractSDKInfo('version', platform)
+  deployment_target = deployment_target or sdk_version
+
+  target = target_cpu + '-apple-ios' + deployment_target
+  if IsSimulator(target_cpu):
+    target = target + '-simulator'
+
+  xcode_version, xcode_build = ExtractXcodeInfo()
+  effective_sdk = platform + sdk_version
+
+  sdk_info = {}
+  sdk_info['compiler'] = 'com.apple.compilers.llvm.clang.1_0'
+  sdk_info['is_simulator'] = IsSimulator(target_cpu)
+  sdk_info['macos_build'] = ExtractOSVersion()
+  sdk_info['platform'] = platform
+  sdk_info['platform_name'] = GetPlaformDisplayName(target_cpu)
+  sdk_info['sdk'] = effective_sdk
+  sdk_info['sdk_build'] = ExtractSDKInfo('build-version', effective_sdk)
+  sdk_info['sdk_path'] = ExtractSDKInfo('path', effective_sdk)
+  sdk_info['sdk_version'] = sdk_version
+  sdk_info['target'] = target
+  sdk_info['xcode_build'] = xcode_build
+  sdk_info['xcode_version'] = xcode_version
+
+  return sdk_info
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-t', '--target-cpu', default='x64',
+      choices=('x86', 'x64', 'arm', 'arm64'),
+      help='target cpu')
+  parser.add_argument(
+      '-s', '--sdk-version',
+      help='version of the sdk')
+  parser.add_argument(
+      '-d', '--deployment-target',
+      help='iOS deployment target')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path of the output file to create; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  sdk_info = GetSDKInfoForCpu(
+      GetAppleCpuName(args.target_cpu),
+      args.sdk_version,
+      args.deployment_target)
+
+  if args.output == '-':
+    sys.stdout.write(json.dumps(sdk_info))
+  else:
+    with open(args.output, 'w') as output:
+      output.write(json.dumps(sdk_info))
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/sdk_info.gni b/examples/ios/build/config/ios/sdk_info.gni
new file mode 100644
index 0000000..90b8631
--- /dev/null
+++ b/examples/ios/build/config/ios/sdk_info.gni
@@ -0,0 +1,14 @@
+# Copyright 2019 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("//build/config/ios/deployment_target.gni")
+
+sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+                       [
+                         "--target-cpu",
+                         current_cpu,
+                         "--deployment-target",
+                         ios_deployment_target,
+                       ],
+                       "json")
diff --git a/examples/ios/build/config/ios/templates/ios_app_bundle.gni b/examples/ios/build/config/ios/templates/ios_app_bundle.gni
new file mode 100644
index 0000000..e83a79b
--- /dev/null
+++ b/examples/ios/build/config/ios/templates/ios_app_bundle.gni
@@ -0,0 +1,159 @@
+# Copyright 2019 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("//build/config/ios/bundle_identifier_prefix.gni")
+import("//build/config/ios/sdk_info.gni")
+import("//build/config/ios/templates/ios_binary_bundle.gni")
+import("//build/config/ios/templates/merge_plist.gni")
+
+# Template to generate an app bundle.
+#
+# All the other parameters are forwarded to a shared_library target that will
+# generate the bundle binary. In general, you want to pass at least "sources"
+# or "deps" to have some binary objects included in your shared library.
+#
+# Arguments
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+#   - bundle_identifier_prefix (optional)
+#
+#       prefix for the bundle identifier (the full identifier will be defined
+#       to $bundle_identifier_prefix.$output_name); if unset will defaults to
+#       default_bundle_identifier_prefix
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; defaults to $target_name
+#
+template("ios_app_bundle") {
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _bundle_identifier_prefix = default_bundle_identifier_prefix
+  if (defined(invoker.bundle_identifier_prefix)) {
+    _bundle_identifier_prefix = invoker.bundle_identifier_prefix
+  }
+
+  _bundle_identifier = "$_bundle_identifier_prefix.$_output_name"
+
+  _app_prefix_target = target_name + "_app_prefix"
+  _app_prefix_output = "$target_out_dir/$_app_prefix_target/app_prefix.json"
+
+  action(_app_prefix_target) {
+    script = "//build/config/ios/scripts/find_app_identifier_prefix.py"
+    sources = []
+    outputs = [
+      _app_prefix_output,
+    ]
+    args = [
+      "-b=" + _bundle_identifier,
+      "-o=" + rebase_path(_app_prefix_output, root_build_dir),
+    ]
+  }
+
+  if (sdk_info.is_simulator) {
+    _simu_xcent_target = target_name + "_simu_xcent"
+    _simu_xcent_output =
+        "$target_out_dir/$_simu_xcent_target/" + "Entitlements-Simulated.plist"
+
+    merge_plist(_simu_xcent_target) {
+      format = "xml1"
+      output = _simu_xcent_output
+      plists = [ "//build/config/ios/resources/Entitlements-Simulated.plist" ]
+      substitutions_json = _app_prefix_output
+      deps = [
+        ":$_app_prefix_target",
+      ]
+    }
+  }
+
+  _executable_target = target_name + "_executable"
+  _executable_bundle = target_name + "_executable_bundle"
+
+  executable(_executable_target) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "display_name",
+                             "info_plist",
+                             "output_name",
+                             "public_headers",
+                           ])
+
+    output_extension = ""
+    output_name = _output_name
+    output_prefix_override = true
+    output_dir = "$target_out_dir/$_executable_target"
+
+    if (sdk_info.is_simulator) {
+      if (!defined(deps)) {
+        deps = []
+      }
+      if (!defined(inputs)) {
+        inputs = []
+      }
+      if (!defined(ldflags)) {
+        ldflags = []
+      }
+
+      deps += [ ":$_simu_xcent_target" ]
+      inputs += [ _simu_xcent_output ]
+      ldflags += [
+        "-Xlinker",
+        "-sectcreate",
+        "-Xlinker",
+        "__TEXT",
+        "-Xlinker",
+        "__entitlements",
+        "-Xlinker",
+        rebase_path(_simu_xcent_output, root_build_dir),
+      ]
+    }
+  }
+
+  bundle_data(_executable_bundle) {
+    public_deps = [
+      ":$_executable_target",
+    ]
+    sources = [
+      "$target_out_dir/$_executable_target/$_output_name",
+    ]
+    outputs = [
+      "{{bundle_executable_dir}}/{{source_file_part}}",
+    ]
+  }
+
+  ios_binary_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "deps",
+                             "output_name",
+                             "public_deps",
+                             "public_headers",
+                           ])
+
+    output_name = _output_name
+    product_type = "com.apple.product-type.application"
+
+    bundle_identifier = _bundle_identifier
+    bundle_extension = "app"
+    bundle_type = "AAPL"
+
+    public_deps = [
+      ":$_executable_bundle",
+    ]
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/ios_binary_bundle.gni b/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
new file mode 100644
index 0000000..0089cd7
--- /dev/null
+++ b/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
@@ -0,0 +1,129 @@
+# Copyright 2019 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("//build/config/ios/templates/merge_plist.gni")
+
+# Template to create an Apple bundle containing a binary file (e.g. .app or
+# .framework bundle).
+#
+# Arguments
+#
+#   - bundle_extension
+#
+#       extension of the bundle (e.g. "app", "framework", ...); must not
+#       include the dot preceding the extension
+#
+#   - bundle_type
+#
+#       four letter code corresponding to the bundle type ("FMWK", "AAPL",
+#       ...); used to fill the "Bundle OS Type code" value in the generated
+#       Info.plist for the bundle
+#
+#   - bundle_identitier
+#
+#       bundle identitifier
+#
+#   - product_type
+#
+#       type of the generated bundle (used for Xcode project)
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; the bundle binary (i.e.
+#       the application or the library) must have the same name; defaults
+#       to $target_name
+#
+#   - display_name (optional)
+#
+#       display name of the bundle (e.g. the name that is displayed to the
+#       user); defaults to $output_name
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+template("ios_binary_bundle") {
+  assert(
+      defined(invoker.bundle_extension),
+      "bundle_extension must be defined for ios_binary_bundle ($target_name)")
+  assert(
+      defined(invoker.bundle_identifier),
+      "bundle_identifier must be defined for ios_binary_bundle ($target_name)")
+  assert(defined(invoker.bundle_type),
+         "bundle_type must be defined for ios_binary_bundle ($target_name)")
+  assert(defined(invoker.product_type),
+         "product_type must be defined for ios_binary_bundle ($target_name)")
+
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _display_name = _output_name
+  if (defined(invoker.display_name)) {
+    _display_name = invoker.display_name
+  }
+
+  _plist_target = target_name + "_plist"
+  _plist_bundle = target_name + "_plist_bundle"
+
+  merge_plist(_plist_target) {
+    substitutions = {
+      CURRENT_PROJECT_VERSION = "1"
+      DEVELOPMENT_LANGUAGE = "en"
+      EXECUTABLE_NAME = "$_output_name"
+      PRODUCT_BUNDLE_IDENTIFIER = invoker.bundle_identifier
+      PRODUCT_BUNDLE_PACKAGE_TYPE = invoker.bundle_type
+      PRODUCT_NAME = "$_display_name"
+    }
+
+    format = "binary1"
+    output = "$target_out_dir/$_plist_target/Info.plist"
+    plists = [
+      get_label_info("//build/config/ios:compiler_plist", "target_out_dir") +
+          "/compiler_plist/Info.plist",
+      "//build/config/ios/resources/Info.plist",
+    ]
+
+    if (defined(invoker.info_plist)) {
+      plists += [ invoker.info_plist ]
+    }
+
+    deps = [
+      "//build/config/ios:compiler_plist",
+    ]
+  }
+
+  bundle_data(_plist_bundle) {
+    public_deps = [
+      ":$_plist_target",
+    ]
+    sources = [
+      "$target_out_dir/$_plist_target/Info.plist",
+    ]
+    outputs = [
+      "{{bundle_contents_dir}}/Info.plist",
+    ]
+  }
+
+  create_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "display_name",
+                             "output_name",
+                             "bundle_extension",
+                             "bundle_type",
+                           ])
+
+    if (!defined(public_deps)) {
+      public_deps = []
+    }
+    public_deps += [ ":$_plist_bundle" ]
+    bundle_root_dir = "$root_out_dir/$_output_name.${invoker.bundle_extension}"
+    bundle_contents_dir = bundle_root_dir
+    bundle_executable_dir = bundle_contents_dir
+    bundle_resources_dir = bundle_contents_dir
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/ios_framework_bundle.gni b/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
new file mode 100644
index 0000000..471f4f3
--- /dev/null
+++ b/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
@@ -0,0 +1,172 @@
+# Copyright 2019 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("//build/config/ios/bundle_identifier_prefix.gni")
+import("//build/config/ios/templates/ios_binary_bundle.gni")
+
+# Template to generate a framework bundle.
+#
+# All the other parameters are forwarded to a shared_library target that will
+# generate the bundle binary. In general, you want to pass at least "sources"
+# or "deps" to have some binary objects included in your shared library.
+#
+# Arguments
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+#   - bundle_identifier_prefix (optional)
+#
+#       prefix for the bundle identifier (the full identifier will be defined
+#       to $bundle_identifier_prefix.$output_name); if unset will defaults to
+#       default_bundle_identifier_prefix
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; defaults to $target_name
+#
+#   - public_headers (optional)
+#
+#       list of public headers files to copy into the framework bundle; this
+#       does not generate an umbrella header; an umbrella header named after
+#       the framework bundle will be created
+#
+template("ios_framework_bundle") {
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _dylib_target = target_name + "_dylib"
+  _dylib_bundle = target_name + "_dylib_bundle"
+
+  _bundle_identifier_prefix = default_bundle_identifier_prefix
+  if (defined(invoker.bundle_identifier_prefix)) {
+    _bundle_identifier_prefix = invoker.bundle_identifier_prefix
+  }
+
+  _bundle_identifier = "$_bundle_identifier_prefix.$_output_name"
+
+  shared_library(_dylib_target) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "display_name",
+                             "info_plist",
+                             "output_name",
+                             "public_headers",
+                           ])
+
+    output_extension = ""
+    output_name = _output_name
+    output_prefix_override = true
+    output_dir = "$target_out_dir/$_dylib_target"
+
+    if (!defined(ldflags)) {
+      ldflags = []
+    }
+    ldflags += [
+      "-Xlinker",
+      "-install_name",
+      "-Xlinker",
+      "@rpath/$_output_name.framework/$_output_name",
+    ]
+  }
+
+  bundle_data(_dylib_bundle) {
+    public_deps = [
+      ":$_dylib_target",
+    ]
+    sources = [
+      "$target_out_dir/$_dylib_target/$_output_name",
+    ]
+    outputs = [
+      "{{bundle_executable_dir}}/{{source_file_part}}",
+    ]
+  }
+
+  if (defined(invoker.public_headers)) {
+    _umbrella_target = target_name + "_umbrella"
+    _umbrella_output = "$target_out_dir/$_umbrella_target/$_output_name.h"
+
+    action(_umbrella_target) {
+      script = "//build/config/ios/scripts/generate_umbrella_header.py"
+      sources = []
+      outputs = [
+        _umbrella_output,
+      ]
+      args = [ "-o=" + rebase_path(_umbrella_output, root_build_dir) ] +
+             rebase_path(invoker.public_headers, root_build_dir)
+    }
+
+    _headers_bundle = target_name + "_headers_bundle"
+
+    bundle_data(_headers_bundle) {
+      sources = invoker.public_headers + [ _umbrella_output ]
+      outputs = [
+        "{{bundle_resources_dir}}/Headers/{{source_file_part}}",
+      ]
+      public_deps = [
+        ":$_umbrella_target",
+      ]
+    }
+  }
+
+  _config_name = target_name + "_config"
+
+  config(_config_name) {
+    framework_dirs = [ root_out_dir ]
+    frameworks = [ "$_output_name.framework" ]
+  }
+
+  ios_binary_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_type",
+                             "configs",
+                             "deps",
+                             "output_name",
+                             "public_configs",
+                             "public_deps",
+                             "public_headers",
+                           ])
+
+    output_name = _output_name
+    product_type = "com.apple.product-type.framework"
+
+    bundle_identifier = _bundle_identifier
+    bundle_extension = "framework"
+    bundle_type = "FMWK"
+
+    public_deps = [
+      ":$_dylib_bundle",
+    ]
+
+    if (defined(invoker.public_headers)) {
+      public_deps += [ ":$_headers_bundle" ]
+    }
+
+    public_configs = [ ":$_config_name" ]
+  }
+
+  _target_name = target_name
+
+  bundle_data("$target_name+bundle") {
+    public_deps = [
+      ":$_target_name",
+    ]
+    sources = [
+      "$root_out_dir/$_output_name.framework",
+    ]
+    outputs = [
+      "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}",
+    ]
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/merge_plist.gni b/examples/ios/build/config/ios/templates/merge_plist.gni
new file mode 100644
index 0000000..61d3518
--- /dev/null
+++ b/examples/ios/build/config/ios/templates/merge_plist.gni
@@ -0,0 +1,89 @@
+# Copyright 2019 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.
+
+# Template to merge multiple Apple Property List file into a single file.
+#
+# Arguments
+#
+#   - output
+#
+#       path of the file that will be generated (must be in a sub-directory
+#       of root_build_dir)
+#
+#   - plists
+#
+#       list of path to Apple Property List file to merge (the file may be
+#       in either "json", "binary1" or "xml1" format)
+#
+#   - format (optional)
+#
+#       format in which the file must be saved; must be one of "json",
+#       "binary1" or "xml1" (default to "json" if omitted)
+#
+#   - substitutions (optional)
+#
+#       a scope defining variable substitutions to perform when merging the
+#       Property List files (i.e. if scope define foo = "bar", occurences
+#       of $(foo) in any string in a property list will be replaced by
+#       bar)
+#
+template("merge_plist") {
+  assert(defined(invoker.output) && invoker.output != "",
+         "output must be defined for merge_plist ($target_name)")
+  assert(defined(invoker.plists) && invoker.plists != [],
+         "plists must be defined for merge_plist ($target_name)")
+
+  if (defined(invoker.substitutions)) {
+    assert(!defined(invoker.substitutions_json),
+           "cannot define both substitutions and substitutions_json")
+
+    _substitutions_json = "$target_out_dir/$target_name/substitutions.json"
+    write_file(_substitutions_json, invoker.substitutions, "json")
+  }
+
+  if (defined(invoker.substitutions_json)) {
+    _substitutions_json = invoker.substitutions_json
+  }
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "args",
+                             "format",
+                             "inputs",
+                             "output",
+                             "plists",
+                             "script",
+                             "sources",
+                             "substitutions",
+                             "substitutions_json",
+                           ])
+
+    script = "//build/config/ios/scripts/merge_plist.py"
+    sources = invoker.plists
+    outputs = [
+      invoker.output,
+    ]
+
+    _format = "json"
+    if (defined(invoker.format)) {
+      _format = invoker.format
+    }
+
+    args = [
+      "-f=" + _format,
+      "-o=" + rebase_path(invoker.output, root_build_dir),
+    ]
+
+    if (defined(_substitutions_json)) {
+      inputs = [
+        _substitutions_json,
+      ]
+      args += [ "-s=" + rebase_path(_substitutions_json) ]
+    }
+
+    args += rebase_path(sources, root_build_dir)
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/storyboards.gni b/examples/ios/build/config/ios/templates/storyboards.gni
new file mode 100644
index 0000000..ecb5662
--- /dev/null
+++ b/examples/ios/build/config/ios/templates/storyboards.gni
@@ -0,0 +1,37 @@
+# Copyright 2019 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("//build/config/ios/deployment_target.gni")
+
+template("storyboards") {
+  assert(defined(invoker.sources),
+         "sources must be defined for storyboard ($target_name)")
+
+  _compile_target = target_name + "_compile"
+  _compile_output =
+      "$target_out_dir/$_compile_target/{{source_name_part}}.storyboardc"
+
+  action_foreach(_compile_target) {
+    script = "//build/config/ios/scripts/compile_storyboard.py"
+    sources = invoker.sources
+    outputs = [
+      _compile_output,
+    ]
+    args = [
+      "{{source}}",
+      "-o=" + rebase_path(_compile_output, root_build_dir),
+      "--minimum-deployment-target=$ios_deployment_target",
+    ]
+  }
+
+  bundle_data(target_name) {
+    public_deps = [
+      ":$_compile_target",
+    ]
+    sources = get_target_outputs(":$_compile_target")
+    outputs = [
+      "{{bundle_root_dir}}/Base.lproj/{{source_file_part}}",
+    ]
+  }
+}
diff --git a/examples/ios/build/toolchain/ios/BUILD.gn b/examples/ios/build/toolchain/ios/BUILD.gn
new file mode 100644
index 0000000..758f449
--- /dev/null
+++ b/examples/ios/build/toolchain/ios/BUILD.gn
@@ -0,0 +1,142 @@
+# Copyright 2019 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("//build/config/ios/deployment_target.gni")
+
+template("ios_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.toolchain_args),
+           "Toolchains must declare toolchain_args")
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+
+    _sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+                            [
+                              "--target-cpu",
+                              current_cpu,
+                              "--deployment-target",
+                              ios_deployment_target,
+                            ],
+                            "json")
+
+    cc = "clang -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
+    cxx = "clang++ -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
+
+    tool("link") {
+      output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = output + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [
+        output,
+      ]
+      command = "$cxx {{ldflags}} -o $output -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "LINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ""
+      output_prefix = ""
+    }
+
+    tool("solink") {
+      dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = dylib + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [
+        dylib,
+      ]
+      command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "SOLINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ".dylib"
+      output_prefix = "lib"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("objc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJC {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("objcxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJCXX {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy_bundle_data") {
+      command = "rm -rf {{output}} && cp -a {{source}} {{output}}"
+      description = "COPY_BUNDLE_DATA {{output}}"
+    }
+  }
+}
+
+ios_toolchain("clang_x86") {
+  toolchain_args = {
+    current_cpu = "x86"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_x64") {
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_arm") {
+  toolchain_args = {
+    current_cpu = "arm"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_arm64") {
+  toolchain_args = {
+    current_cpu = "arm64"
+    current_os = "ios"
+  }
+}
diff --git a/examples/ios/build/toolchain/mac/BUILD.gn b/examples/ios/build/toolchain/mac/BUILD.gn
new file mode 100644
index 0000000..30937dd
--- /dev/null
+++ b/examples/ios/build/toolchain/mac/BUILD.gn
@@ -0,0 +1,131 @@
+# Copyright 2019 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.
+
+template("mac_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.toolchain_args),
+           "Toolchains must declare toolchain_args")
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+
+    cc = "clang"
+    cxx = "clang++"
+
+    tool("link") {
+      output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = output + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [
+        output,
+      ]
+      command = "$cxx {{ldflags}} -o $output -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "LINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ""
+      output_prefix = ""
+    }
+
+    tool("solink") {
+      dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = dylib + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [
+        dylib,
+      ]
+      command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "SOLINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ".dylib"
+      output_prefix = "lib"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("objc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJC {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("objcxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJCXX {{output}}"
+      outputs = [
+        "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o",
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy_bundle_data") {
+      command = "rm -rf {{output}} && cp -a {{source}} {{output}}"
+      description = "COPY_BUNDLE_DATA {{output}}"
+    }
+  }
+}
+
+mac_toolchain("clang_x86") {
+  toolchain_args = {
+    current_cpu = "x86"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_x64") {
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_arm") {
+  toolchain_args = {
+    current_cpu = "arm"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_arm64") {
+  toolchain_args = {
+    current_cpu = "arm64"
+    current_os = "mac"
+  }
+}
diff --git a/examples/ios/host/BUILD.gn b/examples/ios/host/BUILD.gn
new file mode 100644
index 0000000..5bf2ea0
--- /dev/null
+++ b/examples/ios/host/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2019 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.
+
+if (current_toolchain == host_toolchain) {
+  executable("username") {
+    sources = [
+      "main.cc",
+    ]
+  }
+} else {
+  group("username") {
+    deps = [
+      ":username($host_toolchain)",
+    ]
+  }
+}
diff --git a/examples/ios/host/main.cc b/examples/ios/host/main.cc
new file mode 100644
index 0000000..1f244ef
--- /dev/null
+++ b/examples/ios/host/main.cc
@@ -0,0 +1,71 @@
+// Copyright 2019 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.
+
+#include <stdlib.h>
+#include <iostream>
+#include <string>
+
+namespace {
+
+// Returns the current user username.
+std::string Username() {
+  const char* username = getenv("USER");
+  return username ? std::string(username) : std::string();
+}
+
+// Writes |string| to |stream| while escaping all C escape sequences.
+void EscapeString(std::ostream* stream, const std::string& string) {
+  for (char c : string) {
+    switch (c) {
+      case 0:
+        *stream << "\\0";
+        break;
+      case '\a':
+        *stream << "\\a";
+        break;
+      case '\b':
+        *stream << "\\b";
+        break;
+      case '\e':
+        *stream << "\\e";
+        break;
+      case '\f':
+        *stream << "\\f";
+        break;
+      case '\n':
+        *stream << "\\n";
+        break;
+      case '\r':
+        *stream << "\\r";
+        break;
+      case '\t':
+        *stream << "\\t";
+        break;
+      case '\v':
+        *stream << "\\v";
+        break;
+      case '\\':
+        *stream << "\\\\";
+        break;
+      case '\"':
+        *stream << "\\\"";
+        break;
+      default:
+        *stream << c;
+        break;
+    }
+  }
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  std::string username = Username();
+
+  std::cout << "{\"username\": \"";
+  EscapeString(&std::cout, username);
+  std::cout << "\"}" << std::endl;
+
+  return 0;
+}
diff --git a/examples/ios/shared/BUILD.gn b/examples/ios/shared/BUILD.gn
new file mode 100644
index 0000000..f4641c3
--- /dev/null
+++ b/examples/ios/shared/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2019 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("//build/config/ios/templates/ios_framework_bundle.gni")
+
+ios_framework_bundle("hello_framework") {
+  output_name = "HelloShared"
+
+  sources = [
+    "hello_shared.h",
+    "hello_shared.m",
+  ]
+  public_headers = [ "hello_shared.h" ]
+
+  defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
+  frameworks = [ "Foundation.framework" ]
+}
diff --git a/examples/ios/shared/hello_shared.h b/examples/ios/shared/hello_shared.h
new file mode 100644
index 0000000..b351a50
--- /dev/null
+++ b/examples/ios/shared/hello_shared.h
@@ -0,0 +1,13 @@
+// Copyright 2019 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 <Foundation/Foundation.h>
+
+@interface Greetings : NSObject
+
++ (NSString*)greet;
+
++ (NSString*)greetWithName:(NSString*)name;
+
+@end
diff --git a/examples/ios/shared/hello_shared.m b/examples/ios/shared/hello_shared.m
new file mode 100644
index 0000000..5e81114
--- /dev/null
+++ b/examples/ios/shared/hello_shared.m
@@ -0,0 +1,22 @@
+// Copyright 2019 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 "hello_shared.h"
+
+@implementation Greetings
+
++ (NSString*)greet {
+  return [NSString stringWithFormat:@"Hello from %@!", [Greetings displayName]];
+}
+
++ (NSString*)greetWithName:(NSString*)name {
+  return [NSString stringWithFormat:@"Hello, %@!", name];
+}
+
++ (NSString*)displayName {
+  NSBundle* bundle = [NSBundle bundleForClass:[Greetings class]];
+  return [[bundle infoDictionary] objectForKey:@"CFBundleName"];
+}
+
+@end