[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}}" + } } }