diff --git a/swift/internal/action_names.bzl b/swift/internal/action_names.bzl index b3c91baf5..d57b27271 100644 --- a/swift/internal/action_names.bzl +++ b/swift/internal/action_names.bzl @@ -18,6 +18,9 @@ # object files. SWIFT_ACTION_COMPILE = "SwiftCompile" +# Compiles a `.swiftinterface` file into a `.swiftmodule` file. +SWIFT_ACTION_COMPILE_MODULE_INTERFACE = "SwiftCompileModuleInterface" + # Wraps a `.swiftmodule` in a `.o` file on ELF platforms so that it can be # linked into a binary for debugging. SWIFT_ACTION_MODULEWRAP = "SwiftModuleWrap" @@ -35,6 +38,7 @@ def all_action_names(): """A convenience function to return all actions defined by this rule set.""" return ( SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_MODULEWRAP, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, diff --git a/swift/internal/compiling.bzl b/swift/internal/compiling.bzl index 4f8ee34ec..5ae75a3df 100644 --- a/swift/internal/compiling.bzl +++ b/swift/internal/compiling.bzl @@ -20,6 +20,7 @@ load("@bazel_skylib//lib:types.bzl", "types") load( ":action_names.bzl", "SWIFT_ACTION_COMPILE", + "SWIFT_ACTION_COMPILE_MODULE_INTERFACE", "SWIFT_ACTION_PRECOMPILE_C_MODULE", ) load(":actions.bzl", "is_action_enabled", "run_toolchain_action") @@ -34,6 +35,7 @@ load( "SWIFT_FEATURE_OPT", "SWIFT_FEATURE_OPT_USES_WMO", "SWIFT_FEATURE_SUPPORTS_LIBRARY_EVOLUTION", + "SWIFT_FEATURE_SYSTEM_MODULE", "SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP", "SWIFT_FEATURE__NUM_THREADS_1_IN_SWIFTCOPTS", "SWIFT_FEATURE__WMO_IN_SWIFTCOPTS", @@ -134,6 +136,128 @@ def derive_module_name(*args): module_name = "_" + module_name return module_name +def compile_module_interface( + *, + actions, + compilation_contexts, + feature_configuration, + module_name, + swiftinterface_file, + swift_infos, + swift_toolchain): + """Compiles a Swift module interface. + + Args: + actions: The context's `actions` object. + compilation_contexts: A list of `CcCompilationContext`s that represent + C/Objective-C requirements of the target being compiled, such as + Swift-compatible preprocessor defines, header search paths, and so + forth. These are typically retrieved from the `CcInfo` providers of + a target's dependencies. + feature_configuration: A feature configuration obtained from + `swift_common.configure_features`. + module_name: The name of the Swift module being compiled. This must be + present and valid; use `swift_common.derive_module_name` to generate + a default from the target's label if needed. + swiftinterface_file: The Swift module interface file to compile. + swift_infos: A list of `SwiftInfo` providers from dependencies of the + target being compiled. + swift_toolchain: The `SwiftToolchainInfo` provider of the toolchain. + + Returns: + A Swift module context (as returned by `swift_common.create_module`) + that contains the Swift (and potentially C/Objective-C) compilation + prerequisites of the compiled module. This should typically be + propagated by a `SwiftInfo` provider of the calling rule, and the + `CcCompilationContext` inside the Clang module substructure should be + propagated by the `CcInfo` provider of the calling rule. + """ + swiftmodule_file = actions.declare_file("{}.swiftmodule".format(module_name)) + + merged_compilation_context = merge_compilation_contexts( + transitive_compilation_contexts = compilation_contexts + [ + cc_info.compilation_context + for cc_info in swift_toolchain.implicit_deps_providers.cc_infos + ], + ) + merged_swift_info = create_swift_info( + swift_infos = ( + swift_infos + swift_toolchain.implicit_deps_providers.swift_infos + ), + ) + + # Flattening this `depset` is necessary because we need to extract the + # module maps or precompiled modules out of structured values and do so + # conditionally. This should not lead to poor performance because the + # flattening happens only once as the action is being registered, rather + # than the same `depset` being flattened and re-merged multiple times up + # the build graph. + transitive_modules = merged_swift_info.transitive_modules.to_list() + transitive_swiftmodules = [] + for module in transitive_modules: + swift_module = module.swift + if not swift_module: + continue + transitive_swiftmodules.append(swift_module.swiftmodule) + + if is_feature_enabled( + feature_configuration = feature_configuration, + feature_name = SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP, + ): + explicit_swift_module_map_file = actions.declare_file( + "{}.swift-explicit-module-map.json".format(module_name), + ) + write_explicit_swift_module_map_file( + actions = actions, + explicit_swift_module_map_file = explicit_swift_module_map_file, + module_contexts = transitive_modules, + ) + else: + explicit_swift_module_map_file = None + + prerequisites = struct( + bin_dir = feature_configuration._bin_dir, + cc_compilation_context = merged_compilation_context, + explicit_swift_module_map_file = explicit_swift_module_map_file, + genfiles_dir = feature_configuration._genfiles_dir, + is_swift = True, + module_name = module_name, + source_files = [swiftinterface_file], + swiftmodule_file = swiftmodule_file, + transitive_modules = transitive_modules, + transitive_swiftmodules = transitive_swiftmodules, + user_compile_flags = [], + ) + + run_toolchain_action( + actions = actions, + action_name = SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + feature_configuration = feature_configuration, + outputs = [swiftmodule_file], + prerequisites = prerequisites, + progress_message = "Compiling Swift module {} from textual interface".format(module_name), + swift_toolchain = swift_toolchain, + ) + + module_context = create_module( + name = module_name, + clang = create_clang_module( + compilation_context = merged_compilation_context, + module_map = None, + ), + is_system = is_feature_enabled( + feature_configuration = feature_configuration, + feature_name = SWIFT_FEATURE_SYSTEM_MODULE, + ), + swift = create_swift_module( + swiftdoc = None, + swiftinterface = swiftinterface_file, + swiftmodule = swiftmodule_file, + ), + ) + + return module_context + def compile( *, actions, diff --git a/swift/swift_common.bzl b/swift/swift_common.bzl index 5b844d8ce..63ad5698f 100644 --- a/swift/swift_common.bzl +++ b/swift/swift_common.bzl @@ -32,6 +32,7 @@ load( load( "@build_bazel_rules_swift//swift/internal:compiling.bzl", "compile", + "compile_module_interface", "derive_module_name", "precompile_clang_module", ) @@ -71,6 +72,7 @@ swift_common = struct( cc_feature_configuration = get_cc_feature_configuration, compilation_attrs = swift_compilation_attrs, compile = compile, + compile_module_interface = compile_module_interface, configure_features = configure_features, create_clang_module = create_clang_module, create_linking_context_from_compilation_outputs = create_linking_context_from_compilation_outputs, diff --git a/swift/swift_import.bzl b/swift/swift_import.bzl index 92905f68c..8514ded5c 100644 --- a/swift/swift_import.bzl +++ b/swift/swift_import.bzl @@ -27,6 +27,7 @@ load( load( "@build_bazel_rules_swift//swift/internal:utils.bzl", "compact", + "get_compilation_contexts", "get_providers", ) load(":providers.bzl", "SwiftInfo") @@ -36,9 +37,14 @@ def _swift_import_impl(ctx): archives = ctx.files.archives deps = ctx.attr.deps swiftdoc = ctx.file.swiftdoc + swiftinterface = ctx.file.swiftinterface swiftmodule = ctx.file.swiftmodule - swift_toolchain = swift_common.get_toolchain(ctx) + if not (swiftinterface or swiftmodule): + fail("One or both of 'swiftinterface' and 'swiftmodule' must be " + + "specified.") + + swift_toolchain = swift_common.get_toolchain(ctx, attr = "toolchain") feature_configuration = swift_common.configure_features( ctx = ctx, swift_toolchain = swift_toolchain, @@ -69,21 +75,38 @@ def _swift_import_impl(ctx): cc_infos = [dep[CcInfo] for dep in deps if CcInfo in dep], ) - module_context = swift_common.create_module( - name = ctx.attr.module_name, - clang = swift_common.create_clang_module( - compilation_context = cc_info.compilation_context, - module_map = None, - ), - swift = swift_common.create_swift_module( - swiftdoc = swiftdoc, - swiftmodule = swiftmodule, - ), - ) + swift_infos = get_providers(deps, SwiftInfo) + + if swiftinterface and not swiftmodule: + module_context = swift_common.compile_module_interface( + actions = ctx.actions, + compilation_contexts = get_compilation_contexts(ctx.attr.deps), + feature_configuration = feature_configuration, + module_name = ctx.attr.module_name, + swiftinterface_file = swiftinterface, + swift_infos = swift_infos, + swift_toolchain = swift_toolchain, + ) + swift_outputs = [ + module_context.swift.swiftmodule, + ] + compact([module_context.swift.swiftdoc]) + else: + module_context = swift_common.create_module( + name = ctx.attr.module_name, + clang = swift_common.create_clang_module( + compilation_context = cc_info.compilation_context, + module_map = None, + ), + swift = swift_common.create_swift_module( + swiftdoc = swiftdoc, + swiftmodule = swiftmodule, + ), + ) + swift_outputs = [swiftmodule] + compact([swiftdoc]) providers = [ DefaultInfo( - files = depset(archives + [swiftmodule] + compact([swiftdoc])), + files = depset(archives + swift_outputs), runfiles = ctx.runfiles( collect_data = True, collect_default = True, @@ -105,7 +128,7 @@ def _swift_import_impl(ctx): ), swift_common.create_swift_info( modules = [module_context], - swift_infos = get_providers(deps, SwiftInfo), + swift_infos = swift_infos, ), ] @@ -117,12 +140,12 @@ swift_import = rule( swift_toolchain_attrs(), { "archives": attr.label_list( - allow_empty = False, + allow_empty = True, allow_files = ["a"], doc = """\ The list of `.a` files provided to Swift targets that depend on this target. """, - mandatory = True, + mandatory = False, ), "module_name": attr.string( doc = "The name of the module represented by this target.", @@ -132,6 +155,13 @@ The list of `.a` files provided to Swift targets that depend on this target. allow_single_file = ["swiftdoc"], doc = """\ The `.swiftdoc` file provided to Swift targets that depend on this target. +""", + mandatory = False, + ), + "swiftinterface": attr.label( + allow_single_file = ["swiftinterface"], + doc = """\ +The `.swiftinterface` file that defines the module interface for this target. """, mandatory = False, ), @@ -140,12 +170,12 @@ The `.swiftdoc` file provided to Swift targets that depend on this target. doc = """\ The `.swiftmodule` file provided to Swift targets that depend on this target. """, - mandatory = True, + mandatory = False, ), }, ), doc = """\ -Allows for the use of precompiled Swift modules as dependencies in other +Allows for the use of Swift textual module interfaces and/or precompiled Swift modules as dependencies in other `swift_library` and `swift_binary` targets. """, fragments = ["cpp"], diff --git a/swift/toolchains/BUILD b/swift/toolchains/BUILD index d398e2d1c..057e8a19c 100644 --- a/swift/toolchains/BUILD +++ b/swift/toolchains/BUILD @@ -18,6 +18,7 @@ bzl_library( "//swift/toolchains/config:action_config", "//swift/toolchains/config:all_actions_config", "//swift/toolchains/config:compile_config", + "//swift/toolchains/config:compile_module_interface_config", "//swift/toolchains/config:modulewrap_config", "//swift/toolchains/config:symbol_graph_config", "//swift/toolchains/config:tool_config", @@ -41,6 +42,7 @@ bzl_library( "//swift/toolchains/config:action_config", "//swift/toolchains/config:all_actions_config", "//swift/toolchains/config:compile_config", + "//swift/toolchains/config:compile_module_interface_config", "//swift/toolchains/config:symbol_graph_config", "//swift/toolchains/config:tool_config", "@bazel_skylib//lib:dicts", diff --git a/swift/toolchains/config/BUILD b/swift/toolchains/config/BUILD index 53dd8cbfa..844df922a 100644 --- a/swift/toolchains/config/BUILD +++ b/swift/toolchains/config/BUILD @@ -36,6 +36,16 @@ bzl_library( ], ) +bzl_library( + name = "compile_module_interface_config", + srcs = ["compile_module_interface_config.bzl"], + deps = [ + ":action_config", + "//swift/internal:action_names", + "//swift/internal:feature_names", + ], +) + bzl_library( name = "modulewrap_config", srcs = ["modulewrap_config.bzl"], diff --git a/swift/toolchains/config/compile_config.bzl b/swift/toolchains/config/compile_config.bzl index 7536cf875..9004e6fa3 100644 --- a/swift/toolchains/config/compile_config.bzl +++ b/swift/toolchains/config/compile_config.bzl @@ -19,6 +19,7 @@ load("@bazel_skylib//lib:types.bzl", "types") load( "@build_bazel_rules_swift//swift/internal:action_names.bzl", "SWIFT_ACTION_COMPILE", + "SWIFT_ACTION_COMPILE_MODULE_INTERFACE", "SWIFT_ACTION_PRECOMPILE_C_MODULE", "SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT", ) @@ -155,7 +156,9 @@ def compile_action_configs( # Configure library evolution and the path to the .swiftinterface file. ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + ], configurators = [add_arg("-enable-library-evolution")], features = [ SWIFT_FEATURE_SUPPORTS_LIBRARY_EVOLUTION, @@ -219,18 +222,27 @@ def compile_action_configs( # `-O` unless the `swift.opt_uses_osize` feature is enabled, then use # `-Osize`. ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [add_arg("-Onone")], features = [[SWIFT_FEATURE_DBG], [SWIFT_FEATURE_FASTBUILD]], ), ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [add_arg("-O")], features = [SWIFT_FEATURE_OPT], not_features = [SWIFT_FEATURE_OPT_USES_OSIZE], ), ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [add_arg("-Osize")], features = [SWIFT_FEATURE_OPT, SWIFT_FEATURE_OPT_USES_OSIZE], ), @@ -246,7 +258,9 @@ def compile_action_configs( # Enable or disable serialization of debugging options into # swiftmodules. ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + ], configurators = [ add_arg("-Xfrontend", "-no-serialize-debugging-options"), ], @@ -254,7 +268,9 @@ def compile_action_configs( not_features = [SWIFT_FEATURE_OPT], ), ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + ], configurators = [ add_arg("-Xfrontend", "-serialize-debugging-options"), ], @@ -353,6 +369,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -366,13 +383,19 @@ def compile_action_configs( # Configure how implicit modules are handled--either using the module # cache, or disabled completely when using explicit modules. ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [_global_module_cache_configurator], features = [SWIFT_FEATURE_USE_GLOBAL_MODULE_CACHE], not_features = [SWIFT_FEATURE_USE_C_MODULES], ), ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [ add_arg("-Xwrapped-swift=-ephemeral-module-cache"), ], @@ -387,6 +410,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -397,6 +421,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -460,7 +485,28 @@ def compile_action_configs( features = [SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP], ), ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [ + lambda prerequisites, args: _explicit_swift_module_map_configurator( + prerequisites, + args, + is_frontend = True, + ), + ], + features = [SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP], + ), + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [ + add_arg("-disable-implicit-swift-modules"), + ], + features = [SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP], + ), + ActionConfigInfo( + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [_dependencies_swiftmodules_configurator], not_features = [SWIFT_FEATURE_USE_EXPLICIT_SWIFT_MODULE_MAP], ), @@ -478,6 +524,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], configurators = [ @@ -506,6 +553,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -520,6 +568,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -529,6 +578,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -548,6 +598,12 @@ def compile_action_configs( ], configurators = [add_arg("-Xfrontend", "-color-diagnostics")], ), + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [ + add_arg("-color-diagnostics"), + ], + ), # Request batch mode if the compiler supports it. We only do this if the # user hasn't requested WMO in some fashion, because otherwise an @@ -631,7 +687,10 @@ def compile_action_configs( # `copts` attribute. action_configs.append( ActionConfigInfo( - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [_user_compile_flags_configurator], ), ) @@ -640,6 +699,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, ], configurators = [ @@ -656,7 +716,10 @@ def compile_action_configs( # TODO(allevato): Determine if there are any uses of # `-Xcc`-prefixed flags that need to be added to explicit module # actions, or if we should advise against/forbid that. - actions = [SWIFT_ACTION_COMPILE], + actions = [ + SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], configurators = [ lambda _, args: args.add_all(additional_swiftc_copts), ], @@ -667,6 +730,7 @@ def compile_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, ], configurators = [_source_files_configurator], @@ -1078,15 +1142,23 @@ def _dependencies_swiftmodules_configurator(prerequisites, args): inputs = prerequisites.transitive_swiftmodules, ) -def _explicit_swift_module_map_configurator(prerequisites, args): +def _explicit_swift_module_map_configurator(prerequisites, args, is_frontend = False): """Adds the explicit Swift module map file to the command line.""" - args.add_all( - [ + if is_frontend: + # If we're calling frontend directly we don't need to prepend each + # argument with -Xfrontend. Doing so will crash the invocation. + args.add( "-explicit-swift-module-map-file", prerequisites.explicit_swift_module_map_file, - ], - before_each = "-Xfrontend", - ) + ) + else: + args.add_all( + [ + "-explicit-swift-module-map-file", + prerequisites.explicit_swift_module_map_file, + ], + before_each = "-Xfrontend", + ) return ConfigResultInfo( inputs = prerequisites.transitive_swiftmodules + [ prerequisites.explicit_swift_module_map_file, diff --git a/swift/toolchains/config/compile_module_interface_config.bzl b/swift/toolchains/config/compile_module_interface_config.bzl new file mode 100644 index 000000000..b0de944bb --- /dev/null +++ b/swift/toolchains/config/compile_module_interface_config.bzl @@ -0,0 +1,52 @@ +# Copyright 2022 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. + +"""Common configuration for compile module interface actions.""" + +load( + "@build_bazel_rules_swift//swift/internal:action_names.bzl", + "SWIFT_ACTION_COMPILE_MODULE_INTERFACE", +) +load(":action_config.bzl", "ActionConfigInfo", "add_arg") + +def compile_module_interface_action_configs(): + return [ + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [add_arg("-enable-library-evolution")], + ), + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [_emit_module_path_from_module_interface_configurator], + ), + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [ + add_arg("-compile-module-from-interface"), + ], + ), + # Library evolution is implied since we've already produced a + # .swiftinterface file. So we want to unconditionally enable the flag + # for this action. + ActionConfigInfo( + actions = [ + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, + ], + configurators = [add_arg("-enable-library-evolution")], + ), + ] + +def _emit_module_path_from_module_interface_configurator(prerequisites, args): + """Adds the `.swiftmodule` output path to the command line.""" + args.add("-o", prerequisites.swiftmodule_file) diff --git a/swift/toolchains/xcode_swift_toolchain.bzl b/swift/toolchains/xcode_swift_toolchain.bzl index 0d70e091e..57cd6023e 100644 --- a/swift/toolchains/xcode_swift_toolchain.bzl +++ b/swift/toolchains/xcode_swift_toolchain.bzl @@ -29,6 +29,7 @@ load( load( "@build_bazel_rules_swift//swift/internal:action_names.bzl", "SWIFT_ACTION_COMPILE", + "SWIFT_ACTION_COMPILE_MODULE_INTERFACE", "SWIFT_ACTION_PRECOMPILE_C_MODULE", "SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT", ) @@ -81,6 +82,10 @@ load( "@build_bazel_rules_swift//swift/toolchains/config:compile_config.bzl", "compile_action_configs", ) +load( + "@build_bazel_rules_swift//swift/toolchains/config:compile_module_interface_config.bzl", + "compile_module_interface_action_configs", +) load( "@build_bazel_rules_swift//swift/toolchains/config:symbol_graph_config.bzl", "symbol_graph_action_configs", @@ -416,6 +421,7 @@ def _all_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -447,6 +453,7 @@ def _all_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], @@ -466,6 +473,7 @@ def _all_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, ], configurators = [add_arg("-embed-bitcode")], @@ -474,6 +482,7 @@ def _all_action_configs( ActionConfigInfo( actions = [ SWIFT_ACTION_COMPILE, + SWIFT_ACTION_COMPILE_MODULE_INTERFACE, SWIFT_ACTION_PRECOMPILE_C_MODULE, ], configurators = [add_arg("-embed-bitcode-marker")], @@ -500,6 +509,28 @@ def _all_action_configs( ), ) + # For `.swiftinterface` compilation actions, always pass the resource + # directory along with the target SDK version. These two flags together let + # the frontend infer the path to the prebuilt `.swiftmodule`s inside the + # toolchain. (This is necessary because we are invoking the frontend + # directly, so the driver doesn't do this for us as it normally would.) + action_configs.append( + ActionConfigInfo( + actions = [SWIFT_ACTION_COMPILE_MODULE_INTERFACE], + configurators = [ + _make_resource_directory_configurator( + apple_toolchain.developer_dir(), + ), + add_arg( + "-target-sdk-version", + str(xcode_config.sdk_version_for_platform( + _bazel_apple_platform(target_triple), + )), + ), + ], + ), + ) + action_configs.extend(all_actions_action_configs()) action_configs.extend(compile_action_configs( additional_objc_copts = additional_objc_copts, @@ -507,6 +538,7 @@ def _all_action_configs( generated_header_rewriter = generated_header_rewriter.executable, )) action_configs.extend(symbol_graph_action_configs()) + action_configs.extend(compile_module_interface_action_configs()) return action_configs @@ -573,6 +605,17 @@ def _all_tool_configs( use_param_file = True, worker_mode = "persistent", ), + SWIFT_ACTION_COMPILE_MODULE_INTERFACE: ToolConfigInfo( + driver_config = _driver_config(mode = "swiftc"), + args = ["-frontend"], + env = env, + execution_requirements = execution_requirements, + resource_set = _swift_compile_resource_set, + tool_input_manifests = generated_header_rewriter.input_manifests, + tool_inputs = generated_header_rewriter.inputs, + use_param_file = True, + worker_mode = "wrap", + ), } # Xcode 12.0 implies Swift 5.3. diff --git a/test/BUILD b/test/BUILD index ce5320309..8c8fe593d 100644 --- a/test/BUILD +++ b/test/BUILD @@ -3,6 +3,7 @@ load(":cc_library_tests.bzl", "cc_library_test_suite") load(":debug_settings_tests.bzl", "debug_settings_test_suite") load(":generated_header_tests.bzl", "generated_header_test_suite") load(":interop_hints_tests.bzl", "interop_hints_test_suite") +load(":module_interface_tests.bzl", "module_interface_test_suite") load(":private_deps_tests.bzl", "private_deps_test_suite") load(":symbol_graphs_tests.bzl", "symbol_graphs_test_suite") load(":swift_through_non_swift_tests.bzl", "swift_through_non_swift_test_suite") @@ -17,6 +18,8 @@ generated_header_test_suite(name = "generated_header") interop_hints_test_suite(name = "interop_hints") +module_interface_test_suite(name = "module_interface") + private_deps_test_suite(name = "private_deps") swift_through_non_swift_test_suite(name = "swift_through_non_swift") diff --git a/test/fixtures/module_interface/BUILD b/test/fixtures/module_interface/BUILD new file mode 100644 index 000000000..10083d23f --- /dev/null +++ b/test/fixtures/module_interface/BUILD @@ -0,0 +1,59 @@ +load( + "//swift:swift.bzl", + "swift_binary", + "swift_import", + "swift_library", +) +load( + "//test/fixtures:common.bzl", + "FIXTURE_TAGS", +) +load( + "//test/rules:swift_library_artifact_collector.bzl", + "swift_library_artifact_collector", +) + +package( + default_testonly = True, + default_visibility = ["//test:__subpackages__"], +) + +licenses(["notice"]) + +swift_binary( + name = "client", + srcs = ["Client.swift"], + tags = FIXTURE_TAGS, + deps = [":toy_module"], +) + +swift_import( + name = "toy_module", + archives = [":toy_outputs/libToyModule.a"], + module_name = "ToyModule", + swiftdoc = ":toy_outputs/ToyModule.swiftdoc", + swiftinterface = ":toy_outputs/ToyModule.swiftinterface", + tags = FIXTURE_TAGS, +) + +# Checking in pre-built artifacts like a `.swiftinterface` and static libraries +# would require different artifacts for every platform the test might run on. +# Instead, build it on-demand but forward the outputs using the "artifact +# collector" rule below to make them act as if they were pre-built outputs when +# referenced by the `swift_import` rule. + +swift_library( + name = "toy_module_library", + srcs = ["ToyModule.swift"], + module_name = "ToyModule", + tags = FIXTURE_TAGS, +) + +swift_library_artifact_collector( + name = "toy_module_artifact_collector", + static_library = "toy_outputs/libToyModule.a", + swiftdoc = "toy_outputs/ToyModule.swiftdoc", + swiftinterface = "toy_outputs/ToyModule.swiftinterface", + tags = FIXTURE_TAGS, + target = ":toy_module_library", +) diff --git a/test/fixtures/module_interface/Client.swift b/test/fixtures/module_interface/Client.swift new file mode 100644 index 000000000..0965e2e91 --- /dev/null +++ b/test/fixtures/module_interface/Client.swift @@ -0,0 +1,19 @@ +// Copyright 2022 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. + +import ToyModule + +let value = ToyValue(number: 10) +print(value.hexString) +print(value.squared()) diff --git a/test/fixtures/module_interface/ToyModule.swift b/test/fixtures/module_interface/ToyModule.swift new file mode 100644 index 000000000..def5bbb4a --- /dev/null +++ b/test/fixtures/module_interface/ToyModule.swift @@ -0,0 +1,37 @@ +// Copyright 2022 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. + +import Foundation + +/// A toy value. +@frozen +public struct ToyValue { + /// The numeric value. + public var number: Int + + /// The hexadecimal value of the numeric value. + public var hexString: String { + String(format: "0x%x", number) + } + + /// Creates a new toy value with the given numeric value. + public init(number: Int) { + self.number = number + } + + /// Returns the square of the receiver's numeric value. + public func squared() -> Int { + number * number + } +} diff --git a/test/module_interface_tests.bzl b/test/module_interface_tests.bzl new file mode 100644 index 000000000..a3c6b03de --- /dev/null +++ b/test/module_interface_tests.bzl @@ -0,0 +1,42 @@ +# Copyright 2022 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. + +"""Tests for interoperability with `cc_library`-specific features.""" + +load( + "@bazel_skylib//rules:build_test.bzl", + "build_test", +) + +def module_interface_test_suite(name): + """Test suite for features that compile Swift module interfaces. + + Args: + name: The base name to be used in targets created by this macro. + """ + + # Verify that a `swift_binary` builds properly when depending on a + # `swift_import` target that references a `.swiftinterface` file. + build_test( + name = "{}_swift_binary_imports_swiftinterface".format(name), + targets = [ + "@build_bazel_rules_swift//test/fixtures/module_interface:client", + ], + tags = [name], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/rules/swift_library_artifact_collector.bzl b/test/rules/swift_library_artifact_collector.bzl new file mode 100644 index 000000000..a836f3a1b --- /dev/null +++ b/test/rules/swift_library_artifact_collector.bzl @@ -0,0 +1,110 @@ +# Copyright 2022 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. + +"""A rule to collect the outputs of a `swift_library`. + +This rule is used in tests to simulate "pre-built" artifacts without having to +check them in directly. +""" + +load( + "@build_bazel_rules_swift//swift:swift.bzl", + "SwiftInfo", +) + +def _swiftinterface_transition_impl(_settings, attr): + # If the `.swiftinterface` file is requested, apply the setting that causes + # the rule to generate it. + return { + "@build_bazel_rules_swift//swift:emit_swiftinterface": attr.swiftinterface != None, + } + +_swiftinterface_transition = transition( + implementation = _swiftinterface_transition_impl, + inputs = [], + outputs = ["@build_bazel_rules_swift//swift:emit_swiftinterface"], +) + +def _copy_file(actions, source, destination): + """Copies the source file to the destination file. + + Args: + actions: The object used to register actions. + source: A `File` representing the file to be copied. + destination: A `File` representing the destination of the copy. + """ + args = actions.args() + args.add(source) + args.add(destination) + + actions.run_shell( + arguments = [args], + command = """\ +set -e +mkdir -p "$(dirname "$2")" +cp "$1" "$2" +""", + inputs = [source], + outputs = [destination], + ) + +def _swift_library_artifact_collector_impl(ctx): + target = ctx.attr.target[0] + swift_info = target[SwiftInfo] + + if ctx.outputs.static_library: + linker_inputs = target[CcInfo].linking_context.linker_inputs.to_list() + lib_to_link = linker_inputs[0].libraries[0] + _copy_file( + ctx.actions, + # based on build config one (but not both) of these will be present + source = lib_to_link.static_library or lib_to_link.pic_static_library, + destination = ctx.outputs.static_library, + ) + if ctx.outputs.swiftdoc: + _copy_file( + ctx.actions, + source = swift_info.direct_modules[0].swift.swiftdoc, + destination = ctx.outputs.swiftdoc, + ) + if ctx.outputs.swiftinterface: + _copy_file( + ctx.actions, + source = swift_info.direct_modules[0].swift.swiftinterface, + destination = ctx.outputs.swiftinterface, + ) + if ctx.outputs.swiftmodule: + _copy_file( + ctx.actions, + source = swift_info.direct_modules[0].swift.swiftmodule, + destination = ctx.outputs.swiftmodule, + ) + return [] + +swift_library_artifact_collector = rule( + attrs = { + "static_library": attr.output(mandatory = False), + "swiftdoc": attr.output(mandatory = False), + "swiftinterface": attr.output(mandatory = False), + "swiftmodule": attr.output(mandatory = False), + "target": attr.label( + cfg = _swiftinterface_transition, + providers = [[CcInfo, SwiftInfo]], + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + implementation = _swift_library_artifact_collector_impl, +)