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