diff --git a/swift/internal/module_maps.bzl b/swift/internal/module_maps.bzl index 1f4df0c6a..9e0aeb6d0 100644 --- a/swift/internal/module_maps.bzl +++ b/swift/internal/module_maps.bzl @@ -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 = [], @@ -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 @@ -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: @@ -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. diff --git a/swift/internal/swift_clang_module_aspect.bzl b/swift/internal/swift_clang_module_aspect.bzl index 5bbe45dce..394f746f0 100644 --- a/swift/internal/swift_clang_module_aspect.bzl +++ b/swift/internal/swift_clang_module_aspect.bzl @@ -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 @@ -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 = [], @@ -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 @@ -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, @@ -178,6 +192,7 @@ def _generate_module_map( actions, compilation_context, dependent_module_names, + exclude_headers, feature_configuration, module_name, target, @@ -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. @@ -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, @@ -323,6 +341,7 @@ def _module_info_for_target( aspect_ctx, compilation_context, dependent_module_names, + exclude_headers, feature_configuration, module_name, umbrella_header): @@ -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 @@ -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, @@ -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, @@ -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 @@ -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, @@ -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) @@ -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 @@ -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, diff --git a/swift/internal/swift_interop_hint.bzl b/swift/internal/swift_interop_hint.bzl index b325623df..17b8901a3 100644 --- a/swift/internal/swift_interop_hint.bzl +++ b/swift/internal/swift_interop_hint.bzl @@ -20,6 +20,7 @@ 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, @@ -27,6 +28,21 @@ def _swift_interop_hint_impl(ctx): 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 = """\