| # Copyright 2016 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Skylark rules for Swift.""" |
| |
| load("shared", |
| "xcrun_action", |
| "XCRUNWRAPPER_LABEL", |
| "module_cache_path", |
| "label_scoped_path") |
| |
| def _parent_dirs(dirs): |
| """Returns a set of parent directories for each directory in dirs.""" |
| return set([f.rpartition("/")[0] for f in dirs]) |
| |
| def _intersperse(separator, iterable): |
| """Inserts separator before each item in iterable.""" |
| result = [] |
| for x in iterable: |
| result.append(separator) |
| result.append(x) |
| |
| return result |
| |
| def _swift_target(cpu, platform, sdk_version): |
| """Returns a target triplet for Swift compiler.""" |
| platform_string = str(platform.platform_type) |
| if platform_string not in ["ios", "watchos"]: |
| fail("Platform '%s' is not supported" % platform_string) |
| |
| return "%s-apple-%s%s" % (cpu, platform_string, sdk_version) |
| |
| def _swift_compilation_mode_flags(ctx): |
| """Returns additional swiftc flags for the current compilation mode.""" |
| mode = ctx.var["COMPILATION_MODE"] |
| if mode == "dbg": |
| return ["-Onone", "-DDEBUG", "-g", "-enable-testing"] |
| elif mode == "fastbuild": |
| return ["-Onone", "-DDEBUG", "-enable-testing"] |
| elif mode == "opt": |
| return ["-O", "-DNDEBUG"] |
| |
| def _clang_compilation_mode_flags(ctx): |
| """Returns additional clang flags for the current compilation mode.""" |
| |
| # In general, every compilation mode flag from native objc_ rules should be |
| # passed, but -g seems to break Clang module compilation. Since this flag does |
| # not make much sense for module compilation and only touches headers, |
| # it's ok to omit. |
| native_clang_flags = ctx.fragments.objc.copts_for_current_compilation_mode |
| |
| return [x for x in native_clang_flags if x != "-g"] |
| |
| def _swift_bitcode_flags(ctx): |
| """Returns bitcode flags based on selected mode.""" |
| # TODO(dmishe): Remove this when bitcode_mode is available by default. |
| if hasattr(ctx.fragments.apple, "bitcode_mode"): |
| mode = str(ctx.fragments.apple.bitcode_mode) |
| if mode == "embedded": |
| return ["-embed-bitcode"] |
| elif mode == "embedded_markers": |
| return ["-embed-bitcode-marker"] |
| |
| return [] |
| |
| def _module_name(ctx): |
| """Returns a module name for the given rule context.""" |
| return ctx.label.package.lstrip("//").replace("/", "_") + "_" + ctx.label.name |
| |
| def _swift_library_impl(ctx): |
| """Implementation for swift_library Skylark rule.""" |
| # TODO(b/29772303): Assert xcode version. |
| apple_fragment = ctx.fragments.apple |
| |
| cpu = apple_fragment.single_arch_cpu |
| platform = apple_fragment.single_arch_platform |
| |
| target_os = ctx.fragments.objc.ios_minimum_os |
| target = _swift_target(cpu, platform, target_os) |
| apple_toolchain = apple_common.apple_toolchain() |
| |
| module_name = ctx.attr.module_name or _module_name(ctx) |
| |
| # A list of paths to pass with -F flag. |
| framework_dirs = set([ |
| apple_toolchain.platform_developer_framework_dir(apple_fragment)]) |
| |
| # Collect transitive dependecies. |
| dep_modules = [] |
| dep_libs = [] |
| |
| swift_providers = [x.swift for x in ctx.attr.deps if hasattr(x, "swift")] |
| objc_providers = [x.objc for x in ctx.attr.deps if hasattr(x, "objc")] |
| |
| for swift in swift_providers: |
| dep_libs += swift.transitive_libs |
| dep_modules += swift.transitive_modules |
| |
| objc_includes = set() # Everything that needs to be included with -I |
| objc_files = set() # All inputs required for the compile action |
| objc_module_maps = set() # Module maps for dependent targets |
| objc_defines = set() |
| for objc in objc_providers: |
| objc_includes += objc.include |
| |
| objc_files += objc.header |
| objc_files += objc.module_map |
| |
| objc_module_maps += objc.module_map |
| |
| files = set(objc.static_framework_file) + set(objc.dynamic_framework_file) |
| objc_files += files |
| framework_dirs += _parent_dirs(objc.framework_dir) |
| |
| # objc_library#copts is not propagated to its dependencies and so it is not |
| # collected here. In theory this may lead to un-importable targets (since |
| # their module cannot be compiled by clang), but did not occur in practice. |
| objc_defines += objc.define |
| |
| # A unique path for rule's outputs. |
| objs_outputs_path = label_scoped_path(ctx, "_objs/") |
| |
| output_lib = ctx.new_file(objs_outputs_path + module_name + ".a") |
| output_module = ctx.new_file(objs_outputs_path + module_name + ".swiftmodule") |
| |
| # These filenames are guaranteed to be unique, no need to scope. |
| output_header = ctx.new_file(ctx.label.name + "-Swift.h") |
| swiftc_output_map_file = ctx.new_file(ctx.label.name + ".output_file_map.json") |
| |
| swiftc_output_map = struct() # Maps output types to paths. |
| output_objs = [] # Object file outputs, used in archive action. |
| swiftc_outputs = [] # Other swiftc outputs that aren't processed further. |
| for source in ctx.files.srcs: |
| basename = source.basename |
| obj = ctx.new_file(objs_outputs_path + basename + ".o") |
| partial_module = ctx.new_file(objs_outputs_path + basename + |
| ".partial_swiftmodule") |
| output_objs.append(obj) |
| swiftc_outputs.append(partial_module) |
| |
| swiftc_output_map += struct(**{ |
| source.path: struct(object=obj.path, swiftmodule=partial_module.path)}) |
| |
| # Write down the intermediate outputs map for this compilation, to be used |
| # with -output-file-map flag. |
| # It's a JSON file that maps each source input (.swift) to its outputs |
| # (.o, .bc, .d, ...) |
| # Example: |
| # {'foo.swift': |
| # {'object': 'foo.o', 'bitcode': 'foo.bc', 'dependencies': 'foo.d'}} |
| # There's currently no documentation on this option, however all of the keys |
| # are listed here https://github.com/apple/swift/blob/swift-2.2.1-RELEASE/include/swift/Driver/Types.def |
| ctx.file_action(output=swiftc_output_map_file, content=swiftc_output_map.to_json()) |
| |
| srcs_args = [f.path for f in ctx.files.srcs] |
| |
| # Include each swift module's parent directory for imports to work. |
| include_dirs = set([x.dirname for x in dep_modules]) |
| |
| # Include the parent directory of the resulting module so LLDB can find it. |
| include_dirs += set([output_module.dirname]) |
| |
| include_args = ["-I%s" % d for d in include_dirs + objc_includes] |
| framework_args = ["-F%s" % x for x in framework_dirs] |
| define_args = ["-D%s" % x for x in ctx.attr.defines] |
| |
| clang_args = _intersperse( |
| "-Xcc", |
| |
| # Add the current directory to clang's search path. |
| # This instance of clang is spawned by swiftc to compile module maps and |
| # is not passed the current directory as a search path by default. |
| ["-iquote", "."] |
| |
| # Pass DEFINE or copt values from objc configuration and rules to clang |
| + ["-D" + x for x in objc_defines] + ctx.fragments.objc.copts |
| + _clang_compilation_mode_flags(ctx) |
| |
| # Load module maps explicitly instead of letting Clang discover them on |
| # search paths. This is needed to avoid a case where Clang may load the |
| # same header both in modular and non-modular contexts, leading to |
| # duplicate definitions in the same file. |
| # https://llvm.org/bugs/show_bug.cgi?id=19501 |
| + ["-fmodule-map-file=%s" % x.path for x in objc_module_maps]) |
| |
| args = [ |
| "swiftc", |
| "-emit-object", |
| "-emit-module-path", |
| output_module.path, |
| "-module-name", |
| module_name, |
| "-emit-objc-header-path", |
| output_header.path, |
| "-parse-as-library", |
| "-target", |
| target, |
| "-sdk", |
| apple_toolchain.sdk_dir(), |
| "-module-cache-path", |
| module_cache_path(ctx), |
| "-output-file-map", |
| swiftc_output_map_file.path, |
| ] + _swift_compilation_mode_flags(ctx) + _swift_bitcode_flags(ctx) |
| |
| args.extend(srcs_args) |
| args.extend(include_args) |
| args.extend(framework_args) |
| args.extend(clang_args) |
| args.extend(define_args) |
| args.extend(ctx.attr.copts) |
| |
| xcrun_action( |
| ctx, |
| inputs=ctx.files.srcs + dep_modules + list(objc_files) + |
| [swiftc_output_map_file], |
| outputs=[output_module, output_header] + output_objs + swiftc_outputs, |
| mnemonic="SwiftCompile", |
| arguments=args, |
| use_default_shell_env=False, |
| progress_message=("Compiling Swift module %s (%d files)" % |
| (ctx.label.name, len(ctx.files.srcs)))) |
| |
| xcrun_action(ctx, |
| inputs=output_objs, |
| outputs=(output_lib,), |
| mnemonic="SwiftArchive", |
| arguments=[ |
| "libtool", "-static", "-o", output_lib.path |
| ] + [x.path for x in output_objs], |
| progress_message=("Archiving Swift objects %s" % ctx.label.name)) |
| |
| objc_provider = apple_common.new_objc_provider( |
| library=set([output_lib] + dep_libs), |
| header=set([output_header]), |
| providers=objc_providers, |
| uses_swift=True) |
| |
| return struct( |
| swift=struct( |
| transitive_libs=[output_lib] + dep_libs, |
| transitive_modules=[output_module] + dep_modules), |
| objc=objc_provider, |
| files=set([output_lib, output_module, output_header])) |
| |
| swift_library = rule( |
| _swift_library_impl, |
| attrs = { |
| "srcs": attr.label_list(allow_files = [".swift"]), |
| "deps": attr.label_list(providers=[["swift"], ["objc"]]), |
| "module_name": attr.string(mandatory=False), |
| "defines": attr.string_list(mandatory=False, allow_empty=True), |
| "copts": attr.string_list(mandatory=False, allow_empty=True), |
| "_xcrunwrapper": attr.label( |
| executable=True, |
| cfg="host", |
| default=Label(XCRUNWRAPPER_LABEL))}, |
| fragments = ["apple", "objc"], |
| output_to_genfiles=True, |
| ) |
| """ |
| Builds a Swift module. |
| |
| A module is a pair of static library (.a) + module header (.swiftmodule). |
| Dependant targets can import this module as "import RuleName". |
| |
| Args: |
| srcs: Swift sources that comprise this module. |
| deps: Other Swift modules. |
| module_name: Optional. Sets the Swift module name for this target. By default |
| the module name is the target path with all special symbols replaced |
| by "_", e.g. //foo:bar can be imported as "foo_bar". |
| copts: A list of flags passed to swiftc command line. |
| defines: A list of values for build configuration options (-D). These values |
| can be then used for conditional compilation blocks in code. For example: |
| |
| BUILD: |
| swift_library( |
| defines = ["VALUE"] |
| ) |
| |
| Code: |
| #if VALUE |
| #endif |
| """ |