[ios] Add example of building swift on iOS
The CL also adds necessary scaffolding to build some .swift files
in examples/ios (to teach how the tool needs to be used).
Bug: 121
Change-Id: I46b553c022484130b867547b2cdedc853689a46b
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9541
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/examples/ios/app/AppDelegate.m b/examples/ios/app/AppDelegate.m
index 32b3d44..b6f94fb 100644
--- a/examples/ios/app/AppDelegate.m
+++ b/examples/ios/app/AppDelegate.m
@@ -4,10 +4,13 @@
#import "app/AppDelegate.h"
+#import "app/Foo.h"
+
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+ NSLog(@"%@", [[[FooWrapper alloc] init] helloWithName:@"World"]);
return YES;
}
diff --git a/examples/ios/app/BUILD.gn b/examples/ios/app/BUILD.gn
index eb4b8a9..fdb3d03 100644
--- a/examples/ios/app/BUILD.gn
+++ b/examples/ios/app/BUILD.gn
@@ -27,6 +27,7 @@
]
deps = [
+ ":foo",
":storyboards",
"//shared:hello_framework",
"//shared:hello_framework+bundle",
@@ -39,3 +40,28 @@
"resources/Main.storyboard",
]
}
+
+source_set("baz") {
+ module_name = "Baz"
+ sources = [ "Baz.swift" ]
+}
+
+source_set("bar") {
+ module_name = "Bar"
+ sources = [ "Bar.swift" ]
+ deps = [ ":baz" ]
+}
+
+group("bar_indirect") {
+ public_deps = [ ":bar" ]
+}
+
+source_set("foo") {
+ module_name = "Foo"
+ bridge_header = "Foo-Bridging-Header.h"
+ sources = [
+ "Foo.swift",
+ "FooWrapper.swift",
+ ]
+ deps = [ ":bar_indirect" ]
+}
diff --git a/examples/ios/app/Bar.swift b/examples/ios/app/Bar.swift
new file mode 100644
index 0000000..9e35c9d
--- /dev/null
+++ b/examples/ios/app/Bar.swift
@@ -0,0 +1,8 @@
+
+import Baz;
+
+public class Greeter {
+ public static func greet(greeting: String, name: String, from: String) -> String {
+ return greeting + ", " + name + " (from " + from + ")";
+ }
+}
diff --git a/examples/ios/app/Baz.swift b/examples/ios/app/Baz.swift
new file mode 100644
index 0000000..f389578
--- /dev/null
+++ b/examples/ios/app/Baz.swift
@@ -0,0 +1,2 @@
+
+class Baz {}
diff --git a/examples/ios/app/Foo-Bridging-Header.h b/examples/ios/app/Foo-Bridging-Header.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/ios/app/Foo-Bridging-Header.h
diff --git a/examples/ios/app/Foo.swift b/examples/ios/app/Foo.swift
new file mode 100644
index 0000000..5e9faa1
--- /dev/null
+++ b/examples/ios/app/Foo.swift
@@ -0,0 +1,12 @@
+
+import Bar
+
+class Foo {
+ var name: String;
+ public init(name: String) {
+ self.name = name;
+ }
+ public func hello(name: String) -> String {
+ return Greeter.greet(greeting: "Hello", name: name, from: self.name);
+ }
+}
diff --git a/examples/ios/app/FooWrapper.swift b/examples/ios/app/FooWrapper.swift
new file mode 100644
index 0000000..a82c8a8
--- /dev/null
+++ b/examples/ios/app/FooWrapper.swift
@@ -0,0 +1,10 @@
+
+import Foundation;
+
+@objc
+public class FooWrapper : NSObject {
+ @objc
+ public func hello(name: String) -> String {
+ return Foo(name: "Foo").hello(name: name);
+ }
+}
diff --git a/examples/ios/build/BUILD.gn b/examples/ios/build/BUILD.gn
index aa51efa..2831117 100644
--- a/examples/ios/build/BUILD.gn
+++ b/examples/ios/build/BUILD.gn
@@ -2,6 +2,8 @@
# 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")
+
config("compiler") {
configs = [
":include_dirs",
@@ -9,11 +11,16 @@
":objc_use_arc",
":objc_abi_version",
]
+ cflags = [ "-g" ]
+ swiftflags = [ "-g" ]
}
config("shared_binary") {
if (current_os == "ios" || current_os == "mac") {
- configs = [ ":rpath_config" ]
+ configs = [
+ ":rpath_config",
+ ":swift_libdir",
+ ]
}
}
@@ -31,7 +38,7 @@
config("include_dirs") {
include_dirs = [
"//",
- root_out_dir,
+ root_gen_dir,
]
}
@@ -65,4 +72,17 @@
"@loader_path/Frameworks",
]
}
+
+ _sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+ [
+ "--target-cpu",
+ current_cpu,
+ "--deployment-target",
+ ios_deployment_target,
+ ],
+ "json")
+
+ config("swift_libdir") {
+ lib_dirs = [ "${_sdk_info.sdk_path}/usr/lib/swift" ]
+ }
}
diff --git a/examples/ios/build/toolchain/apple/swiftc.py b/examples/ios/build/toolchain/apple/swiftc.py
new file mode 100755
index 0000000..88ae6e5
--- /dev/null
+++ b/examples/ios/build/toolchain/apple/swiftc.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python3
+
+
+import argparse
+import collections
+import json
+import os
+import subprocess
+import sys
+import tempfile
+
+
+class OrderedSet(collections.OrderedDict):
+
+ def add(self, value):
+ self[value] = True
+
+
+def compile_module(module, sources, settings, extras, tmpdir):
+ output_file_map = {}
+ if settings.whole_module_optimization:
+ output_file_map[''] = {
+ 'object': os.path.join(settings.object_dir, module + '.o'),
+ 'dependencies': os.path.join(tmpdir, module + '.d'),
+ }
+ else:
+ for source in sources:
+ name, _ = os.path.splitext(os.path.basename(source))
+ output_file_map[source] = {
+ 'object': os.path.join(settings.object_dir, name + '.o'),
+ 'dependencies': os.path.join(tmpdir, name + '.d'),
+ }
+
+ for key in ('module_path', 'header_path', 'depfile'):
+ path = getattr(settings, key)
+ if os.path.exists(path):
+ os.unlink(path)
+ if key == 'module_path':
+ for ext in '.swiftdoc', '.swiftsourceinfo':
+ path = os.path.splitext(getattr(settings, key))[0] + ext
+ if os.path.exists(path):
+ os.unlink(path)
+ directory = os.path.dirname(path)
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ if not os.path.exists(settings.object_dir):
+ os.makedirs(settings.object_dir)
+
+ for key in output_file_map:
+ path = output_file_map[key]['object']
+ if os.path.exists(path):
+ os.unlink(path)
+
+ output_file_map_path = os.path.join(tmpdir, module + '.json')
+ with open(output_file_map_path, 'w') as output_file_map_file:
+ output_file_map_file.write(json.dumps(output_file_map))
+ output_file_map_file.flush()
+
+ extra_args = []
+ if settings.bridge_header:
+ extra_args.extend([
+ '-import-objc-header',
+ os.path.abspath(settings.bridge_header),
+ ])
+
+ if settings.whole_module_optimization:
+ extra_args.append('-whole-module-optimization')
+
+ if settings.target:
+ extra_args.extend([
+ '-target',
+ settings.target,
+ ])
+
+ if settings.sdk:
+ extra_args.extend([
+ '-sdk',
+ os.path.abspath(settings.sdk),
+ ])
+
+ if settings.include_dirs:
+ for include_dir in settings.include_dirs:
+ extra_args.append('-I' + include_dir)
+
+ process = subprocess.Popen(
+ ['swiftc',
+ '-parse-as-library',
+ '-module-name',
+ module,
+ '-emit-object',
+ '-emit-dependencies',
+ '-emit-module',
+ '-emit-module-path',
+ settings.module_path,
+ '-emit-objc-header',
+ '-emit-objc-header-path',
+ settings.header_path,
+ '-output-file-map',
+ output_file_map_path,
+ ] + extra_args + extras + sources,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+
+ stdout, stderr = process.communicate()
+ if process.returncode:
+ sys.stdout.write(stdout)
+ sys.stderr.write(stderr)
+ sys.exit(process.returncode)
+
+
+ depfile_content = collections.OrderedDict()
+ for key in output_file_map:
+ for line in open(output_file_map[key]['dependencies']):
+ output, inputs = line.split(' : ', 2)
+ _, ext = os.path.splitext(output)
+ if ext == '.o':
+ key = output
+ else:
+ key = os.path.splitext(settings.module_path)[0] + ext
+ if key not in depfile_content:
+ depfile_content[key] = OrderedSet()
+ for path in inputs.split():
+ depfile_content[key].add(path)
+
+ with open(settings.depfile, 'w') as depfile:
+ for key in depfile_content:
+ if not settings.depfile_filter or key in settings.depfile_filter:
+ inputs = depfile_content[key]
+ depfile.write('%s : %s\n' % (key, ' '.join(inputs)))
+
+
+def main(args):
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--module-name',
+ help='name of the Swift module')
+ parser.add_argument(
+ '--include', '-I', action='append', dest='include_dirs',
+ help='add directory to header search path')
+ parser.add_argument(
+ 'sources', nargs='+',
+ help='Swift source file to compile')
+ parser.add_argument(
+ '--whole-module-optimization', action='store_true',
+ help='enable whole module optimization')
+ parser.add_argument(
+ '--object-dir', '-o',
+ help='path to the generated object files directory')
+ parser.add_argument(
+ '--module-path', '-m',
+ help='path to the generated module file')
+ parser.add_argument(
+ '--header-path', '-h',
+ help='path to the generated header file')
+ parser.add_argument(
+ '--bridge-header', '-b',
+ help='path to the Objective-C bridge header')
+ parser.add_argument(
+ '--depfile', '-d',
+ help='path to the generated depfile')
+ parser.add_argument(
+ '--depfile-filter', action='append',
+ help='limit depfile to those files')
+ parser.add_argument(
+ '--target', action='store',
+ help='generate code for the given target <triple>')
+ parser.add_argument(
+ '--sdk', action='store',
+ help='compile against sdk')
+
+ parsed, extras = parser.parse_known_args(args)
+ with tempfile.TemporaryDirectory() as tmpdir:
+ compile_module(
+ parsed.module_name,
+ parsed.sources,
+ parsed,
+ extras,
+ tmpdir)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/toolchain/ios/BUILD.gn b/examples/ios/build/toolchain/ios/BUILD.gn
index b4e0869..12d246b 100644
--- a/examples/ios/build/toolchain/ios/BUILD.gn
+++ b/examples/ios/build/toolchain/ios/BUILD.gn
@@ -4,6 +4,12 @@
import("//build/config/ios/deployment_target.gni")
+declare_args() {
+ # Configure whether whole module optimization is enabled when compiling
+ # swift modules.
+ swift_whole_module_optimization = true
+}
+
template("ios_toolchain") {
toolchain(target_name) {
assert(defined(invoker.toolchain_args),
@@ -25,13 +31,15 @@
cc = "clang -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
cxx = "clang++ -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
+ swiftmodule_switch = "-Wl,-add_ast_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}}"
+ command = "$cxx {{ldflags}} -o $output -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}} {{swiftmodules}}"
description = "LINK {{output}}"
default_output_dir = "{{root_out_dir}}"
@@ -45,7 +53,7 @@
rspfile_content = "{{inputs_newline}}"
outputs = [ dylib ]
- command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+ command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}} {{swiftmodules}}"
description = "SOLINK {{output}}"
default_output_dir = "{{root_out_dir}}"
@@ -95,9 +103,38 @@
}
tool("copy_bundle_data") {
- command = "rm -rf {{output}} && cp -a {{source}} {{output}}"
+ command = "rm -rf {{output}} && cp -PR {{source}} {{output}}"
description = "COPY_BUNDLE_DATA {{output}}"
}
+
+ tool("swift") {
+ depfile = "{{target_out_dir}}/{{module_name}}.d"
+ depsformat = "gcc"
+
+ outputs = [
+ # The module needs to be the first output to ensure the
+ # depfile generate works correctly with ninja < 1.9.0.
+ "{{target_gen_dir}}/{{module_name}}.swiftmodule",
+
+ "{{target_gen_dir}}/{{module_name}}.h",
+ "{{target_gen_dir}}/{{module_name}}.swiftdoc",
+ "{{target_gen_dir}}/{{module_name}}.swiftsourceinfo",
+ ]
+
+ if (swift_whole_module_optimization) {
+ _extra_flag = "--whole-module-optimization"
+ _object_dir = "{{target_out_dir}}"
+ outputs += [ "{{target_out_dir}}/{{module_name}}.o" ]
+ } else {
+ _extra_flag = ""
+ _object_dir = "{{target_out_dir}}/{{label_name}}"
+ partial_outputs =
+ [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+ }
+
+ _swiftc = rebase_path("//build/toolchain/apple/swiftc.py", root_build_dir)
+ command = "$_swiftc --target ${_sdk_info.target} --sdk ${_sdk_info.sdk_path} --module-name {{module_name}} --object-dir $_object_dir --module-path {{target_gen_dir}}/{{module_name}}.swiftmodule --header-path {{target_gen_dir}}/{{module_name}}.h --depfile {{target_out_dir}}/{{module_name}}.d --depfile-filter {{target_gen_dir}}/{{module_name}}.swiftmodule --bridge-header {{bridge_header}} $_extra_flag {{defines}} {{swiftflags}} {{include_dirs}} {{module_dirs}} {{inputs}}"
+ }
}
}