Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow testing of swift_binary targets #1400

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions swift/internal/attrs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("//swift:providers.bzl", "SwiftInfo")
load(":providers.bzl", "SwiftCompilerPluginInfo")
load(":providers.bzl", "SwiftBinaryInfo", "SwiftCompilerPluginInfo")

def swift_common_rule_attrs(
additional_deps_aspects = [],
Expand Down Expand Up @@ -147,7 +147,7 @@ Swift 5.9+.
A list of `swift_compiler_plugin` targets that should be loaded by the compiler
when compiling this module and any modules that directly depend on it.
""",
providers = [[SwiftCompilerPluginInfo]],
providers = [[SwiftBinaryInfo, SwiftCompilerPluginInfo]],
),
"srcs": attr.label_list(
allow_empty = not requires_srcs,
Expand Down
42 changes: 31 additions & 11 deletions swift/internal/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,45 @@

"""Internal providers."""

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.
""",
},
)
Expand Down
80 changes: 72 additions & 8 deletions swift/swift_binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,19 @@ load(
"//swift/internal:linking.bzl",
"binary_rule_attrs",
"configure_features_for_binary",
"create_linking_context_from_compilation_outputs",
"malloc_linking_context",
"register_link_binary_action",
)
load(
"//swift/internal:output_groups.bzl",
"supplemental_compilation_output_groups",
)
load("//swift/internal:providers.bzl", "SwiftCompilerPluginInfo")
load(
"//swift/internal:providers.bzl",
"SwiftBinaryInfo",
"SwiftCompilerPluginInfo",
)
load(
"//swift/internal:toolchain_utils.bzl",
"get_swift_toolchain",
Expand Down Expand Up @@ -88,6 +93,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)

include_dev_srch_paths = include_developer_search_paths(ctx.attr)

Expand All @@ -99,7 +105,15 @@ def _swift_binary_impl(ctx):
ctx,
ctx.attr.copts,
ctx.attr.swiftc_inputs,
) + _maybe_parse_as_library_copts(srcs),
) + _maybe_parse_as_library_copts(srcs) + [
# 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,
include_dev_srch_paths = include_dev_srch_paths,
Expand All @@ -120,6 +134,8 @@ def _swift_binary_impl(ctx):
supplemental_outputs,
)
else:
compile_result = None
entry_point_function_name = None
compilation_outputs = cc_common.create_compilation_outputs()

additional_linking_contexts.append(malloc_linking_context(ctx))
Expand All @@ -135,6 +151,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 = []

if is_feature_enabled(
feature_configuration = feature_configuration,
feature_name = SWIFT_FEATURE_ADD_TARGET_NAME_TO_OUTPUT,
Expand All @@ -157,15 +187,11 @@ def _swift_binary_impl(ctx):
owner = ctx.label,
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(
Expand All @@ -191,6 +217,44 @@ def _swift_binary_impl(ctx):
),
]

# 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(
Expand Down
28 changes: 18 additions & 10 deletions swift/swift_compiler_plugin.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ load(
"//swift/internal:output_groups.bzl",
"supplemental_compilation_output_groups",
)
load("//swift/internal:providers.bzl", "SwiftCompilerPluginInfo")
load(
"//swift/internal:providers.bzl",
"SwiftBinaryInfo",
"SwiftCompilerPluginInfo",
)
load(
"//swift/internal:toolchain_utils.bzl",
"get_swift_toolchain",
Expand Down Expand Up @@ -196,14 +200,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,
),
]

Expand Down Expand Up @@ -305,10 +311,10 @@ def _universal_swift_compiler_plugin_impl(ctx):
module_name = None
swift_infos = []
for plugin in ctx.split_attr.plugin.values():
cc_infos.append(plugin[SwiftCompilerPluginInfo].cc_info)
direct_swift_modules.extend(plugin[SwiftCompilerPluginInfo].swift_info.direct_modules)
cc_infos.append(plugin[SwiftBinaryInfo].cc_info)
direct_swift_modules.extend(plugin[SwiftBinaryInfo].swift_info.direct_modules)
module_name = plugin[SwiftCompilerPluginInfo].module_names.to_list()[0]
swift_infos.append(plugin[SwiftCompilerPluginInfo].swift_info)
swift_infos.append(plugin[SwiftBinaryInfo].swift_info)

first_output_group_info = ctx.split_attr.plugin.values()[0][OutputGroupInfo]
combined_output_group_info = {}
Expand All @@ -330,15 +336,17 @@ def _universal_swift_compiler_plugin_impl(ctx):
runfiles = ctx.runfiles().merge_all(transitive_runfiles),
),
OutputGroupInfo(**combined_output_group_info),
SwiftCompilerPluginInfo(
SwiftBinaryInfo(
cc_info = cc_common.merge_cc_infos(cc_infos = cc_infos),
executable = output,
module_names = depset([module_name]),
swift_info = SwiftInfo(
modules = direct_swift_modules,
swift_infos = swift_infos,
),
),
SwiftCompilerPluginInfo(
executable = output,
module_names = depset([module_name]),
),
]

universal_swift_compiler_plugin = rule(
Expand All @@ -349,7 +357,7 @@ universal_swift_compiler_plugin = rule(
cfg = macos_universal_transition,
doc = "Target to generate a 'fat' binary from.",
mandatory = True,
providers = [SwiftCompilerPluginInfo],
providers = [[SwiftBinaryInfo, SwiftCompilerPluginInfo]],
),
"_allowlist_function_transition": attr.label(
default = Label(
Expand Down
14 changes: 9 additions & 5 deletions swift/swift_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ load(
"//swift/internal:output_groups.bzl",
"supplemental_compilation_output_groups",
)
load("//swift/internal:providers.bzl", "SwiftCompilerPluginInfo")
load(
"//swift/internal:providers.bzl",
"SwiftBinaryInfo",
"SwiftCompilerPluginInfo",
)
load(
"//swift/internal:swift_symbol_graph_aspect.bzl",
"make_swift_symbol_graph_aspect",
Expand Down Expand Up @@ -369,8 +373,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_cc_infos = []
deps_compilation_contexts = []
deps_objc_infos = []
Expand All @@ -384,8 +388,8 @@ def _swift_test_impl(ctx):
deps_objc_infos.append(dep[apple_common.Objc])
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_swift_infos.append(plugin_info.swift_info)
additional_linking_contexts.append(
plugin_info.cc_info.linking_context,
Expand Down