# 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 _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) 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 """