diff --git a/swift/internal/providers.bzl b/swift/internal/providers.bzl index df72e425f..512b9cef4 100644 --- a/swift/internal/providers.bzl +++ b/swift/internal/providers.bzl @@ -18,25 +18,47 @@ visibility([ "@build_bazel_rules_swift//swift/...", ]) -SwiftCompilerPluginInfo = provider( - doc = "Information about compiler plugins, like macros.", +SwiftBinaryInfo = provider( + doc = """ +Information about a binary target's module. + +`swift_binary` and `swift_compiler_plugin` propagate this provider that wraps +`CcInfo` and `SwiftInfo` providers, instead of propagating them directly, so +that `swift_test` targets can depend on those binaries and test their modules +(similar to what Swift Package Manager allows) without allowing any +`swift_library` to depend on an arbitrary binary. +""", fields = { "cc_info": """\ -A `CcInfo` provider containing the `swift_compiler_plugin`'s code compiled as a -static library, which is suitable for linking into a `swift_test` so that unit -tests can be written against it. +A `CcInfo` provider containing the binary's code compiled as a static library, +which is suitable for linking into a `swift_test` so that unit tests can be +written against it. + +Notably, this `CcInfo`'s linking context does *not* contain the linker flags +used to alias the `main` entry point function, because the purpose of this +provider is to allow it to be linked into another binary that would provide its +own entry point instead. +""", + "swift_info": """\ +A `SwiftInfo` provider representing the Swift module created by compiling the +target. This is used specifically by `swift_test` to allow test code to depend +on the binary's module without making it possible for arbitrary libraries or +binaries to depend on other binaries. +""", + }, +) + +SwiftCompilerPluginInfo = provider( + doc = """ +Information about compiler plugins (like macros) that is needed by the compiler +when loading modules that declare those macros. """, + fields = { "executable": "A `File` representing the plugin's binary executable.", "module_names": """\ A `depset` of strings denoting the names of the Swift modules that provide plugin types looked up by the compiler. This currently contains a single element, the name of the module created by the `swift_compiler_plugin` target. -""", - "swift_info": """\ -A `SwiftInfo` provider representing the Swift module created by the -`swift_compiler_plugin` target. This is used specifically by `swift_test` to -allow test code to depend on the plugin's module without making it possible for -arbitrary libraries/binaries to depend on a plugin. """, }, ) diff --git a/swift/swift_binary.bzl b/swift/swift_binary.bzl index dbd3e8941..a4718c4f5 100644 --- a/swift/swift_binary.bzl +++ b/swift/swift_binary.bzl @@ -23,6 +23,7 @@ load( "@build_bazel_rules_swift//swift/internal:linking.bzl", "binary_rule_attrs", "configure_features_for_binary", + "create_linking_context_from_compilation_outputs", "malloc_linking_context", "register_link_binary_action", ) @@ -32,6 +33,7 @@ load( ) load( "@build_bazel_rules_swift//swift/internal:providers.bzl", + "SwiftBinaryInfo", "SwiftCompilerPluginInfo", ) load( @@ -70,6 +72,7 @@ def _swift_binary_impl(ctx): module_name = ctx.attr.module_name if not module_name: module_name = derive_swift_module_name(ctx.label) + entry_point_function_name = "{}_main".format(module_name) compile_result = compile( actions = ctx.actions, @@ -79,7 +82,15 @@ def _swift_binary_impl(ctx): ctx, ctx.attr.copts, ctx.attr.swiftc_inputs, - ), + ) + [ + # Use a custom entry point name so that the binary's code can + # also be linked into another process (like a test executable) + # without having its main function collide. + "-Xfrontend", + "-entry-point-function-name", + "-Xfrontend", + entry_point_function_name, + ], defines = ctx.attr.defines, feature_configuration = feature_configuration, module_name = module_name, @@ -96,6 +107,8 @@ def _swift_binary_impl(ctx): supplemental_outputs, ) else: + compile_result = None + entry_point_function_name = None compilation_outputs = cc_common.create_compilation_outputs() # Apply the optional debugging outputs extension if the toolchain defines @@ -109,6 +122,20 @@ def _swift_binary_impl(ctx): additional_debug_outputs = [] variables_extension = {} + binary_link_flags = expand_locations( + ctx, + ctx.attr.linkopts, + ctx.attr.swiftc_inputs, + ) + ctx.fragments.cpp.linkopts + + # When linking the binary, make sure we use the correct entry point name. + if entry_point_function_name: + entry_point_linkopts = swift_toolchain.entry_point_linkopts_provider( + entry_point_name = entry_point_function_name, + ).linkopts + else: + entry_point_linkopts = [] + linking_outputs = register_link_binary_action( actions = ctx.actions, additional_inputs = ctx.files.swiftc_inputs, @@ -122,15 +149,11 @@ def _swift_binary_impl(ctx): output_type = "executable", stamp = ctx.attr.stamp, swift_toolchain = swift_toolchain, - user_link_flags = expand_locations( - ctx, - ctx.attr.linkopts, - ctx.attr.swiftc_inputs, - ) + ctx.fragments.cpp.linkopts, + user_link_flags = binary_link_flags + entry_point_linkopts, variables_extension = variables_extension, ) - return [ + providers = [ DefaultInfo( executable = linking_outputs.executable, files = depset( @@ -145,6 +168,44 @@ def _swift_binary_impl(ctx): OutputGroupInfo(**output_groups), ] + # Only create a linking context and propagate `SwiftBinaryInfo` if this rule + # compiled something (i.e., it had sources). If it didn't, then there's + # nothing to allow testing against. + if compile_result: + linking_context, _ = ( + create_linking_context_from_compilation_outputs( + actions = ctx.actions, + additional_inputs = ctx.files.swiftc_inputs, + alwayslink = True, + compilation_outputs = compilation_outputs, + feature_configuration = feature_configuration, + label = ctx.label, + linking_contexts = [ + dep[CcInfo].linking_context + for dep in ctx.attr.deps + if CcInfo in dep + ], + module_context = compile_result.module_context, + swift_toolchain = swift_toolchain, + # Exclude the entry point linkopts from this linking context, + # because it is meant to be used by other binary rules that + # provide their own entry point while linking this "binary" in + # as a library. + user_link_flags = binary_link_flags, + ) + ) + providers.append(SwiftBinaryInfo( + cc_info = CcInfo( + compilation_context = ( + compile_result.module_context.clang.compilation_context + ), + linking_context = linking_context, + ), + swift_info = compile_result.swift_info, + )) + + return providers + swift_binary = rule( attrs = dicts.add( binary_rule_attrs(stamp_default = -1), diff --git a/swift/swift_compiler_plugin.bzl b/swift/swift_compiler_plugin.bzl index 352b4fc5b..0cc7378fb 100644 --- a/swift/swift_compiler_plugin.bzl +++ b/swift/swift_compiler_plugin.bzl @@ -33,6 +33,7 @@ load( ) load( "@build_bazel_rules_swift//swift/internal:providers.bzl", + "SwiftBinaryInfo", "SwiftCompilerPluginInfo", ) load( @@ -179,14 +180,16 @@ def _swift_compiler_plugin_impl(ctx): OutputGroupInfo( **supplemental_compilation_output_groups(supplemental_outputs) ), - SwiftCompilerPluginInfo( + SwiftBinaryInfo( cc_info = CcInfo( compilation_context = module_context.clang.compilation_context, linking_context = linking_context, ), + swift_info = compile_result.swift_info, + ), + SwiftCompilerPluginInfo( executable = binary_linking_outputs.executable, module_names = depset([module_name]), - swift_info = compile_result.swift_info, ), ] diff --git a/swift/swift_test.bzl b/swift/swift_test.bzl index 7360f62a5..170937ece 100644 --- a/swift/swift_test.bzl +++ b/swift/swift_test.bzl @@ -32,6 +32,7 @@ load( ) load( "@build_bazel_rules_swift//swift/internal:providers.bzl", + "SwiftBinaryInfo", "SwiftCompilerPluginInfo", ) load( @@ -323,8 +324,8 @@ def _swift_test_impl(ctx): else: additional_link_deps = [] - # We also need to collect nested providers from `SwiftCompilerPluginInfo` - # since we support testing those. + # We also need to collect nested providers from `SwiftBinaryInfo` since we + # support testing those. deps_compilation_contexts = [] deps_swift_infos = [] plugin_linking_contexts = [] @@ -333,8 +334,8 @@ def _swift_test_impl(ctx): deps_compilation_contexts.append(dep[CcInfo].compilation_context) if SwiftInfo in dep: deps_swift_infos.append(dep[SwiftInfo]) - if SwiftCompilerPluginInfo in dep: - plugin_info = dep[SwiftCompilerPluginInfo] + if SwiftBinaryInfo in dep: + plugin_info = dep[SwiftBinaryInfo] deps_compilation_contexts.append( plugin_info.cc_info.compilation_context, )