Skip to content

Commit

Permalink
Add exclude_hdrs to swift_interop_hint.
Browse files Browse the repository at this point in the history
This attribute can be used to exclude a list of headers from the Swift-generated module map (via `exclude header` declarations) without removing them from the hinted target completely. This is often helpful in cases where some subset of headers are not Swift-compatible but still needed as part of the library for other reasons (e.g., they are private headers used by implementation source files, or still used by other non-Swift dependents).

PiperOrigin-RevId: 398076709
  • Loading branch information
allevato authored and swiple-rules-gardener committed Sep 21, 2021
1 parent 868a76b commit ef6662c
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 14 deletions.
56 changes: 44 additions & 12 deletions swift/internal/module_maps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

"""Logic for generating Clang module map files."""

load("@bazel_skylib//lib:sets.bzl", "sets")

def write_module_map(
actions,
module_map_file,
module_name,
dependent_module_names = [],
exclude_headers = [],
exported_module_ids = [],
public_headers = [],
public_textual_headers = [],
Expand All @@ -34,6 +37,8 @@ def write_module_map(
module_name: The name of the module being generated.
dependent_module_names: A `list` of names of Clang modules that are
direct dependencies of the target whose module map is being written.
exclude_headers: A `list` of `File`s representing headers that should be
explicitly excluded from the module being written.
exported_module_ids: A `list` of Clang wildcard module identifiers that
will be re-exported as part of the API of the module being written.
The values in this list should match `wildcard-module-id` as
Expand Down Expand Up @@ -68,34 +73,55 @@ def write_module_map(
relative_to_dir = module_map_file.dirname
back_to_root_path = "../" * len(relative_to_dir.split("/"))

excluded_headers_set = sets.make(exclude_headers)

content = actions.args()
content.set_param_file_format("multiline")

content.add(module_name, format = 'module "%s" {')
def _relativized_header_path(file):
return _header_path(
header_file = file,
relative_to_dir = relative_to_dir,
back_to_root_path = back_to_root_path,
)

# Write an `export` declaration for each of the module identifiers that
# should be re-exported by this module.
content.add_all(exported_module_ids, format_each = " export %s")
content.add("")
def _relativized_header_paths_with_exclusion(
file_or_dir,
directory_expander):
return [
_relativized_header_path(file)
for file in directory_expander.expand(file_or_dir)
if not sets.contains(excluded_headers_set, file)
]

def _relativized_header_paths(file_or_dir, directory_expander):
return [
_header_path(
header_file = file,
relative_to_dir = relative_to_dir,
back_to_root_path = back_to_root_path,
)
_relativized_header_path(file)
for file in directory_expander.expand(file_or_dir)
]

def _add_headers(*, headers, kind):
def _add_headers(*, allow_excluded_headers = False, headers, kind):
if allow_excluded_headers:
map_each = _relativized_header_paths
else:
map_each = _relativized_header_paths_with_exclusion

content.add_all(
headers,
allow_closure = True,
format_each = ' {} "%s"'.format(kind),
map_each = _relativized_header_paths,
map_each = map_each,
)

content.add(module_name, format = 'module "%s" {')

# Write an `export` declaration for each of the module identifiers that
# should be re-exported by this module.
content.add_all(exported_module_ids, format_each = " export %s")
content.add("")

# When writing these headers, honor the `exclude_headers` list (i.e., remove
# any headers from these lists that also appear there).
if umbrella_header:
_add_headers(headers = [umbrella_header], kind = "umbrella header")
else:
Expand All @@ -107,6 +133,12 @@ def write_module_map(
kind = "private textual header",
)

_add_headers(
allow_excluded_headers = True,
headers = exclude_headers,
kind = "exclude header",
)

content.add("")

# Write a `use` declaration for each of the module's dependencies.
Expand Down
33 changes: 31 additions & 2 deletions swift/internal/swift_clang_module_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ Contains minimal information required to allow `swift_clang_module_aspect` to
manage the creation of a `SwiftInfo` provider for a C/Objective-C target.
""",
fields = {
"exclude_headers": """\
A `list` of `File`s representing headers that should be excluded from the
module, if a module map is being automatically generated based on the headers in
the target's compilation context.
""",
"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
Expand Down Expand Up @@ -92,6 +97,7 @@ enabled by default in the toolchain.

def create_swift_interop_info(
*,
exclude_headers = [],
module_map = None,
module_name = None,
requested_features = [],
Expand Down Expand Up @@ -124,6 +130,8 @@ def create_swift_interop_info(
exclude dependencies if necessary.
Args:
exclude_headers: A `list` of `File`s representing headers that should be
excluded from the module if the module map is generated.
module_map: A `File` representing an existing module map that should be
used to represent the module, or `None` (the default) if the module
map should be generated based on the headers in the target's
Expand Down Expand Up @@ -162,10 +170,16 @@ def create_swift_interop_info(
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.")
if module_map:
if not module_name:
fail("'module_name' must be specified when 'module_map' is " +
"specified.")
if exclude_headers:
fail("'exclude_headers' may not be specified when 'module_map' " +
"is specified.")

return _SwiftInteropInfo(
exclude_headers = exclude_headers,
module_map = module_map,
module_name = module_name,
requested_features = requested_features,
Expand All @@ -178,6 +192,7 @@ def _generate_module_map(
actions,
compilation_context,
dependent_module_names,
exclude_headers,
feature_configuration,
module_name,
target,
Expand All @@ -190,6 +205,8 @@ def _generate_module_map(
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.
exclude_headers: A `list` of `File`s representing header files to
exclude, if any, if we are generating the module map.
feature_configuration: A Swift feature configuration.
module_name: The name of the module.
target: The target for which the module map is being generated.
Expand Down Expand Up @@ -231,6 +248,7 @@ def _generate_module_map(
write_module_map(
actions = actions,
dependent_module_names = sorted(dependent_module_names),
exclude_headers = sorted(exclude_headers, key = _path_sorting_key),
exported_module_ids = ["*"],
module_map_file = module_map_file,
module_name = module_name,
Expand Down Expand Up @@ -323,6 +341,7 @@ def _module_info_for_target(
aspect_ctx,
compilation_context,
dependent_module_names,
exclude_headers,
feature_configuration,
module_name,
umbrella_header):
Expand All @@ -335,6 +354,8 @@ def _module_info_for_target(
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.
exclude_headers: A `list` of `File`s representing header files to
exclude, if any, if we are generating the module map.
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
Expand Down Expand Up @@ -387,6 +408,7 @@ def _module_info_for_target(
actions = aspect_ctx.actions,
compilation_context = compilation_context,
dependent_module_names = dependent_module_names,
exclude_headers = exclude_headers,
feature_configuration = feature_configuration,
module_name = module_name,
target = target,
Expand All @@ -398,6 +420,7 @@ def _module_info_for_target(
def _handle_module(
aspect_ctx,
compilation_context,
exclude_headers,
feature_configuration,
module_map_file,
module_name,
Expand All @@ -410,6 +433,8 @@ def _handle_module(
aspect_ctx: The aspect's context.
compilation_context: The `CcCompilationContext` containing the target's
headers.
exclude_headers: A `list` of `File`s representing header files to
exclude, if any, if we are generating the module map.
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
Expand Down Expand Up @@ -455,6 +480,7 @@ def _handle_module(
aspect_ctx = aspect_ctx,
compilation_context = compilation_context,
dependent_module_names = dependent_module_names,
exclude_headers = exclude_headers,
feature_configuration = feature_configuration,
module_name = module_name,
umbrella_header = umbrella_header,
Expand Down Expand Up @@ -657,6 +683,7 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx):
if interop_info.suppressed:
return []

exclude_headers = interop_info.exclude_headers
module_map_file = interop_info.module_map
module_name = (
interop_info.module_name or derive_module_name(target.label)
Expand All @@ -665,6 +692,7 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx):
requested_features.extend(interop_info.requested_features)
unsupported_features.extend(interop_info.unsupported_features)
else:
exclude_headers = []
module_map_file = None
module_name = None

Expand All @@ -680,6 +708,7 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx):
return _handle_module(
aspect_ctx = aspect_ctx,
compilation_context = _compilation_context_for_target(target),
exclude_headers = exclude_headers,
feature_configuration = feature_configuration,
module_map_file = module_map_file,
module_name = module_name,
Expand Down
16 changes: 16 additions & 0 deletions swift/internal/swift_interop_hint.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,29 @@ def _swift_interop_hint_impl(ctx):
# TODO(b/194733180): Take advantage of the richer API to add support for
# other features, like APINotes, later.
return swift_common.create_swift_interop_info(
exclude_headers = ctx.files.exclude_hdrs,
module_map = ctx.file.module_map,
module_name = ctx.attr.module_name,
suppressed = ctx.attr.suppressed,
)

swift_interop_hint = rule(
attrs = {
"exclude_hdrs": attr.label_list(
allow_files = True,
doc = """\
A list of header files that should be excluded from the Clang module generated
for the target to which this hint is applied. This allows a target to exclude
a subset of a library's headers specifically from the Swift module map without
removing them from the library completely, which can be useful if some headers
are not Swift-compatible but are still needed by other sources in the library or
by non-Swift dependents.
This attribute may only be specified if a custom `module_map` is not provided.
Setting both attributes is an error.
""",
mandatory = False,
),
"module_map": attr.label(
allow_single_file = True,
doc = """\
Expand Down

2 comments on commit ef6662c

@keith
Copy link
Member

@keith keith commented on ef6662c Jan 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brentleyjones
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.