Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Commit

Permalink
Add swift_common.create_swift_interop_info.
Browse files Browse the repository at this point in the history
This API allows custom rules to opt-in to "lightweight Swift interop". If they are already propagating a `CcInfo` with a compilation context, then `swift_clang_module_aspect` will detect this provider and generate a module name/module map (if necessary), and also compile the explicit module (if enabled). This frees the custom rule from having to understand details of the Swift compilation APIs, or even having to have a dependency on the Swift toolchain at all.

PiperOrigin-RevId: 367251939
  • Loading branch information
allevato authored and swiple-rules-gardener committed Apr 7, 2021
1 parent 5f7af69 commit 9be992e
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 32 deletions.
184 changes: 153 additions & 31 deletions swift/internal/swift_clang_module_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,86 @@ _SINGLE_TARGET_ASPECT_ATTRS = [
"_jre_lib",
]

_SwiftInteropInfo = provider(
doc = """\
Contains minimal information required for the `swift_clang_module_aspect` to
generate a module map and/or precompiled module for a target so that it can
expose C/Objective-C APIs to Swift.
""",
fields = {
"additional_swift_infos": """\
A list of additional `SwiftInfo` providers from dependencies that aren't
reachable via the `deps` attribute of the rule propagating this provider, but
which should be treated as dependencies of the C/Objective-C module.
""",
"module_map": """\
A `File` representing an existing module map that should be used to represent
the module, or `None` if the module map should be generated based on the headers
in the target's compilation context.
""",
"module_name": """\
A string denoting the name of the module, or `None` if the name should be
derived automatically from the target label.
""",
},
)

def create_swift_interop_info(
*,
additional_swift_infos = [],
module_map = None,
module_name = None):
"""Returns a provider that lets a target expose C/Objective-C APIs to Swift.
The provider returned by this function allows custom build rules written in
Starlark to be uninvolved with much of the low-level machinery involved in
making a Swift-compatible module. Such a target should propagate a `CcInfo`
provider whose compilation context contains the headers that it wants to
make into a module, and then also propagate the provider returned from this
function.
The simplest usage is for the custom rule to simply return
`swift_common.create_swift_interop_info()` without any arguments; this
tells `swift_clang_module_aspect` to derive the module name from the target
label and create a module map using the headers from the compilation
context.
If the custom rule has reason to provide its own module name or module map,
then it can do so using the `module_name` and `module_map` arguments.
The `swift_clang_module_aspect` automatically traverses the `deps` attribute
of even custom rules, so those rules don't need to do anything extra to
collect those dependencies. However, if a custom rule has dependencies in
other attributes (such as a runtime library specified as the default value
of a private attribute), it should pass the `SwiftInfo` provider from that
target into this function via the `additional_swift_infos` argument.
Args:
additional_swift_infos: A list of additional `SwiftInfo` providers from
dependencies that aren't reachable via the `deps` attribute of the
rule propagating this provider, but which should be treated as
dependencies of the C/Objective-C module.
module_map: A `File` representing an existing module map that should be
used to represent the module, or `None` if the module map should be
generated based on the headers in the target's compilation context.
If this argument is provided, then `module_name` must also be
provided.
module_name: A string denoting the name of the module, or `None` if the
name should be derived automatically from the target label.
Returns:
A provider whose type/layout is an implementation detail and should not
be relied upon.
"""
if module_map and not module_name:
fail("'module_name' must be specified when 'module_map' is specified.")

return _SwiftInteropInfo(
additional_swift_infos = additional_swift_infos,
module_map = module_map,
module_name = module_name,
)

def _tagged_target_module_name(label, tags):
"""Returns the module name of a `swift_module`-tagged target.
Expand Down Expand Up @@ -159,25 +239,29 @@ def _module_info_for_target(
aspect_ctx,
compilation_context,
dependent_module_names,
feature_configuration):
feature_configuration,
module_name):
"""Returns the module name and module map for the target.
Args:
aspect_ctx: The aspect context.
target: The target for which the module map is being generated.
aspect_ctx: The aspect context.
compilation_context: The C++ compilation context that provides the
headers for the module.
dependent_module_names: A `list` of names of Clang modules that are
direct dependencies of the target whose module map is being written.
feature_configuration: A Swift feature configuration.
module_name: The module name to prefer (if we're generating a module map
from `_SwiftInteropInfo`), or None to derive it from other
properties of the target.
Returns:
A tuple containing the module name (a string) and module map file (a
`File`) for the target. One or both of these values may be `None`.
"""
attr = aspect_ctx.rule.attr

if apple_common.Objc in target:
if not module_name and apple_common.Objc in target:
# TODO(b/142867898): For `objc_library`, stop using the module map from
# the Objc provider and generate our own. (For imported frameworks,
# continue using the module map included with it.)
Expand Down Expand Up @@ -233,15 +317,26 @@ def _module_info_for_target(
)
return module_name, module_map_file

# For all other targets, there is no mechanism to provide a custom
# module map, and we only generate one if the target is tagged.
module_name = _tagged_target_module_name(
label = target.label,
tags = attr.tags,
)
if not module_name:
# If a target doesn't have any headers (and if we're on this code path, it
# didn't provide an explicit module map), then don't generate a module map
# for it. Such modules define nothing and only waste space on the
# compilation command line and add more work for the compiler.
if not (
compilation_context.direct_headers or
compilation_context.direct_textual_headers
):
return None, None

if not module_name:
# For all other targets, there is no mechanism to provide a custom
# module map, and we only generate one if the target is tagged.
module_name = _tagged_target_module_name(
label = target.label,
tags = attr.tags,
)
if not module_name:
return None, None

module_map_file = _generate_module_map(
actions = aspect_ctx.actions,
compilation_context = compilation_context,
Expand All @@ -252,20 +347,30 @@ def _module_info_for_target(
)
return module_name, module_map_file

def _handle_cc_target(
def _handle_module(
aspect_ctx,
compilation_context,
feature_configuration,
module_map_file,
module_name,
swift_infos,
swift_toolchain,
target):
"""Processes a C++ target that is a dependency of a Swift target.
"""Processes a C/Objective-C target that is a dependency of a Swift target.
Args:
aspect_ctx: The aspect's context.
compilation_context: The `CcCompilationContext` containing the target's
headers.
feature_configuration: The current feature configuration.
module_map_file: The `.modulemap` file that defines the module, or None
if it should be inferred from other properties of the target (for
legacy support).
module_name: The name of the module, or None if it should be inferred
from other properties of the target (for legacy support).
swift_infos: The `SwiftInfo` providers of the current target's
dependencies, which should be merged into the `SwiftInfo` provider
created and returned for this C++ target.
created and returned for this target.
swift_toolchain: The Swift toolchain being used to build this target.
target: The C++ target to which the aspect is currently being applied.
Expand All @@ -274,13 +379,6 @@ def _handle_cc_target(
"""
attr = aspect_ctx.rule.attr

# TODO(b/142867898): Only check `CcInfo` once all rules correctly propagate
# it.
if CcInfo in target:
compilation_context = target[CcInfo].compilation_context
else:
compilation_context = None

if swift_infos:
merged_swift_info = create_swift_info(swift_infos = swift_infos)
else:
Expand All @@ -294,13 +392,18 @@ def _handle_cc_target(
if module.clang:
dependent_module_names.append(module.name)

module_name, module_map_file = _module_info_for_target(
target = target,
aspect_ctx = aspect_ctx,
compilation_context = compilation_context,
dependent_module_names = dependent_module_names,
feature_configuration = feature_configuration,
)
# If we weren't passed a module map (i.e., from a `_SwiftInteropInfo`
# provider), infer it and the module name based on properties of the rule to
# support legacy rules.
if not module_map_file:
module_name, module_map_file = _module_info_for_target(
target = target,
aspect_ctx = aspect_ctx,
compilation_context = compilation_context,
dependent_module_names = dependent_module_names,
feature_configuration = feature_configuration,
module_name = module_name,
)

if not module_map_file:
if merged_swift_info:
Expand Down Expand Up @@ -402,12 +505,31 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx):
unsupported_features = aspect_ctx.disabled_features,
)

# TODO(b/142867898): Only check `CcInfo` once all native rules correctly
# propagate both.
if apple_common.Objc in target or CcInfo in target:
return _handle_cc_target(
if CcInfo in target:
compilation_context = target[CcInfo].compilation_context
else:
compilation_context = None

if _SwiftInteropInfo in target:
interop_info = target[_SwiftInteropInfo]
swift_infos.extend(interop_info.additional_swift_infos)
module_map_file = interop_info.module_map
module_name = interop_info.module_name
else:
module_map_file = None
module_name = None

if (
_SwiftInteropInfo in target or
apple_common.Objc in target or
CcInfo in target
):
return _handle_module(
aspect_ctx = aspect_ctx,
compilation_context = compilation_context,
feature_configuration = feature_configuration,
module_map_file = module_map_file,
module_name = module_name,
swift_infos = swift_infos,
swift_toolchain = swift_toolchain,
target = target,
Expand Down
7 changes: 6 additions & 1 deletion swift/internal/swift_common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ load(
"create_swift_info",
"create_swift_module",
)
load(":swift_clang_module_aspect.bzl", "swift_clang_module_aspect")
load(
":swift_clang_module_aspect.bzl",
"create_swift_interop_info",
"swift_clang_module_aspect",
)

# The exported `swift_common` module, which defines the public API for directly
# invoking actions that compile Swift code from other rules.
Expand All @@ -61,6 +65,7 @@ swift_common = struct(
create_clang_module = create_clang_module,
create_module = create_module,
create_swift_info = create_swift_info,
create_swift_interop_info = create_swift_interop_info,
create_swift_module = create_swift_module,
derive_module_name = derive_module_name,
is_enabled = is_feature_enabled,
Expand Down

0 comments on commit 9be992e

Please sign in to comment.