From 9a8bd4a3dba28f8b1a755cf40440eb8908ecb985 Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Tue, 16 Jul 2024 16:47:46 -0500 Subject: [PATCH] Add `mixed_langauge_library` Replaces `experimental_mixed_langauge_library` in rules_apple. Meant to support SE-0403. Signed-off-by: Brentley Jones --- doc/BUILD | 1 + doc/api.md | 7 +- doc/doc.bzl | 5 + doc/rules.md | 38 ++ swift/BUILD | 22 ++ swift/internal/compiling.bzl | 1 + swift/internal/feature_names.bzl | 4 +- swift/internal/module_maps.bzl | 10 + swift/internal/providers.bzl | 3 + swift/mixed_language_library.bzl | 616 +++++++++++++++++++++++++++++++ 10 files changed, 703 insertions(+), 4 deletions(-) create mode 100644 swift/mixed_language_library.bzl diff --git a/doc/BUILD b/doc/BUILD index 5fc30a1a5..90612785c 100644 --- a/doc/BUILD +++ b/doc/BUILD @@ -23,6 +23,7 @@ _DOC_SRCS = { "swift_interop_hint", "swift_library", "swift_library_group", + "mixed_language_library", "swift_module_alias", "swift_package_configuration", "swift_test", diff --git a/doc/api.md b/doc/api.md index 6ca045dfa..958fc04bb 100644 --- a/doc/api.md +++ b/doc/api.md @@ -476,9 +476,9 @@ A provider whose type/layout is an implementation detail and should not ## swift_common.create_swift_module
-swift_common.create_swift_module(swiftdoc, swiftmodule, ast_files, defines, indexstore, plugins,
-                                 swiftsourceinfo, swiftinterface, private_swiftinterface,
-                                 const_protocols_to_gather)
+swift_common.create_swift_module(swiftdoc, swiftmodule, ast_files, defines, generated_header,
+                                 indexstore, plugins, swiftsourceinfo, swiftinterface,
+                                 private_swiftinterface, const_protocols_to_gather)
 
Creates a value representing a Swift module use as a Swift dependency. @@ -492,6 +492,7 @@ Creates a value representing a Swift module use as a Swift dependency. | swiftmodule | The `.swiftmodule` file emitted by the compiler for this module. | none | | ast_files | A list of `File`s output from the `DUMP_AST` action. | `[]` | | defines | A list of defines that will be provided as `copts` to targets that depend on this module. If omitted, the empty list will be used. | `[]` | +| generated_header | A `File` representing the Swift generated header. | `None` | | indexstore | A `File` representing the directory that contains the index store data generated by the compiler if the `"swift.index_while_building"` feature is enabled, otherwise this will be `None`. | `None` | | plugins | A list of `SwiftCompilerPluginInfo` providers representing compiler plugins that are required by this module and should be loaded by the compiler when this module is directly depended on. | `[]` | | swiftsourceinfo | The `.swiftsourceinfo` file emitted by the compiler for this module. May be `None` if no source info file was emitted. | `None` | diff --git a/doc/doc.bzl b/doc/doc.bzl index d938e5294..c012a628f 100644 --- a/doc/doc.bzl +++ b/doc/doc.bzl @@ -42,6 +42,10 @@ load( "//proto:swift_proto_library_group.bzl", _swift_proto_library_group = "swift_proto_library_group", ) +load( + "//swift:mixed_language_library.bzl", + _mixed_language_library = "mixed_language_library", +) load( "//swift:providers.bzl", _SwiftInfo = "SwiftInfo", @@ -102,6 +106,7 @@ swift_import = _swift_import swift_interop_hint = _swift_interop_hint swift_library = _swift_library swift_library_group = _swift_library_group +mixed_language_library = _mixed_language_library swift_module_alias = _swift_module_alias swift_package_configuration = _swift_package_configuration swift_test = _swift_test diff --git a/doc/rules.md b/doc/rules.md index b2e9a84d7..28598fa95 100644 --- a/doc/rules.md +++ b/doc/rules.md @@ -22,6 +22,7 @@ On this page: * [swift_interop_hint](#swift_interop_hint) * [swift_library](#swift_library) * [swift_library_group](#swift_library_group) + * [mixed_language_library](#mixed_language_library) * [swift_module_alias](#swift_module_alias) * [swift_package_configuration](#swift_package_configuration) * [swift_test](#swift_test) @@ -802,3 +803,40 @@ swift_library( | plugin | Target to generate a 'fat' binary from. | Label | required | | + + +## mixed_language_library + +
+mixed_language_library(name, clang_copts, clang_defines, clang_srcs, enable_modules, hdrs, includes,
+                       linkopts, module_map, module_name, private_deps, swift_copts, swift_defines,
+                       swift_srcs, textual_hdrs, umbrella_header, deps, kwargs)
+
+ +Creates a mixed language library from a clang and swift library target pair. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | The name of the target. | none | +| clang_copts | The compiler flags for the clang library. These will only be used for the clang library. If you want them to affect the swift library as well, you need to pass them with `-Xcc` in `swift_copts`. | `[]` | +| clang_defines | Extra clang `-D` flags to pass to the compiler. They should be in the form `KEY=VALUE` or simply `KEY` and are passed not only to the compiler for this target (as `clang_copts` are) but also to all dependers of this target. Subject to "Make variable" substitution and Bourne shell tokenization. | `[]` | +| clang_srcs | The sources for the clang library. | none | +| enable_modules | Enables clang module support (via `-fmodules`). Setting this to `True` will allow you to `@import` system headers and other targets: `@import UIKit;` `@import path_to_package_target;`. | `False` | +| hdrs | The list of C, C++, Objective-C, or Objective-C++ header files published by this library to be included by sources in dependent rules. This can't include `umbrella_header`. | `[]` | +| includes | List of `#include`/`#import` search paths to add to this target and all depending targets. | `[]` | +| linkopts | Extra flags to pass to the linker. | `[]` | +| 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 `hdrs`. If this argument is provided, then `module_name` must also be provided.

Warning: If a module map (whether provided here or not) is able to be found via an include path, it will result in duplicate module definition errors for downstream targets unless sandboxing or remote execution is used. | `None` | +| module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | `None` | +| private_deps | A list of targets that are implementation-only dependencies of the target being built. Libraries/linker flags from these dependencies will be propagated to dependent for linking, but artifacts/flags required for compilation (such as .swiftmodule files, C headers, and search paths) will not be propagated. | `[]` | +| swift_copts | The compiler flags for the swift library. | `[]` | +| swift_defines | A list of Swift defines to add to the compilation command line.

Note that unlike C-family languages, Swift defines do not have values; they are simply identifiers that are either defined or undefined. So strings in this list should be simple identifiers, not `name=value` pairs.

Each string is prepended with `-D` and added to the command line. Unlike `swift_copts`, these flags are added for the target and every target that depends on it, so use this attribute with caution. It is preferred that you add defines directly to `swift_copts`, only using this feature in the rare case that a library needs to propagate a symbol up to those that depend on it. | `[]` | +| swift_srcs | The sources for the swift library. | none | +| textual_hdrs | The list of C, C++, Objective-C, or Objective-C++ files that are included as headers by source files in this rule or by users of this library. Unlike `hdrs`, these will not be compiled separately from the sources. | `[]` | +| umbrella_header | A `File` representing an existing umbrella header that should be used in the generated module map or is used in the custom module map, or `None` (the default) if the umbrella header should be generated based on `hdrs`. A symlink to this header is added to an include path such that `#import ` works for this and downstream targets. | `None` | +| deps | A list of targets that are dependencies of the target being built. | `[]` | +| kwargs | Additional arguments to pass to the underlying clang and swift library targets. | none | + + diff --git a/swift/BUILD b/swift/BUILD index bd87bf866..db6efd087 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -35,6 +35,27 @@ bzl_library( ], ) +bzl_library( + name = "mixed_language_library", + srcs = ["mixed_language_library.bzl"], + deps = [ + ":providers", + ":swift_clang_module_aspect", + ":swift_interop_hint", + ":swift_library", + "//swift/internal:attrs", + "//swift/internal:compiling", + "//swift/internal:feature_names", + "//swift/internal:features", + "//swift/internal:module_maps", + "//swift/internal:providers", + "//swift/internal:toolchain_utils", + "//swift/internal:utils", + "@bazel_skylib//lib:dicts", + "@bazel_skylib//lib:paths", + ], +) + bzl_library( name = "module_name", srcs = ["module_name.bzl"], @@ -56,6 +77,7 @@ bzl_library( name = "swift", srcs = ["swift.bzl"], deps = [ + ":mixed_language_library", ":providers", ":swift_binary", ":swift_clang_module_aspect", diff --git a/swift/internal/compiling.bzl b/swift/internal/compiling.bzl index 27a892b30..3c861c6fa 100644 --- a/swift/internal/compiling.bzl +++ b/swift/internal/compiling.bzl @@ -813,6 +813,7 @@ to use swift_common.compile(include_dev_srch_paths = ...) instead.\ swift = create_swift_module( ast_files = compile_outputs.ast_files, defines = defines, + generated_header = compile_outputs.generated_header_file, indexstore = compile_outputs.indexstore_directory, plugins = depset(plugins), private_swiftinterface = compile_outputs.private_swiftinterface_file, diff --git a/swift/internal/feature_names.bzl b/swift/internal/feature_names.bzl index 5d38fd30f..29a78294d 100644 --- a/swift/internal/feature_names.bzl +++ b/swift/internal/feature_names.bzl @@ -132,7 +132,9 @@ SWIFT_FEATURE_NO_GENERATED_MODULE_MAP = "swift.no_generated_module_map" # If enabled, the parent directory of the generated module map is added to # `CcInfo.compilation_context.includes`. This allows `objc_library` to import -# the Swift module. +# the Swift module. If you swap this faeture between enabled and disabled, and +# sandboxing is disabled, you may need to clean your output base to prevent +# implicit discovery of the generated module map. SWIFT_FEATURE_PROPAGATE_GENERATED_MODULE_MAP = "swift.propagate_generated_module_map" # If enabled, builds using the "opt" compilation mode will invoke `swiftc` with diff --git a/swift/internal/module_maps.bzl b/swift/internal/module_maps.bzl index 21ff4ce9d..50390d2f3 100644 --- a/swift/internal/module_maps.bzl +++ b/swift/internal/module_maps.bzl @@ -30,6 +30,7 @@ def write_module_map( public_textual_headers = [], private_headers = [], private_textual_headers = [], + swift_generated_header = None, umbrella_header = None, workspace_relative = False): """Writes the content of the module map to a file. @@ -58,6 +59,8 @@ def write_module_map( headers of the target whose module map is being written. private_textual_headers: The `list` of `File`s representing the private textual headers of the target whose module map is being written. + swift_generated_header: A `File` representing the Swift generated + header, that if present, will be used to create the Swift submodule. umbrella_header: A `File` representing an umbrella header that, if present, will be written into the module map instead of the list of headers in the compilation context. @@ -148,6 +151,13 @@ def write_module_map( content.add_all(dependent_module_names, format_each = ' use "%s"') content.add("}") + if swift_generated_header: + content.add("") + content.add(module_name, format = 'module "%s".Swift {') + _add_headers(headers = [swift_generated_header], kind = "header") + content.add(" requires objc") + content.add("}") + actions.write( content = content, output = module_map_file, diff --git a/swift/internal/providers.bzl b/swift/internal/providers.bzl index c3592ab83..9d24e02af 100644 --- a/swift/internal/providers.bzl +++ b/swift/internal/providers.bzl @@ -140,6 +140,7 @@ def create_swift_module( swiftmodule, ast_files = [], defines = [], + generated_header = None, indexstore = None, plugins = [], swiftsourceinfo = None, @@ -155,6 +156,7 @@ def create_swift_module( ast_files: A list of `File`s output from the `DUMP_AST` action. defines: A list of defines that will be provided as `copts` to targets that depend on this module. If omitted, the empty list will be used. + generated_header: A `File` representing the Swift generated header. indexstore: A `File` representing the directory that contains the index store data generated by the compiler if the `"swift.index_while_building"` feature is enabled, otherwise this @@ -181,6 +183,7 @@ def create_swift_module( return struct( ast_files = tuple(ast_files), defines = tuple(defines), + generated_header = generated_header, plugins = plugins, private_swiftinterface = private_swiftinterface, indexstore = indexstore, diff --git a/swift/mixed_language_library.bzl b/swift/mixed_language_library.bzl new file mode 100644 index 000000000..f5f3bf5f6 --- /dev/null +++ b/swift/mixed_language_library.bzl @@ -0,0 +1,616 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implementation of the `mixed_language_library` macro and \ +`mixed_language_library_assembler` rule.""" + +load("@bazel_skylib//lib:dicts.bzl", "dicts") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//swift/internal:attrs.bzl", "swift_deps_attr", "swift_toolchain_attrs") +load("//swift/internal:compiling.bzl", "derive_module_name") +load( + "//swift/internal:feature_names.bzl", + "SWIFT_FEATURE_PROPAGATE_GENERATED_MODULE_MAP", +) +load( + "//swift/internal:features.bzl", + "configure_features", + "is_feature_enabled", +) +load("//swift/internal:module_maps.bzl", "write_module_map") +load( + "//swift/internal:providers.bzl", + "create_clang_module", + "create_module", + "create_swift_info", +) +load( + "//swift/internal:toolchain_utils.bzl", + "get_swift_toolchain", + "use_swift_toolchain", +) +load("//swift/internal:utils.bzl", "get_providers") +load(":providers.bzl", "SwiftInfo") +load(":swift_clang_module_aspect.bzl", "swift_clang_module_aspect") +load(":swift_interop_hint.bzl", "swift_interop_hint") +load(":swift_library.bzl", "swift_library") + +# `_mixed_language_library_umbrella_header` + +def _write_umbrella_header(*, actions, headers, umbrella_header): + content = actions.args() + content.set_param_file_format("multiline") + + content.add_all(headers, format_each = '#import "%s"') + + actions.write( + content = content, + output = umbrella_header, + ) + +def _mixed_language_library_umbrella_header_impl(ctx): + actions = ctx.actions + + includes = [] + umbrella_header = actions.declare_file( + "{name}/{module_name}/{module_name}.h".format( + module_name = ctx.attr.module_name, + name = ctx.attr.name, + ), + ) + _write_umbrella_header( + actions = actions, + headers = ctx.files.hdrs, + umbrella_header = umbrella_header, + ) + outputs = [umbrella_header] + outputs_depset = depset(outputs) + + # This allows to be found + includes = [paths.dirname(umbrella_header.dirname)] + + compilation_context = cc_common.create_compilation_context( + headers = outputs_depset, + direct_public_headers = outputs, + includes = depset(includes), + ) + + return [ + DefaultInfo(files = outputs_depset), + CcInfo(compilation_context = compilation_context), + ] + +_mixed_language_library_umbrella_header = rule( + attrs = { + "actual_name": attr.string(mandatory = True), + "includes": attr.string_list(), + "hdrs": attr.label_list(allow_files = True), + "module_name": attr.string(mandatory = True), + }, + doc = "Creates an umbrella header for a mixed language target.", + implementation = _mixed_language_library_umbrella_header_impl, +) + +# `_mixed_language_library_internal_module_map` + +def _mixed_language_library_internal_module_map_impl(ctx): + actions = ctx.actions + + module_map = actions.declare_file(ctx.attr.actual_name + ".modulemap") + + # TODO: Set `dependent_module_names` + write_module_map( + actions = actions, + exported_module_ids = ["*"], + module_map_file = module_map, + module_name = ctx.attr.module_name, + umbrella_header = ctx.file.umbrella_header, + public_headers = ctx.files.hdrs, + public_textual_headers = ctx.files.textual_hdrs, + ) + + return [DefaultInfo(files = depset([module_map]))] + +_mixed_language_library_internal_module_map = rule( + attrs = { + "actual_name": attr.string(mandatory = True), + "hdrs": attr.label_list(allow_files = True), + "module_name": attr.string(mandatory = True), + "textual_hdrs": attr.label_list(allow_files = True), + "umbrella_header": attr.label(allow_single_file = True), + }, + doc = """\ +Creates the module map file that is used internally by the clang and swift +library targets in the `mixed_language_library` macro. +""", + implementation = _mixed_language_library_internal_module_map_impl, +) + +# `_mixed_language_library` + +def _write_extended_module_map( + *, + actions, + module_name, + original_module_map, + output_module_map, + swift_generated_header): + args = actions.args() + args.add(output_module_map) + args.add(original_module_map) + args.add(module_name) + args.add(swift_generated_header.basename) + + actions.run_shell( + arguments = [args], + command = """\ +set -eu + +output="$1" +input="$2" +module_name="$3" +generated_header="$4" + +# FIXME: Actually parse the map and fix the paths relative to the original +# location, since it might not be the generated one. We can't use VFS overlay +# like SE-0403, so rewriting the modulemap is the only way. +sed 's|header "|header "../../|g' "$input" > "$output" + +cat <> "$output" + +module "$module_name".Swift { + header "$generated_header" + + requires objc +} +EOF +""", + inputs = [original_module_map], + mnemonic = "ExtendMixedLanguageModuleMap", + outputs = [output_module_map], + ) + +def _mixed_language_library_impl(ctx): + actions = ctx.actions + clang_target = ctx.attr.clang_target + module_name = ctx.attr.module_name + name = ctx.attr.name + + swift_target = ctx.attr.swift_target + swift_info = swift_target[SwiftInfo] + + feature_configuration = configure_features( + ctx = ctx, + requested_features = ctx.features, + swift_toolchain = get_swift_toolchain(ctx), + unsupported_features = ctx.disabled_features, + ) + + umbrella_header_symlink = actions.declare_file( + "{name}/{module_name}/{module_name}.h".format( + module_name = module_name, + name = name, + ), + ) + ctx.actions.symlink( + output = umbrella_header_symlink, + target_file = ctx.file.umbrella_header, + ) + + swift_module = swift_info.direct_modules[0] + swift_generated_header = swift_module.swift.generated_header + if not swift_generated_header: + fail("{} must have 'generate_header = True'".format(swift_target.label)) + + swift_generated_header_symlink = actions.declare_file( + "{name}/{module_name}/{basename}".format( + module_name = module_name, + name = name, + basename = swift_generated_header.basename, + ), + ) + ctx.actions.symlink( + output = swift_generated_header_symlink, + target_file = swift_generated_header, + ) + + propagate_module_map = is_feature_enabled( + feature_configuration = feature_configuration, + feature_name = SWIFT_FEATURE_PROPAGATE_GENERATED_MODULE_MAP, + ) + + if propagate_module_map: + module_map_basename = "module.modulemap" + else: + module_map_basename = name + ".modulemap" + + extended_module_map = actions.declare_file( + "{name}/{module_name}/{basename}".format( + basename = module_map_basename, + module_name = module_name, + name = name, + ), + ) + _write_extended_module_map( + actions = actions, + module_name = module_name, + original_module_map = ctx.file.module_map, + output_module_map = extended_module_map, + swift_generated_header = swift_generated_header_symlink, + ) + + outputs = [ + umbrella_header_symlink, + extended_module_map, + swift_generated_header_symlink, + ] + + compilation_context = cc_common.create_compilation_context( + headers = depset(outputs), + direct_public_headers = outputs, + includes = depset( + # This allows `#import ` and + # `#import ` to work + [paths.dirname(umbrella_header_symlink.dirname)], + ), + ) + + cc_info = cc_common.merge_cc_infos( + direct_cc_infos = [ + CcInfo(compilation_context = compilation_context), + ], + cc_infos = [clang_target[CcInfo], swift_target[CcInfo]], + ) + swift_info = create_swift_info( + modules = [ + create_module( + name = module_name, + clang = create_clang_module( + compilation_context = cc_info.compilation_context, + module_map = extended_module_map, + ), + swift = swift_module.swift, + ), + ], + # Collect transitive modules, without including `swift_target` (which is + # covered with the `create_module` above) + swift_infos = get_providers(ctx.attr.deps, SwiftInfo), + ) + + return [ + DefaultInfo(files = depset( + outputs, + transitive = [ + clang_target[DefaultInfo].files, + swift_target[DefaultInfo].files, + ], + )), + cc_info, + coverage_common.instrumented_files_info( + ctx, + dependency_attributes = ["deps"], + ), + swift_info, + # Propagate an `apple_common.Objc` provider with linking info about the + # library so that linking with Apple Starlark APIs/rules works + # correctly. + # TODO(b/171413861): This can be removed when the Obj-C rules are + # migrated to use `CcLinkingContext`. + apple_common.new_objc_provider( + providers = get_providers( + [clang_target, swift_target], + apple_common.Objc, + ), + ), + ] + +_mixed_language_library = rule( + attrs = dicts.add( + swift_toolchain_attrs(), + { + "clang_target": attr.label( + mandatory = True, + providers = [CcInfo], + ), + "deps": swift_deps_attr( + aspects = [swift_clang_module_aspect], + doc = "Dependencies of the target being built.", + ), + "swift_target": attr.label( + mandatory = True, + providers = [SwiftInfo], + ), + "umbrella_header": attr.label( + allow_single_file = True, + mandatory = True, + ), + "module_name": attr.string(mandatory = True), + "module_map": attr.label( + allow_single_file = True, + mandatory = True, + ), + }, + ), + doc = """\ +Assembles a mixed language library from a clang and swift library target pair. +""", + fragments = ["cpp"], + implementation = _mixed_language_library_impl, + toolchains = use_swift_toolchain(), +) + +# `mixed_language_library` + +def mixed_language_library( + *, + name, + clang_copts = [], + clang_defines = [], + clang_srcs, + enable_modules = False, + hdrs = [], + includes = [], + linkopts = [], + module_map = None, + module_name = None, + private_deps = [], + swift_copts = [], + swift_defines = [], + swift_srcs, + textual_hdrs = [], + umbrella_header = None, + deps = [], + **kwargs): + """Creates a mixed language library from a clang and swift library target \ + pair. + + Args: + name: The name of the target. + clang_copts: The compiler flags for the clang library. These will only + be used for the clang library. If you want them to affect the swift + library as well, you need to pass them with `-Xcc` in `swift_copts`. + clang_defines: Extra clang `-D` flags to pass to the compiler. They + should be in the form `KEY=VALUE` or simply `KEY` and are passed not + only to the compiler for this target (as `clang_copts` are) but also + to all dependers of this target. Subject to "Make variable" + substitution and Bourne shell tokenization. + clang_srcs: The sources for the clang library. + enable_modules: Enables clang module support (via `-fmodules`). Setting + this to `True` will allow you to `@import` system headers and other + targets: `@import UIKit;` `@import path_to_package_target;`. + hdrs: The list of C, C++, Objective-C, or Objective-C++ header files + published by this library to be included by sources in dependent + rules. This can't include `umbrella_header`. + includes: List of `#include`/`#import` search paths to add to this + target and all depending targets. + linkopts: Extra flags to pass to the linker. + 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 `hdrs`. If this argument is + provided, then `module_name` must also be provided. + + Warning: If a module map (whether provided here or not) is able to + be found via an include path, it will result in duplicate module + definition errors for downstream targets unless sandboxing or remote + execution is used. + module_name: The name of the Swift module being built. + + If left unspecified, the module name will be computed based on the + target's build label, by stripping the leading `//` and replacing + `/`, `:`, and other non-identifier characters with underscores. + private_deps: A list of targets that are implementation-only + dependencies of the target being built. Libraries/linker flags from + these dependencies will be propagated to dependent for linking, but + artifacts/flags required for compilation (such as .swiftmodule + files, C headers, and search paths) will not be propagated. + swift_copts: The compiler flags for the swift library. + swift_defines: A list of Swift defines to add to the compilation command + line. + + Note that unlike C-family languages, Swift defines do not have + values; they are simply identifiers that are either defined or + undefined. So strings in this list should be simple identifiers, + not `name=value` pairs. + + Each string is prepended with `-D` and added to the command line. + Unlike `swift_copts`, these flags are added for the target and + every target that depends on it, so use this attribute with caution. + It is preferred that you add defines directly to `swift_copts`, only + using this feature in the rare case that a library needs to + propagate a symbol up to those that depend on it. + swift_srcs: The sources for the swift library. + textual_hdrs: The list of C, C++, Objective-C, or Objective-C++ files + that are included as headers by source files in this rule or by + users of this library. Unlike `hdrs`, these will not be compiled + separately from the sources. + umbrella_header: A `File` representing an existing umbrella header that + should be used in the generated module map or is used in the custom + module map, or `None` (the default) if the umbrella header should be + generated based on `hdrs`. A symlink to this header is added to an + include path such that `#import ` works for + this and downstream targets. + deps: A list of targets that are dependencies of the target being built. + **kwargs: Additional arguments to pass to the underlying clang and swift + library targets. + """ + features = kwargs.pop("features", []) + tags = kwargs.pop("tags", []) + visibility = kwargs.pop("visibility", None) + + internal_tags = (["manual"] + tags) if "manual" not in tags else [] + + if not kwargs.pop("generates_header", True): + fail( + """\ +Mixed language libraries must generate an Objective-C interop header.\ +""", + attr = "generates_header", + ) + if kwargs.pop("copts", None): + fail( + "Use 'clang_copts' and/or 'swift_copts' to set copts.", + attr = "copts", + ) + if kwargs.pop("implementation_deps", None): + fail( + """\ +'implementation_deps' is not supported. Use 'private_deps' to set \ +implementation deps.\ +""", + attr = "implementation_deps", + ) + + if not clang_srcs and not hdrs: + fail("'clang_srcs' or 'hdrs' must not be empty.", attr = "clang_srcs") + if not swift_srcs: + fail("'swift_srcs' must not be empty.", attr = "swift_srcs") + + if not module_name: + module_name = derive_module_name(native.package_name(), name) + + if not module_map: + internal_modulemap_name = name + "_modulemap" + _mixed_language_library_internal_module_map( + name = internal_modulemap_name, + actual_name = name, + hdrs = hdrs, + module_name = module_name, + tags = internal_tags, + textual_hdrs = textual_hdrs, + # We can't use a generated umbrella header here, it results in + # duplicated headers being found. Ther is probvably a way to make + # that work, but I don't know of it right now. + umbrella_header = umbrella_header, + ) + module_map = ":" + internal_modulemap_name + elif not module_name: + fail( + "If 'module_map' is provided, 'module_name' must also be provided.", + attr = "module_name", + ) + + if not umbrella_header: + internal_umbrella_header_name = name + "_umbrella_header" + _mixed_language_library_umbrella_header( + name = internal_umbrella_header_name, + actual_name = name, + hdrs = hdrs, + includes = includes, + module_name = module_name, + tags = internal_tags, + ) + umbrella_header = ":" + internal_umbrella_header_name + + # We need both targets to depend on this to get the includes path that + # lets `#import ` work, since minimally it + # might be in the `-Swift.h` header. + private_deps = private_deps + [umbrella_header] + + # The umbrella header is a public header + adjusted_hdrs = [umbrella_header] + hdrs + + # `_headers` is the public interface of the `_clang` target, to be consumed + # by the `_swift` target. It won't be propagated to dependers of the mixed + # language library. The `_swift_interop` aspect hint allows the `_swift` + # target to import the module map for the `_clang` target. + internal_swift_interop_name = name + "_swift_interop" + swift_interop_hint( + name = internal_swift_interop_name, + module_map = module_map, + module_name = module_name, + ) + headers_library_name = name + "_headers" + native.objc_library( + name = headers_library_name, + hdrs = adjusted_hdrs, + aspect_hints = [":" + internal_swift_interop_name], + defines = clang_defines, + features = features, + includes = includes, + tags = internal_tags, + textual_hdrs = textual_hdrs, + **kwargs + ) + + swift_library_name = name + "_swift" + swift_library( + name = swift_library_name, + srcs = swift_srcs, + copts = ["-import-underlying-module"] + swift_copts, + defines = swift_defines, + # We generate a module map in `_mixed_language_library` instead + features = features + ["swift.no_generated_module_map"], + generates_header = True, + # This should be the default for `swift_library`, but is a breaking + # change + generated_header_name = module_name + "-Swift.h", + linkopts = linkopts, + module_name = module_name, + # The `_headers` target will have headers set for inputs, set + # `includes`, and set `SwiftInfo.clang.modulemap` (via the + # `_swift_interop` target). It needs to be a private dep to prevent from + # propagating outside of these targets. The final + # `_mixed_language_library` target will propagate the umbrella header + # and extended module map. + private_deps = [":" + headers_library_name] + private_deps, + tags = internal_tags, + deps = deps, + **kwargs + ) + + # TODO: Should this support `cc_library`, and if so, how do we choose? + # We can't look at the `clang_srcs` attribute because it might be a + # `select`. + clang_library_name = name + "_clang" + native.objc_library( + name = clang_library_name, + srcs = clang_srcs, + hdrs = adjusted_hdrs, + copts = clang_copts, + defines = clang_defines, + enable_modules = enable_modules, + features = features, + includes = includes, + # The `_swift` target is an implementation dep because the + # `_mixed_language_library` will propagate the final `SwiftInfo` + implementation_deps = [":" + swift_library_name] + private_deps, + linkopts = linkopts, + tags = internal_tags, + textual_hdrs = textual_hdrs, + deps = deps, + **kwargs + ) + + # `_mixed_language_library` creates an extended modulemap that includes + # the `.Swift` submodule. It returns a new `SwiftInfo` that has that + # module map set so downstream Swift targets will see it. If the + # `swift.propagate_generated_module_map` feature is set, then an include + # path is set in `CcInfo.compilation+context` so that downstream Clang + # targets will set it via implicit search. + _mixed_language_library( + name = name, + clang_target = ":" + clang_library_name, + features = features, + module_map = module_map, + module_name = module_name, + swift_target = ":" + swift_library_name, + tags = tags, + umbrella_header = umbrella_header, + visibility = visibility, + # We collect the same deps as the `_swift` target in order to propagate + # transitive `SwiftInfo`. It's inefficent to try to do that from the + # `SwiftInfo` of the `_swift` target. + deps = deps, + **kwargs + )