diff --git a/.bazelrc b/.bazelrc index ab04f51f7..4a132c30e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -11,6 +11,12 @@ build --macos_minimum_os=13.0 # dependencies cause us to use a newer version build --check_direct_dependencies=off +# This is needed for Bazel 6 compatibility. +# It's enabled (and can't be disabled) in Bazel 7. +# TODO: Remove this once we drop Bazel 6 support. +# See also: https://github.com/bazelbuild/bazel/issues/14327 +build --experimental_enable_aspect_hints + # Make sure no warnings slip into the C++ tools we vendor build --features treat_warnings_as_errors diff --git a/doc/BUILD b/doc/BUILD index 4e38d3cea..61998df11 100644 --- a/doc/BUILD +++ b/doc/BUILD @@ -21,11 +21,11 @@ _DOC_SRCS = { ], "rules": [ "swift_binary", - "swift_c_module", "swift_compiler_plugin", "universal_swift_compiler_plugin", "swift_feature_allowlist", "swift_import", + "swift_interop_hint", "swift_library", "swift_library_group", "swift_module_alias", diff --git a/doc/README.md b/doc/README.md index d0268b088..8fbfdf7f1 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,8 +3,8 @@ ## BUILD Rules * [swift_binary](rules.md#swift_binary) -* [swift_c_module](rules.md#swift_c_module) * [swift_import](rules.md#swift_import) +* [swift_interop_hint](rules.md#swift_interop_hint) * [swift_library](rules.md#swift_library) * [swift_module_alias](rules.md#swift_module_alias) * [swift_proto_library](rules.md#swift_proto_library) diff --git a/doc/api.md b/doc/api.md index d5d386a69..0d57fa156 100644 --- a/doc/api.md +++ b/doc/api.md @@ -103,7 +103,7 @@ Compiles a Swift module. | additional_inputs | A list of `File`s representing additional input files that need to be passed to the Swift compile action because they are referenced by compiler flags. | `[]` | | copts | A list of compiler flags that apply to the target being built. These flags, along with those from Bazel's Swift configuration fragment (i.e., `--swiftcopt` command line flags) are scanned to determine whether whole module optimization is being requested, which affects the nature of the output files. | `[]` | | defines | Symbols that should be defined by passing `-D` to the compiler. | `[]` | -| deps | Non-private dependencies of the target being compiled. These targets are used as dependencies of both the Swift module being compiled and the Clang module for the generated header. These targets must propagate one of the following providers: `CcInfo`, `SwiftInfo`, or `apple_common.Objc`. | `[]` | +| deps | Non-private dependencies of the target being compiled. These targets are used as dependencies of both the Swift module being compiled and the Clang module for the generated header. These targets must propagate `CcInfo` or `SwiftInfo`. | `[]` | | extra_swift_infos | Extra `SwiftInfo` providers that aren't contained by the `deps` of the target being compiled but are required for compilation. | `[]` | | feature_configuration | A feature configuration obtained from `swift_common.configure_features`. | none | | generated_header_name | The name of the Objective-C generated header that should be generated for this module. If omitted, no header will be generated. | `None` | @@ -112,7 +112,7 @@ Compiles a Swift module. | module_name | The name of the Swift module being compiled. This must be present and valid; use `swift_common.derive_module_name` to generate a default from the target's label if needed. | none | | package_name | The semantic package of the name of the Swift module being compiled. | none | | plugins | A list of `SwiftCompilerPluginInfo` providers that represent plugins that should be loaded by the compiler. | `[]` | -| private_deps | Private (implementation-only) dependencies of the target being compiled. These are only used as dependencies of the Swift module, not of the Clang module for the generated header. These targets must propagate one of the following providers: `CcInfo`, `SwiftInfo`, or `apple_common.Objc`. | `[]` | +| private_deps | Private (implementation-only) dependencies of the target being compiled. These are only used as dependencies of the Swift module, not of the Clang module for the generated header. These targets must propagate `CcInfo` or `SwiftInfo`. | `[]` | | srcs | The Swift source files to compile. | none | | swift_toolchain | The `SwiftToolchainInfo` provider of the toolchain. | none | | target_name | The name of the target for which the code is being compiled, which is used to determine unique file paths for the outputs. | none | @@ -215,31 +215,12 @@ An opaque value representing the feature configuration that can be ## swift_common.create_clang_module
-swift_common.create_clang_module(compilation_context, module_map, precompiled_module)
+swift_common.create_clang_module(compilation_context, module_map, precompiled_module,
+                                 strict_includes)
 
Creates a value representing a Clang module used as a Swift dependency. -Note: The `compilation_context` argument of this function is primarily -intended to communicate information *to* the Swift build rules, not to -retrieve information *back out.* In most cases, it is better to depend on -the `CcInfo` provider propagated by a Swift target to collect transitive -C/Objective-C compilation information about that target. This is because the -context used when compiling the module itself may not be the same as the -context desired when depending on it. (For example, `apple_common.Objc` -supports "strict include paths" which are only propagated to direct -dependents.) - -One valid exception to the guidance above is retrieving the generated header -associated with a specific Swift module. Since the `CcInfo` provider -propagated by the library will have already merged them transitively (or, -in the case of a hypothetical custom rule that propagates multiple direct -modules, the `direct_public_headers` of the `CcInfo` would also have them -merged), it is acceptable to read the headers from the compilation context -of the module struct itself in order to associate them with the module that -generated them. - - **PARAMETERS** @@ -248,11 +229,13 @@ generated them. | compilation_context | A `CcCompilationContext` that contains the header files and other context (such as include paths, preprocessor defines, and so forth) needed to compile this module as an explicit module. | none | | module_map | The text module map file that defines this module. This argument may be specified as a `File` or as a `string`; in the latter case, it is assumed to be the path to a file that cannot be provided as an action input because it is outside the workspace (for example, the module map for a module from an Xcode SDK). | none | | precompiled_module | A `File` representing the precompiled module (`.pcm` file) if one was emitted for the module. This may be `None` if no explicit module was built for the module; in that case, targets that depend on the module will fall back to the text module map and headers. | `None` | +| strict_includes | A `depset` of strings representing additional Clang include paths that should be passed to the compiler when this module is a _direct_ dependency of the module being compiled. May be `None`. **This field only exists to support a specific legacy use case and should otherwise not be used, as it is fundamentally incompatible with Swift's import model.** | `None` | **RETURNS** -A `struct` containing the `compilation_context`, `module_map`, and - `precompiled_module` fields provided as arguments. +A `struct` containing the `compilation_context`, `module_map`, + `precompiled_module`, and `strict_includes` fields provided as + arguments. @@ -421,8 +404,8 @@ A new `SwiftInfo` provider with the given values. ## swift_common.create_swift_interop_info
-swift_common.create_swift_interop_info(module_map, module_name, requested_features, swift_infos,
-                                       unsupported_features)
+swift_common.create_swift_interop_info(exclude_headers, module_map, module_name, requested_features,
+                                       suppressed, swift_infos, unsupported_features)
 
Returns a provider that lets a target expose C/Objective-C APIs to Swift. @@ -456,9 +439,11 @@ exclude dependencies if necessary. | Name | Description | Default Value | | :------------- | :------------- | :------------- | +| 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 compilation context. If this argument is provided, then `module_name` must also be provided. | `None` | | module_name | A string denoting the name of the module, or `None` (the default) if the name should be derived automatically from the target label. | `None` | | requested_features | A list of features (empty by default) that should be requested for the target, which are added to those supplied in the `features` attribute of the target. These features will be enabled unless they are otherwise marked as unsupported (either on the target or by the toolchain). This allows the rule implementation to have additional control over features that should be supported by default for all instances of that rule as if it were creating the feature configuration itself; for example, a rule can request that `swift.emit_c_module` always be enabled for its targets even if it is not explicitly enabled in the toolchain or on the target directly. | `[]` | +| suppressed | A `bool` indicating whether the module that the aspect would create for the target should instead be suppressed. | `False` | | swift_infos | A list of `SwiftInfo` providers from dependencies, which will be merged with the new `SwiftInfo` created by the aspect. | `[]` | | unsupported_features | A list of features (empty by default) that should be considered unsupported for the target, which are added to those supplied as negations in the `features` attribute. This allows the rule implementation to have additional control over features that should be disabled by default for all instances of that rule as if it were creating the feature configuration itself; for example, a rule that processes frameworks with headers that do not follow strict layering can request that `swift.strict_module` always be disabled for its targets even if it is enabled by default in the toolchain. | `[]` | diff --git a/doc/doc.bzl b/doc/doc.bzl index 55a0966d5..6b51d7d2e 100644 --- a/doc/doc.bzl +++ b/doc/doc.bzl @@ -54,12 +54,12 @@ load( _deprecated_swift_grpc_library = "deprecated_swift_grpc_library", _deprecated_swift_proto_library = "deprecated_swift_proto_library", _swift_binary = "swift_binary", - _swift_c_module = "swift_c_module", # api _swift_common = "swift_common", _swift_compiler_plugin = "swift_compiler_plugin", _swift_feature_allowlist = "swift_feature_allowlist", _swift_import = "swift_import", + _swift_interop_hint = "swift_interop_hint", _swift_library = "swift_library", _swift_library_group = "swift_library_group", _swift_module_alias = "swift_module_alias", @@ -90,11 +90,11 @@ SwiftUsageInfo = _SwiftUsageInfo deprecated_swift_grpc_library = _deprecated_swift_grpc_library deprecated_swift_proto_library = _deprecated_swift_proto_library swift_binary = _swift_binary -swift_c_module = _swift_c_module swift_compiler_plugin = _swift_compiler_plugin universal_swift_compiler_plugin = _universal_swift_compiler_plugin swift_feature_allowlist = _swift_feature_allowlist swift_import = _swift_import +swift_interop_hint = _swift_interop_hint swift_library = _swift_library swift_library_group = _swift_library_group swift_module_alias = _swift_module_alias diff --git a/doc/rules.md b/doc/rules.md index 46b838e00..d98cd76e5 100644 --- a/doc/rules.md +++ b/doc/rules.md @@ -20,11 +20,11 @@ load("@build_bazel_rules_swift//proto:proto.bzl", "swift_proto_library") On this page: * [swift_binary](#swift_binary) - * [swift_c_module](#swift_c_module) * [swift_compiler_plugin](#swift_compiler_plugin) * [universal_swift_compiler_plugin](#universal_swift_compiler_plugin) * [swift_feature_allowlist](#swift_feature_allowlist) * [swift_import](#swift_import) + * [swift_interop_hint](#swift_interop_hint) * [swift_library](#swift_library) * [swift_library_group](#swift_library_group) * [swift_module_alias](#swift_module_alias) @@ -249,7 +249,7 @@ please use one of the platform-specific application rules in | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | srcs | A list of `.swift` source files that will be compiled into the library. | List of labels | optional | `[]` | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | | copts | Additional compiler options that should be passed to `swiftc`. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | @@ -263,56 +263,6 @@ please use one of the platform-specific application rules in | swiftc_inputs | Additional files that are referenced using `$(location ...)` in attributes that support location expansion. | List of labels | optional | `[]` | - - -## swift_c_module - -
-swift_c_module(name, deps, module_map, module_name, system_module_map)
-
- -Wraps one or more C targets in a new module map that allows it to be imported -into Swift to access its C interfaces. - -The `cc_library` rule in Bazel does not produce module maps that are compatible -with Swift. In order to make interop between Swift and C possible, users have -one of two options: - -1. **Use an auto-generated module map.** In this case, the `swift_c_module` - rule is not needed. If a `cc_library` is a direct dependency of a - `swift_{binary,library,test}` target, a module map will be automatically - generated for it and the module's name will be derived from the Bazel target - label (in the same fashion that module names for Swift targets are derived). - The module name can be overridden by setting the `swift_module` tag on the - `cc_library`; e.g., `tags = ["swift_module=MyModule"]`. - -2. **Use a custom module map.** For finer control over the headers that are - exported by the module, use the `swift_c_module` rule to provide a custom - module map that specifies the name of the module, its headers, and any other - module information. The `cc_library` targets that contain the headers that - you wish to expose to Swift should be listed in the `deps` of your - `swift_c_module` (and by listing multiple targets, you can export multiple - libraries under a single module if desired). Then, your - `swift_{binary,library,test}` targets should depend on the `swift_c_module` - target, not on the underlying `cc_library` target(s). - -NOTE: Swift at this time does not support interop directly with C++. Any headers -referenced by a module map that is imported into Swift must have only C features -visible, often by using preprocessor conditions like `#if __cplusplus` to hide -any C++ declarations. - -**ATTRIBUTES** - - -| Name | Description | Type | Mandatory | Default | -| :------------- | :------------- | :------------- | :------------- | :------------- | -| name | A unique name for this target. | Name | required | | -| deps | A list of C targets (or anything propagating `CcInfo`) that are dependencies of this target and whose headers may be referenced by the module map. | List of labels | optional | `[]` | -| module_map | The module map file that should be loaded to import the C library dependency into Swift. This is mutally exclusive with `system_module_map`. | Label | optional | `None` | -| module_name | The name of the top-level module in the module map that this target represents.

A single `module.modulemap` file can define multiple top-level modules. When building with implicit modules, the presence of that module map allows any of the modules defined in it to be imported. When building explicit modules, however, there is a one-to-one correspondence between top-level modules and BUILD targets and the module name must be known without reading the module map file, so it must be provided directly. Therefore, one may have multiple `swift_c_module` targets that reference the same `module.modulemap` file but with different module names and headers. | String | required | | -| system_module_map | The path to a system framework module map. This is mutually exclusive with `module_map`.

Variables `__BAZEL_XCODE_SDKROOT__` and `__BAZEL_XCODE_DEVELOPER_DIR__` will be substitued appropriately for, i.e. `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk` and `/Applications/Xcode.app/Contents/Developer` respectively. | String | optional | `""` | - - ## swift_compiler_plugin @@ -387,7 +337,7 @@ swift_library( | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | srcs | A list of `.swift` source files that will be compiled into the library. | List of labels | optional | `[]` | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | | copts | Additional compiler options that should be passed to `swiftc`. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | @@ -460,7 +410,7 @@ the `.private.swiftinterface` files are required in order to build any code that | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | | archives | The list of `.a` files provided to Swift targets that depend on this target. | List of labels | optional | `[]` | | module_name | The name of the module represented by this target. | String | required | | @@ -469,6 +419,148 @@ the `.private.swiftinterface` files are required in order to build any code that | swiftmodule | The `.swiftmodule` file provided to Swift targets that depend on this target. | Label | optional | `None` | + + +## swift_interop_hint + +
+swift_interop_hint(name, exclude_hdrs, module_map, module_name, suppressed)
+
+ +Defines an aspect hint that associates non-Swift BUILD targets with additional +information required for them to be imported by Swift. + +> [!NOTE] +> Bazel 6 users must set the `--experimental_enable_aspect_hints` flag to utilize +> this rule. In addition, downstream consumers of rules that utilize this rule +> must also set the flag. The flag is enabled by default in Bazel 7. + +Some build rules, such as `objc_library`, support interoperability with Swift +simply by depending on them; a module map is generated automatically. This is +for convenience, because the common case is that most `objc_library` targets +contain code that is compatible (i.e., capable of being imported) by Swift. + +For other rules, like `cc_library`, additional information must be provided to +indicate that a particular target is compatible with Swift. This is done using +the `aspect_hints` attribute and the `swift_interop_hint` rule. + +#### Using the automatically derived module name (recommended) + +If you want to import a non-Swift, non-Objective-C target into Swift using the +module name that is automatically derived from the BUILD label, there is no need +to declare an instance of `swift_interop_hint`. A canonical one that requests +module name derivation has been provided in +`@build_bazel_rules_swift//swift:auto_module`. Simply add it to the `aspect_hints` of +the target you wish to import: + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = ["@build_bazel_rules_swift//swift:auto_module"], +) +``` + +When this `cc_library` is a dependency of a Swift target, a module map will be +generated for it. In this case, the module's name would be `my_project_somelib`. + +#### Using an explicit module name + +If you need to provide an explicit name for the module (for example, if it is +part of a third-party library that expects to be imported with a specific name), +then you can declare your own `swift_interop_hint` target to define the name: + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = [":somelib_swift_interop"], +) + +swift_interop_hint( + name = "somelib_swift_interop", + module_name = "CSomeLib", +) +``` + +When this `cc_library` is a dependency of a Swift target, a module map will be +generated for it with the module name `CSomeLib`. + +#### Using a custom module map + +In rare cases, the automatically generated module map may not be suitable. For +example, a Swift module may depend on a C module that defines specific +submodules, and this is not handled by the Swift build rules. In this case, you +can provide the module map file using the `module_map` attribute. + +When setting the `module_map` attribute, `module_name` must also be set to the +name of the desired top-level module; it cannot be omitted. + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = [":somelib_swift_interop"], +) + +swift_interop_hint( + name = "somelib_swift_interop", + module_map = "module.modulemap", + module_name = "CSomeLib", +) +``` + +#### Suppressing a module + +As mentioned above, `objc_library` and other Objective-C targets generate +modules by default, without an explicit hint, for convenience. In some +situations, this behavior may not be desirable. For example, an `objc_library` +might contain only Objective-C++ code in its headers that would not be possible +to import into Swift at all. + +When building with implicit modules, this is not typically an issue because the +module map would only be used if Swift code tried to import it (although it does +create useless actions and compiler inputs during the build). When building with +explicit modules, however, Bazel needs to know which targets represent modules +that it can compile and which do not. + +In these cases, there is no need to declare an instance of `swift_interop_hint`. +A canonical one that suppresses module generation has been provided in +`@build_bazel_rules_swift//swift:no_module`. Simply add it to the `aspect_hints` of +the target whose module you wish to suppress: + +```build +# //my/project/BUILD +objc_library( + name = "somelib", + srcs = ["somelib.mm"], + hdrs = ["somelib.h"], + aspect_hints = ["@build_bazel_rules_swift//swift:no_module"], +) +``` + +When this `objc_library` is a dependency of a Swift target, no module map or +explicit module will be generated for it, nor will any Swift information from +its transitive dependencies be propagated. + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| exclude_hdrs | 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. | List of labels | optional | `[]` | +| module_map | An optional custom `.modulemap` file that defines the Clang module for the headers in the target to which this hint is applied.

If this attribute is omitted, a module map will be automatically generated based on the headers in the hinted target.

If this attribute is provided, then `module_name` must also be provided and match the name of the desired top-level module in the `.modulemap` file. (A single `.modulemap` file may define multiple top-level modules.) | Label | optional | `None` | +| module_name | The name that will be used to import the hinted module into Swift.

If left unspecified, the module name will be computed based on the hinted target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | +| suppressed | If `True`, the hinted target should suppress any module that it would otherwise generate. | Boolean | optional | `False` | + + ## swift_library @@ -487,7 +579,7 @@ Compiles and links Swift code into a static library and Swift module. | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | srcs | A list of `.swift` source files that will be compiled into the library. | List of labels | required | | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | | always_include_developer_search_paths | If `True`, the developer framework search paths will be added to the compilation command. This enables a Swift module to access `XCTest` without having to mark the target as `testonly = True`. | Boolean | optional | `False` | @@ -501,7 +593,7 @@ Compiles and links Swift code into a static library and Swift module. | 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. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | 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. | List of labels | optional | `[]` | -| 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.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| 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.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | swiftc_inputs | Additional files that are referenced using `$(location ...)` in attributes that support location expansion. | List of labels | optional | `[]` | @@ -526,7 +618,7 @@ need to import the grouped libraries directly. | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that should be included in the group.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that should be included in the group.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | @@ -671,10 +763,10 @@ swift_proto_library( | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | srcs | A list of `.swift` source files that will be compiled into the library. | List of labels | optional | `[]` | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | -| additional_compiler_deps | List of additional dependencies required by the generated Swift code at compile time, whose SwiftProtoInfo will be ignored.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| additional_compiler_deps | List of additional dependencies required by the generated Swift code at compile time, whose SwiftProtoInfo will be ignored.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | additional_compiler_info | Dictionary of additional information passed to the compiler targets. See the documentation of the respective compiler rules for more information on which fields are accepted and how they are used. | Dictionary: String -> String | optional | `{}` | | always_include_developer_search_paths | If `True`, the developer framework search paths will be added to the compilation command. This enables a Swift module to access `XCTest` without having to mark the target as `testonly = True`. | Boolean | optional | `False` | | alwayslink | If true, any binary that depends (directly or indirectly) on this Swift module will link in all the object files for the files listed in `srcs`, even if some contain no symbols referenced by the binary. This is useful if your code isn't explicitly called by code in the binary; for example, if you rely on runtime checks for protocol conformances added in extensions in the library but do not directly reference any other symbols in the object file that adds that conformance. | Boolean | optional | `False` | @@ -830,7 +922,7 @@ bazel test //:Tests --test_filter=TestModuleName.TestClassName/testMethodName | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | -| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | +| deps | A list of targets that are dependencies of the target being built, which will be linked into that target.

If the Swift toolchain supports implementation-only imports (`private_deps` on `swift_library`), then targets in `deps` are treated as regular (non-implementation-only) imports that are propagated both to their direct and indirect (transitive) dependents.

Allowed kinds of dependencies are:

* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` (or anything propagating `CcInfo`)

Additionally, on platforms that support Objective-C interop, `objc_library` targets (or anything propagating the `apple_common.Objc` provider) are allowed as dependencies. On platforms that do not support Objective-C interop (such as Linux), those dependencies will be **ignored.** | List of labels | optional | `[]` | | srcs | A list of `.swift` source files that will be compiled into the library. | List of labels | optional | `[]` | | data | The list of files needed by this target at runtime.

Files and targets named in the `data` attribute will appear in the `*.runfiles` area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | List of labels | optional | `[]` | | copts | Additional compiler options that should be passed to `swiftc`. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | diff --git a/examples/apple/swift_explicit_modules/BUILD b/examples/apple/swift_explicit_modules/BUILD deleted file mode 100644 index 380984838..000000000 --- a/examples/apple/swift_explicit_modules/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("//swift:swift.bzl", "swift_binary", "swift_c_module") - -# This package is meant to demonstrate the use of explicit clang module compilation. - -# In order to build with features "swift.use_c_modules" and "swift.emit_c_module", it is necessary -# to add all of the framework dependencies explicitly. SwiftShims is the only one required for -# compiling a swift binary with no imports. -swift_c_module( - name = "shims", - module_name = "SwiftShims", - system_module_map = "__BAZEL_XCODE_SDKROOT__/usr/lib/swift/shims/module.modulemap", -) - -swift_binary( - name = "hello_world", - srcs = ["main.swift"], - deps = [":shims"], -) diff --git a/examples/apple/swift_explicit_modules/main.swift b/examples/apple/swift_explicit_modules/main.swift deleted file mode 100644 index 44159b395..000000000 --- a/examples/apple/swift_explicit_modules/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("Hello world") diff --git a/examples/xplatform/c_from_swift/BUILD b/examples/xplatform/c_from_swift/BUILD index 308d748a1..41eabeff6 100644 --- a/examples/xplatform/c_from_swift/BUILD +++ b/examples/xplatform/c_from_swift/BUILD @@ -1,4 +1,9 @@ -load("//swift:swift.bzl", "swift_binary", "swift_library") +load( + "//swift:swift.bzl", + "swift_binary", + "swift_interop_hint", + "swift_library", +) licenses(["notice"]) @@ -10,16 +15,24 @@ cc_library( linkstatic = True, ) -# 2. ...but Swift can't import C++ yet, so we implement a wrapper API in C. +# 2. ...but Swift can't import C++ yet, so we implement a wrapper API in C. Use +# the `swift_interop_hint` rule to enable module map generation and provide the +# module name for these headers, since `cc_library` doesn't do enable this by +# default. cc_library( name = "c_counter", srcs = ["c_counter.cc"], hdrs = ["c_counter.h"], + aspect_hints = [":c_counter_swift_hint"], linkstatic = True, - tags = ["swift_module=CCounter"], deps = [":counter"], ) +swift_interop_hint( + name = "c_counter_swift_hint", + module_name = "CCounter", +) + # 3. The Swift library then depends on the `cc_library`. This causes a # Swift-compatible module map to be created for the `cc_library` so that the # Swift code can import it. diff --git a/swift/BUILD b/swift/BUILD index f4439ad8b..9aef22966 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -5,6 +5,7 @@ load( "per_module_swiftcopt_flag", "repeatable_string_flag", ) +load(":swift.bzl", "swift_interop_hint") package(default_visibility = ["//visibility:public"]) @@ -53,10 +54,10 @@ bzl_library( "//swift/deprecated_proto:deprecated_swift_proto_library", "//swift/internal:providers", "//swift/internal:swift_binary_test_rules", - "//swift/internal:swift_c_module", "//swift/internal:swift_common", "//swift/internal:swift_feature_allowlist", "//swift/internal:swift_import", + "//swift/internal:swift_interop_hint", "//swift/internal:swift_library", "//swift/internal:swift_library_group", "//swift/internal:swift_module_alias", @@ -93,6 +94,21 @@ repeatable_string_flag( repeatable_string_flag( name = "exec_copt", build_setting_default = [], +) + +# An aspect hint that enables module map generation for a non-Swift, +# non-Objective-C target, deriving the module name automatically based on the +# hinted target's label. +swift_interop_hint( + name = "auto_module", + visibility = ["//visibility:public"], +) + +# An aspect hint that suppresses generation of a module for a non-Swift target +# that would otherwise generate one by default, like an `objc_library`. +swift_interop_hint( + name = "no_module", + suppressed = True, visibility = ["//visibility:public"], ) diff --git a/swift/internal/BUILD b/swift/internal/BUILD index 31a669689..7ff653f46 100644 --- a/swift/internal/BUILD +++ b/swift/internal/BUILD @@ -144,16 +144,6 @@ bzl_library( deps = ["@build_bazel_rules_swift//swift/internal:feature_names"], ) -bzl_library( - name = "swift_c_module", - srcs = ["swift_c_module.bzl"], - visibility = ["//swift:__subpackages__"], - deps = [ - ":swift_common", - ":utils", - ], -) - bzl_library( name = "swift_feature_allowlist", srcs = ["swift_feature_allowlist.bzl"], @@ -210,6 +200,13 @@ bzl_library( ], ) +bzl_library( + name = "swift_interop_hint", + srcs = ["swift_interop_hint.bzl"], + visibility = ["//swift:__subpackages__"], + deps = [":swift_common"], +) + bzl_library( name = "swift_library", srcs = ["swift_library.bzl"], diff --git a/swift/internal/attrs.bzl b/swift/internal/attrs.bzl index 42f8f13c4..e95b122b9 100644 --- a/swift/internal/attrs.bzl +++ b/swift/internal/attrs.bzl @@ -221,8 +221,7 @@ def swift_deps_attr(*, additional_deps_providers = [], doc, **kwargs): Allowed kinds of dependencies are: -* `swift_c_module`, `swift_import` and `swift_library` (or anything - propagating `SwiftInfo`) +* `swift_import` and `swift_library` (or anything propagating `SwiftInfo`) * `cc_library` (or anything propagating `CcInfo`) diff --git a/swift/internal/compiling.bzl b/swift/internal/compiling.bzl index 1f285a40a..c05405eb9 100644 --- a/swift/internal/compiling.bzl +++ b/swift/internal/compiling.bzl @@ -1458,17 +1458,28 @@ def _pcm_developer_framework_paths_configurator(prerequisites, args): args, ) +def _clang_module_strict_includes(module_context): + """Returns the strict Clang include paths for a module context.""" + if not module_context.clang: + return None + strict_includes = module_context.clang.strict_includes + if not strict_includes: + return None + return strict_includes.to_list() + def _clang_search_paths_configurator(prerequisites, args): """Adds Clang search paths to the command line.""" args.add_all( - depset(transitive = [ - prerequisites.cc_compilation_context.includes, - # TODO(b/146575101): Replace with `objc_info.include` once this bug - # is fixed. See `_merge_target_providers` below for more details. - prerequisites.objc_include_paths_workaround, - ]), + prerequisites.cc_compilation_context.includes, + before_each = "-Xcc", + format_each = "-I%s", + ) + args.add_all( + prerequisites.transitive_modules, before_each = "-Xcc", format_each = "-I%s", + map_each = _clang_module_strict_includes, + uniquify = True, ) # Add Clang search paths for the workspace root and Bazel output roots. The @@ -1515,7 +1526,6 @@ def _collect_clang_module_inputs( cc_compilation_context, is_swift, modules, - objc_info, prefer_precompiled_modules): """Collects Clang module-related inputs to pass to an action. @@ -1529,8 +1539,6 @@ def _collect_clang_module_inputs( `swift_common.create_module`). The precompiled Clang modules or the textual module maps and headers of these modules (depending on the value of `prefer_precompiled_modules`) will be collected as inputs. - objc_info: The `apple_common.Objc` provider of the target being - compiled. prefer_precompiled_modules: If True, precompiled module artifacts should be preferred over textual module map files and headers for modules that have them. If False, textual module map files and headers @@ -1544,36 +1552,22 @@ def _collect_clang_module_inputs( direct_inputs = [] transitive_inputs = [] - if cc_compilation_context: - # The headers stored in the compilation context differ depending on the - # kind of action we're invoking: - if (is_swift and not prefer_precompiled_modules) or not is_swift: - # If this is a `SwiftCompile` with explicit modules disabled, the - # `headers` field is an already-computed set of the transitive - # headers of all the deps. (For an explicit module build, we skip it - # and will more selectively pick subsets for any individual modules - # that need to fallback to implicit modules in the loop below). - # - # If this is a `SwiftPrecompileCModule`, then by definition we're - # only here in a build with explicit modules enabled. We should only - # need the direct headers of the module being compiled and its - # direct dependencies (the latter because Clang needs them present - # on the file system to map them to the module that contains them.) - # However, we may also need some of the transitive headers, if the - # module has dependencies that aren't recognized as modules (e.g., - # `cc_library` targets without the `swift_module` tag) and the - # module's headers include those. This will likely over-estimate the - # needed inputs, but we can't do better without include scanning in - # Starlark. - transitive_inputs.append(cc_compilation_context.headers) - - # Some rules still use the `umbrella_header` field to propagate a header - # that they don't also include in `cc_compilation_context.headers`, so we - # also need to pull these in for the time being. - # TODO(b/142867898): This can be removed once the Swift rules start - # generating its own module map for these targets. - if objc_info: - transitive_inputs.append(objc_info.umbrella_header) + if cc_compilation_context and not is_swift: + # This is a `SwiftPrecompileCModule` action, so by definition we're + # only here in a build with explicit modules enabled. We should only + # need the direct headers of the module being compiled and its + # direct dependencies (the latter because Clang needs them present + # on the file system to map them to the module that contains them.) + # However, we may also need some of the transitive headers, if the + # module has dependencies that aren't recognized as modules (e.g., + # `cc_library` targets without an aspect hint) and the module's + # headers include those. This will likely over-estimate the needed + # inputs, but we can't do better without include scanning in + # Starlark. + transitive_inputs.append(cc_compilation_context.headers) + transitive_inputs.append( + depset(cc_compilation_context.direct_textual_headers), + ) for module in modules: clang_module = module.clang @@ -1584,22 +1578,24 @@ def _collect_clang_module_inputs( if not module.is_system and type(module_map) == "File": direct_inputs.append(module_map) - if prefer_precompiled_modules: - precompiled_module = clang_module.precompiled_module - if precompiled_module: - # For builds preferring explicit modules, use it if we have it - # and don't include any headers as inputs. - direct_inputs.append(precompiled_module) - else: - # If we don't have an explicit module, we need the transitive - # headers from the compilation context associated with the - # module. This will likely overestimate the headers that will - # actually be used in the action, but until we can use include - # scanning from Starlark, we can't compute a more precise input - # set. - transitive_inputs.append( - clang_module.compilation_context.headers, - ) + precompiled_module = clang_module.precompiled_module + + if prefer_precompiled_modules and precompiled_module: + # For builds preferring explicit modules, use it if we have it + # and don't include any headers as inputs. + direct_inputs.append(precompiled_module) + else: + # If we don't have an explicit module (or we're not using it), we + # need the transitive headers from the compilation context + # associated with the module. This will likely overestimate the + # headers that will actually be used in the action, but until we can + # use include scanning from Starlark, we can't compute a more + # precise input set. + compilation_context = clang_module.compilation_context + transitive_inputs.append(compilation_context.headers) + transitive_inputs.append( + depset(compilation_context.direct_textual_headers), + ) return swift_toolchain_config.config_result( inputs = direct_inputs, @@ -1684,7 +1680,6 @@ def _dependencies_clang_modulemaps_configurator(prerequisites, args): cc_compilation_context = prerequisites.cc_compilation_context, is_swift = prerequisites.is_swift, modules = modules, - objc_info = prerequisites.objc_info, prefer_precompiled_modules = False, ) @@ -1710,7 +1705,6 @@ def _dependencies_clang_modules_configurator(prerequisites, args): cc_compilation_context = prerequisites.cc_compilation_context, is_swift = prerequisites.is_swift, modules = modules, - objc_info = prerequisites.objc_info, prefer_precompiled_modules = True, ) @@ -2303,8 +2297,7 @@ def compile( deps: Non-private dependencies of the target being compiled. These targets are used as dependencies of both the Swift module being compiled and the Clang module for the generated header. These - targets must propagate one of the following providers: `CcInfo`, - `SwiftInfo`, or `apple_common.Objc`. + targets must propagate `CcInfo` or `SwiftInfo`. extra_swift_infos: Extra `SwiftInfo` providers that aren't contained by the `deps` of the target being compiled but are required for compilation. @@ -2328,8 +2321,7 @@ def compile( private_deps: Private (implementation-only) dependencies of the target being compiled. These are only used as dependencies of the Swift module, not of the Clang module for the generated header. These - targets must propagate one of the following providers: `CcInfo`, - `SwiftInfo`, or `apple_common.Objc`. + targets must propagate `CcInfo` or `SwiftInfo`. srcs: The Swift source files to compile. swift_toolchain: The `SwiftToolchainInfo` provider of the toolchain. target_name: The name of the target for which the code is being @@ -2443,9 +2435,9 @@ def compile( all_derived_outputs = [] # Merge the providers from our dependencies so that we have one each for - # `SwiftInfo`, `CcInfo`, and `apple_common.Objc`. Then we can pass these - # into the action prerequisites so that configurators have easy access to - # the full set of values and inputs through a single accessor. + # `SwiftInfo` and `CcInfo`. Then we can pass these into the action + # prerequisites so that configurators have easy access to the full set of + # values and inputs through a single accessor. merged_providers = _merge_targets_providers( implicit_deps_providers = swift_toolchain.implicit_deps_providers, targets = deps + private_deps, @@ -2555,9 +2547,6 @@ to use swift_common.compile(include_dev_srch_paths = ...) instead.\ is_swift = True, module_name = module_name, package_name = package_name, - objc_include_paths_workaround = ( - merged_providers.objc_include_paths_workaround - ), objc_info = merged_providers.objc_info, plugins = depset(used_plugins), source_files = srcs, @@ -2834,7 +2823,6 @@ def _precompile_clang_module( is_swift_generated_header = is_swift_generated_header, module_name = module_name, package_name = None, - objc_include_paths_workaround = depset(), objc_info = apple_common.new_objc_provider(), pcm_file = precompiled_module, source_files = [module_map_file], @@ -2874,8 +2862,7 @@ def _create_cc_compilation_context( deps: Non-private dependencies of the target being compiled. These targets are used as dependencies of both the Swift module being compiled and the Clang module for the generated header. These - targets must propagate one of the following providers: `CcInfo`, - `SwiftInfo`, or `apple_common.Objc`. + targets must propagate `CcInfo` or `SwiftInfo`. feature_configuration: A feature configuration obtained from `swift_common.configure_features`. public_hdrs: Public headers that should be propagated by the new @@ -3315,11 +3302,10 @@ def _declare_validated_generated_header(actions, generated_header_name): def _merge_targets_providers(implicit_deps_providers, targets): """Merges the compilation-related providers for the given targets. - This function merges the `CcInfo`, `SwiftInfo`, and `apple_common.Objc` - providers from the given targets into a single provider for each. These - providers are then meant to be passed as prerequisites to compilation - actions so that configurators can populate command lines and inputs based on - their data. + This function merges the `CcInfo` and `SwiftInfo` providers from the given + targets into a single provider for each. These providers are then meant to + be passed as prerequisites to compilation actions so that configurators can + populate command lines and inputs based on their data. Args: implicit_deps_providers: The implicit deps providers `struct` from the @@ -3330,10 +3316,6 @@ def _merge_targets_providers(implicit_deps_providers, targets): A `struct` containing the following fields: * `cc_info`: The merged `CcInfo` provider of the targets. - * `objc_include_paths_workaround`: A `depset` containing the include - paths from the given targets that should be passed to ClangImporter. - This is a workaround for some currently incorrect propagation - behavior that is being removed in the future. * `objc_info`: The merged `apple_common.Objc` provider of the targets. * `swift_info`: The merged `SwiftInfo` provider of the targets. """ @@ -3341,12 +3323,6 @@ def _merge_targets_providers(implicit_deps_providers, targets): objc_infos = list(implicit_deps_providers.objc_infos) swift_infos = list(implicit_deps_providers.swift_infos) - # TODO(b/146575101): This is only being used to preserve the current - # behavior of strict Objective-C include paths being propagated one more - # level than they should be. Once the remaining targets that depend on this - # behavior have been fixed, remove it. - objc_include_paths_workaround_depsets = [] - for target in targets: if CcInfo in target: cc_infos.append(target[CcInfo]) @@ -3354,15 +3330,9 @@ def _merge_targets_providers(implicit_deps_providers, targets): swift_infos.append(target[SwiftInfo]) if apple_common.Objc in target: objc_infos.append(target[apple_common.Objc]) - objc_include_paths_workaround_depsets.append( - target[apple_common.Objc].strict_include, - ) return struct( cc_info = cc_common.merge_cc_infos(cc_infos = cc_infos), - objc_include_paths_workaround = depset( - transitive = objc_include_paths_workaround_depsets, - ), objc_info = apple_common.new_objc_provider(providers = objc_infos), swift_info = create_swift_info(swift_infos = swift_infos), ) diff --git a/swift/internal/module_maps.bzl b/swift/internal/module_maps.bzl index 972ad2b0b..21ff4ce9d 100644 --- a/swift/internal/module_maps.bzl +++ b/swift/internal/module_maps.bzl @@ -14,6 +14,8 @@ """Logic for generating Clang module map files.""" +load("@bazel_skylib//lib:sets.bzl", "sets") + # TODO(#723): Remove these disables once https://github.com/bazelbuild/buildtools/issues/926 is fixed # buildifier: disable=return-value # buildifier: disable=function-docstring-return @@ -22,6 +24,7 @@ def write_module_map( module_map_file, module_name, dependent_module_names = [], + exclude_headers = [], exported_module_ids = [], public_headers = [], public_textual_headers = [], @@ -37,6 +40,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 @@ -71,34 +76,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: @@ -110,6 +136,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/providers.bzl b/swift/internal/providers.bzl index 9b3128f80..aa703a5e6 100644 --- a/swift/internal/providers.bzl +++ b/swift/internal/providers.bzl @@ -394,28 +394,10 @@ def create_clang_module( *, compilation_context, module_map, - precompiled_module = None): + precompiled_module = None, + strict_includes = None): """Creates a value representing a Clang module used as a Swift dependency. - Note: The `compilation_context` argument of this function is primarily - intended to communicate information *to* the Swift build rules, not to - retrieve information *back out.* In most cases, it is better to depend on - the `CcInfo` provider propagated by a Swift target to collect transitive - C/Objective-C compilation information about that target. This is because the - context used when compiling the module itself may not be the same as the - context desired when depending on it. (For example, `apple_common.Objc` - supports "strict include paths" which are only propagated to direct - dependents.) - - One valid exception to the guidance above is retrieving the generated header - associated with a specific Swift module. Since the `CcInfo` provider - propagated by the library will have already merged them transitively (or, - in the case of a hypothetical custom rule that propagates multiple direct - modules, the `direct_public_headers` of the `CcInfo` would also have them - merged), it is acceptable to read the headers from the compilation context - of the module struct itself in order to associate them with the module that - generated them. - Args: compilation_context: A `CcCompilationContext` that contains the header files and other context (such as include paths, preprocessor @@ -431,15 +413,23 @@ def create_clang_module( explicit module was built for the module; in that case, targets that depend on the module will fall back to the text module map and headers. + strict_includes: A `depset` of strings representing additional Clang + include paths that should be passed to the compiler when this module + is a _direct_ dependency of the module being compiled. May be + `None`. **This field only exists to support a specific legacy use + case and should otherwise not be used, as it is fundamentally + incompatible with Swift's import model.** Returns: - A `struct` containing the `compilation_context`, `module_map`, and - `precompiled_module` fields provided as arguments. + A `struct` containing the `compilation_context`, `module_map`, + `precompiled_module`, and `strict_includes` fields provided as + arguments. """ return struct( compilation_context = compilation_context, module_map = module_map, precompiled_module = precompiled_module, + strict_includes = strict_includes, ) def create_swift_module( @@ -533,8 +523,9 @@ def create_swift_info( """ direct_modules = modules + [ - provider.modules + module for provider in direct_swift_infos + for module in provider.direct_modules ] transitive_modules = [ provider.transitive_modules diff --git a/swift/internal/swift_c_module.bzl b/swift/internal/swift_c_module.bzl deleted file mode 100644 index cc84f18c3..000000000 --- a/swift/internal/swift_c_module.bzl +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright 2018 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 `swift_c_module` rule.""" - -load("@bazel_skylib//lib:dicts.bzl", "dicts") -load(":attrs.bzl", "swift_toolchain_attrs") -load(":feature_names.bzl", "SWIFT_FEATURE_SYSTEM_MODULE") -load(":providers.bzl", "SwiftInfo", "SwiftToolchainInfo") -load(":swift_common.bzl", "swift_common") -load(":utils.bzl", "merge_runfiles") - -def _swift_c_module_impl(ctx): - if ( - (ctx.file.module_map and ctx.attr.system_module_map) or - (not ctx.file.module_map and not ctx.attr.system_module_map) - ): - fail("Must specify one (and only one) of module_map and system_module_map.") - swift_toolchain = ctx.attr._toolchain[SwiftToolchainInfo] - module_map = ctx.file.module_map or ctx.attr.system_module_map - is_system = True if ctx.attr.system_module_map else False - - deps = ctx.attr.deps - cc_infos = [dep[CcInfo] for dep in deps] - swift_infos = [dep[SwiftInfo] for dep in deps if SwiftInfo in dep] - data_runfiles = [dep[DefaultInfo].data_runfiles for dep in deps] - default_runfiles = [dep[DefaultInfo].default_runfiles for dep in deps] - - if cc_infos: - cc_info = cc_common.merge_cc_infos(cc_infos = cc_infos) - compilation_context = cc_info.compilation_context - else: - compilation_context = cc_common.create_compilation_context() - cc_info = CcInfo(compilation_context = compilation_context) - - requested_features = ctx.features - if is_system: - requested_features.append(SWIFT_FEATURE_SYSTEM_MODULE) - - feature_configuration = swift_common.configure_features( - ctx = ctx, - requested_features = requested_features, - swift_toolchain = swift_toolchain, - ) - - precompiled_module = swift_common.precompile_clang_module( - actions = ctx.actions, - cc_compilation_context = compilation_context, - module_map_file = module_map, - module_name = ctx.attr.module_name, - target_name = ctx.attr.name, - swift_toolchain = swift_toolchain, - feature_configuration = feature_configuration, - swift_infos = swift_infos, - ) - - providers = [ - # We must repropagate the dependencies' DefaultInfos, otherwise we - # will lose runtime dependencies that the library expects to be - # there during a test (or a regular `bazel run`). - DefaultInfo( - data_runfiles = merge_runfiles(data_runfiles), - default_runfiles = merge_runfiles(default_runfiles), - files = depset([module_map]) if not is_system else None, - ), - swift_common.create_swift_info( - modules = [ - swift_common.create_module( - name = ctx.attr.module_name, - clang = swift_common.create_clang_module( - compilation_context = compilation_context, - module_map = module_map, - precompiled_module = precompiled_module, - ), - is_system = is_system, - ), - ], - swift_infos = swift_infos, - ), - cc_info, - ] - - return providers - -swift_c_module = rule( - attrs = dicts.add( - swift_toolchain_attrs(), - { - "module_map": attr.label( - allow_single_file = True, - doc = """\ -The module map file that should be loaded to import the C library dependency -into Swift. This is mutally exclusive with `system_module_map`. -""", - mandatory = False, - ), - "system_module_map": attr.string( - doc = """\ -The path to a system framework module map. This is mutually exclusive with `module_map`. - -Variables `__BAZEL_XCODE_SDKROOT__` and `__BAZEL_XCODE_DEVELOPER_DIR__` will be substitued -appropriately for, i.e. -`/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk` -and -`/Applications/Xcode.app/Contents/Developer` respectively. -""", - mandatory = False, - ), - "module_name": attr.string( - doc = """\ -The name of the top-level module in the module map that this target represents. - -A single `module.modulemap` file can define multiple top-level modules. When -building with implicit modules, the presence of that module map allows any of -the modules defined in it to be imported. When building explicit modules, -however, there is a one-to-one correspondence between top-level modules and -BUILD targets and the module name must be known without reading the module map -file, so it must be provided directly. Therefore, one may have multiple -`swift_c_module` targets that reference the same `module.modulemap` file but -with different module names and headers. -""", - mandatory = True, - ), - "deps": attr.label_list( - allow_empty = True, - doc = """\ -A list of C targets (or anything propagating `CcInfo`) that are dependencies of -this target and whose headers may be referenced by the module map. -""", - mandatory = False, - providers = [[CcInfo]], - ), - }, - ), - doc = """\ -Wraps one or more C targets in a new module map that allows it to be imported -into Swift to access its C interfaces. - -The `cc_library` rule in Bazel does not produce module maps that are compatible -with Swift. In order to make interop between Swift and C possible, users have -one of two options: - -1. **Use an auto-generated module map.** In this case, the `swift_c_module` - rule is not needed. If a `cc_library` is a direct dependency of a - `swift_{binary,library,test}` target, a module map will be automatically - generated for it and the module's name will be derived from the Bazel target - label (in the same fashion that module names for Swift targets are derived). - The module name can be overridden by setting the `swift_module` tag on the - `cc_library`; e.g., `tags = ["swift_module=MyModule"]`. - -2. **Use a custom module map.** For finer control over the headers that are - exported by the module, use the `swift_c_module` rule to provide a custom - module map that specifies the name of the module, its headers, and any other - module information. The `cc_library` targets that contain the headers that - you wish to expose to Swift should be listed in the `deps` of your - `swift_c_module` (and by listing multiple targets, you can export multiple - libraries under a single module if desired). Then, your - `swift_{binary,library,test}` targets should depend on the `swift_c_module` - target, not on the underlying `cc_library` target(s). - -NOTE: Swift at this time does not support interop directly with C++. Any headers -referenced by a module map that is imported into Swift must have only C features -visible, often by using preprocessor conditions like `#if __cplusplus` to hide -any C++ declarations. -""", - implementation = _swift_c_module_impl, - fragments = ["cpp"], -) diff --git a/swift/internal/swift_clang_module_aspect.bzl b/swift/internal/swift_clang_module_aspect.bzl index 31bf73cea..2cb271c7c 100644 --- a/swift/internal/swift_clang_module_aspect.bzl +++ b/swift/internal/swift_clang_module_aspect.bzl @@ -14,13 +14,17 @@ """Propagates unified `SwiftInfo` providers for C/Objective-C targets.""" +load("@bazel_skylib//lib:sets.bzl", "sets") load(":attrs.bzl", "swift_toolchain_attrs") load(":compiling.bzl", "derive_module_name", "precompile_clang_module") load(":derived_files.bzl", "derived_files") load( ":feature_names.bzl", + "SWIFT_FEATURE_EMIT_C_MODULE", + "SWIFT_FEATURE_LAYERING_CHECK", "SWIFT_FEATURE_MODULE_MAP_HOME_IS_CWD", "SWIFT_FEATURE_MODULE_MAP_NO_PRIVATE_HEADERS", + "SWIFT_FEATURE_USE_C_MODULES", ) load(":features.bzl", "configure_features", "is_feature_enabled") load(":module_maps.bzl", "write_module_map") @@ -35,7 +39,6 @@ load( load( ":utils.bzl", "compilation_context_for_explicit_module_compilation", - "get_providers", ) _MULTIPLE_TARGET_ASPECT_ATTRS = [ @@ -48,6 +51,14 @@ _MULTIPLE_TARGET_ASPECT_ATTRS = [ _SINGLE_TARGET_ASPECT_ATTRS = [ # TODO(b/151667396): Remove j2objc-specific attributes when possible. "_jre_lib", + "_j2objc_proto_toolchain", + "runtime", +] + +# TODO(b/151667396): Remove j2objc-specific attributes when possible. +_DIRECT_ASPECT_ATTRS = [ + "exports", + "_j2objc_proto_toolchain", ] _SwiftInteropInfo = provider( @@ -56,6 +67,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 @@ -73,6 +89,10 @@ implementation to supply an additional set of fixed features that should always be enabled when the aspect processes that target; for example, a rule can request that `swift.emit_c_module` always be enabled for its targets even if it is not explicitly enabled in the toolchain or on the target directly. +""", + "suppressed": """\ +A `bool` indicating whether the module that the aspect would create for the +target should instead be suppressed. """, "swift_infos": """\ A list of `SwiftInfo` providers from dependencies of the target, which will be @@ -92,9 +112,11 @@ enabled by default in the toolchain. def create_swift_interop_info( *, + exclude_headers = [], module_map = None, module_name = None, requested_features = [], + suppressed = False, swift_infos = [], unsupported_features = []): """Returns a provider that lets a target expose C/Objective-C APIs to Swift. @@ -123,6 +145,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 @@ -142,6 +166,8 @@ def create_swift_interop_info( `swift.emit_c_module` always be enabled for its targets even if it is not explicitly enabled in the toolchain or on the target directly. + suppressed: A `bool` indicating whether the module that the aspect would + create for the target should instead be suppressed. swift_infos: A list of `SwiftInfo` providers from dependencies, which will be merged with the new `SwiftInfo` created by the aspect. unsupported_features: A list of features (empty by default) that should @@ -159,77 +185,82 @@ 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, + suppressed = suppressed, swift_infos = swift_infos, unsupported_features = unsupported_features, ) -def _tagged_target_module_name(label, tags): - """Returns the module name of a `swift_module`-tagged target. - - The `swift_module` tag may take one of two forms: - - * `swift_module`: By itself, this indicates that the target is compatible - with Swift and should be given a module name that is derived from its - target label. - * `swift_module=name`: The module should be given the name `name`. +def _compute_all_excluded_headers(*, exclude_headers, target): + """Returns the full set of headers to exclude for a target. - If the `swift_module` tag is not present, no module name is used or - computed. - - Since tags are unprocessed strings, nothing prevents the `swift_module` tag - from being listed multiple times on the same target with different values. - For this reason, the aspect uses the _last_ occurrence that it finds in the - list. + This function specifically handles the `cc_library` logic around the + `include_prefix` and `strip_include_prefix` attributes, which cause Bazel to + create a virtual header (symlink) for every public header in the target. For + the generated module map to be created, we must exclude both the actual + header file and the symlink. Args: - label: The target label from which a module name should be derived, if - necessary. - tags: The list of tags from the `cc_library` target to which the aspect - is being applied. + exclude_headers: A list of `File`s representing headers that should be + excluded from the module. + target: The target to which the aspect is being applied. Returns: - If the `swift_module` tag was present, then the return value is the - explicit name if it was of the form `swift_module=name`, or the - label-derived name if the tag was not followed by a name. Otherwise, if - the tag is not present, `None` is returned. + A list containing the complete set of headers that should be excluded, + including any virtual header symlinks that match a real header in the + excluded headers list passed into the function. """ - module_name = None - for tag in tags: - if tag == "swift_module": - module_name = derive_module_name(label) - elif tag.startswith("swift_module="): - _, _, module_name = tag.partition("=") - return module_name + exclude_headers_set = sets.make(exclude_headers) + virtual_exclude_headers = [] + + for action in target.actions: + if action.mnemonic != "Symlink": + continue + + original_header = action.inputs.to_list()[0] + virtual_header = action.outputs.to_list()[0] + + if sets.contains(exclude_headers_set, original_header): + virtual_exclude_headers.append(virtual_header) + + return exclude_headers + virtual_exclude_headers def _generate_module_map( + *, actions, + aspect_ctx, compilation_context, dependent_module_names, + exclude_headers, feature_configuration, module_name, - target, - umbrella_header): + target): """Generates the module map file for the given target. Args: actions: The object used to register actions. + 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. + 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. - 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. Returns: A `File` representing the generated module map. """ @@ -251,33 +282,58 @@ def _generate_module_map( else: private_headers = compilation_context.direct_private_headers - module_map_file = derived_files.module_map( - actions = actions, - target_name = target.label.name, - ) - # Sort dependent module names and the headers to ensure a deterministic # order in the output file, in the event the compilation context would ever # change this on us. For files, use the execution path as the sorting key. def _path_sorting_key(file): return file.path + # The headers in a `cc_inc_library` are actually symlinks to headers in its + # `deps`. This interferes with layering because the `cc_inc_library` won't + # depend directly on the libraries containing headers that the symlinked + # headers include. Generating the module map with the symlinks as textual + # headers instead of modular headers fixes this. + if aspect_ctx.rule.kind == "cc_inc_library": + public_headers = [] + textual_headers = sorted( + compilation_context.direct_public_headers, + key = _path_sorting_key, + ) + else: + public_headers = sorted( + compilation_context.direct_public_headers, + key = _path_sorting_key, + ) + textual_headers = sorted( + compilation_context.direct_textual_headers, + key = _path_sorting_key, + ) + + module_map_file = derived_files.module_map( + actions = actions, + target_name = target.label.name, + ) + + if exclude_headers: + # If we're excluding headers from the module map, make sure to pick up + # any virtual header symlinks that might be created, for example, by a + # `cc_library` using the `include_prefix` and/or `strip_include_prefix` + # attributes. + exclude_headers = _compute_all_excluded_headers( + exclude_headers = exclude_headers, + target = target, + ) + 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, private_headers = sorted(private_headers, key = _path_sorting_key), - public_headers = sorted( - compilation_context.direct_public_headers, - key = _path_sorting_key, - ), - public_textual_headers = sorted( - compilation_context.direct_textual_headers, - key = _path_sorting_key, - ), - umbrella_header = umbrella_header, + public_headers = public_headers, + public_textual_headers = textual_headers, workspace_relative = workspace_relative, ) return module_map_file @@ -310,56 +366,77 @@ def _objc_library_module_info(aspect_ctx): return module_name, module_map_file # TODO(b/151667396): Remove j2objc-specific knowledge. -def _j2objc_umbrella_workaround(target): - """Tries to find and return the umbrella header for a J2ObjC target. +def _j2objc_compilation_context(target): + """Construct and return a compilation context for a J2ObjC target This is an unfortunate hack/workaround needed for J2ObjC, which needs to use an umbrella header that `#include`s, rather than `#import`s, the headers in - the module due to the way they're segmented. + the module due to the way they're segmented. Additionally, the headers + need a header search path set for them to be found. It's also somewhat ugly in the way that it has to find the umbrella header, which is tied to Bazel's built-in module map generation. Since there's not a direct umbrella header field in `ObjcProvider`, we scan the target's actions to find the one that writes it out. Then, we return it and a new compilation - context with the direct headers from the `ObjcProvider`, since the generated - headers are not accessible via `CcInfo`--Java targets to which the J2ObjC - aspect are applied do not propagate `CcInfo` directly, but a native Bazel - provider that wraps the `CcInfo`, and we have no access to it from Starlark. + context with the direct headers from the `CcInfo` of the J2ObjC aspect. Args: target: The target to which the aspect is being applied. Returns: - A tuple containing two elements: - - * A `File` representing the umbrella header generated by the target, - or `None` if there was none. - * A `CcCompilationContext` containing the direct generated headers of - the J2ObjC target (including the umbrella header), or `None` if the - target did not generate an umbrella header. + A `CcCompilationContext` containing the direct generated headers of + the J2ObjC target (including the umbrella header), or `None` if the + target did not generate an umbrella header. """ for action in target.actions: if action.mnemonic != "UmbrellaHeader": continue umbrella_header = action.outputs.to_list()[0] - compilation_context = cc_common.create_compilation_context( - headers = depset( - target[apple_common.Objc].direct_headers + [umbrella_header], - ), + headers = target[CcInfo].compilation_context.direct_headers + + if JavaInfo not in target: + # This is not a `java_library`, but a `proto_library` processed by J2ObjC. We need to + # treat the generated headers as textual, but do not need the special header search path + # logic like we do for `java_library` targets below. + return cc_common.create_compilation_context( + headers = depset([umbrella_header]), + direct_textual_headers = headers, + ) + + include_paths = sets.make() + for header in headers: + header_path = header.path + + if "/_j2objc/src_jar_files/" in header_path: + # When source jars are used the headers are generated within a tree artifact. + # We can use the path to the tree artifact as the include search path. + include_path = header_path + else: + # J2ObjC generates headers within //_j2objc//. + # Compute that path to use as the include search path. + header_path_components = header_path.split("/") + j2objc_index = header_path_components.index("_j2objc") + include_path = "/".join(header_path_components[:j2objc_index + 2]) + + sets.insert(include_paths, include_path) + + return cc_common.create_compilation_context( + headers = depset([umbrella_header]), + direct_textual_headers = headers, + includes = depset(sets.to_list(include_paths)), ) - return umbrella_header, compilation_context - return None, None + return None def _module_info_for_target( target, aspect_ctx, compilation_context, dependent_module_names, + exclude_headers, feature_configuration, - module_name, - umbrella_header): + module_name): """Returns the module name and module map for the target. Args: @@ -369,13 +446,12 @@ 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 properties of the target. - 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. Returns: A tuple containing the module name (a string) and module map file (a @@ -399,20 +475,8 @@ def _module_info_for_target( ): return None, None - attr = aspect_ctx.rule.attr module_map_file = None - # TODO: Remove once we cherry-pick the `swift_interop_hint` rule - if not module_name and aspect_ctx.rule.kind == "cc_library": - # 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 - if not module_name: if apple_common.Objc not in target: return None, None @@ -430,21 +494,23 @@ def _module_info_for_target( if not module_map_file: module_map_file = _generate_module_map( actions = aspect_ctx.actions, + 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, target = target, - umbrella_header = umbrella_header, ) return module_name, module_map_file def _handle_module( aspect_ctx, - compilation_context, + exclude_headers, feature_configuration, module_map_file, module_name, + direct_swift_infos, swift_infos, swift_toolchain, target): @@ -452,14 +518,17 @@ def _handle_module( Args: 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 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). + direct_swift_infos: The `SwiftInfo` providers of the current target's + dependencies, which should be merged into the `SwiftInfo` provider + created and returned for this target. swift_infos: The `SwiftInfo` providers of the current target's dependencies, which should be merged into the `SwiftInfo` provider created and returned for this target. @@ -472,9 +541,14 @@ def _handle_module( attr = aspect_ctx.rule.attr all_swift_infos = ( - swift_infos + swift_toolchain.clang_implicit_deps_providers.swift_infos + direct_swift_infos + swift_infos + swift_toolchain.clang_implicit_deps_providers.swift_infos ) + if CcInfo in target: + compilation_context = target[CcInfo].compilation_context + else: + compilation_context = None + # Collect the names of Clang modules that the module being built directly # depends on. dependent_module_names = [] @@ -488,10 +562,8 @@ def _handle_module( # support legacy rules. if not module_map_file: # TODO(b/151667396): Remove j2objc-specific knowledge. - umbrella_header, new_compilation_context = _j2objc_umbrella_workaround( - target = target, - ) - if umbrella_header: + new_compilation_context = _j2objc_compilation_context(target = target) + if new_compilation_context: compilation_context = new_compilation_context module_name, module_map_file = _module_info_for_target( @@ -499,23 +571,52 @@ 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, ) if not module_map_file: if all_swift_infos: - return [create_swift_info(swift_infos = swift_infos)] + return [ + create_swift_info( + direct_swift_infos = direct_swift_infos, + swift_infos = swift_infos, + ), + ] else: return [] + compilation_contexts_to_merge_for_compilation = [compilation_context] + + # Fold the `strict_includes` from `apple_common.Objc` into the Clang module + # descriptor in `SwiftInfo` so that the `Objc` provider doesn't have to be + # passed as a separate input to Swift build APIs. + if apple_common.Objc in target: + strict_includes = target[apple_common.Objc].strict_include + compilation_contexts_to_merge_for_compilation.append( + cc_common.create_compilation_context(includes = strict_includes), + ) + else: + strict_includes = None + compilation_context_to_compile = ( compilation_context_for_explicit_module_compilation( - compilation_contexts = [compilation_context], - deps = getattr(attr, "deps", []), + compilation_contexts = ( + compilation_contexts_to_merge_for_compilation + ), + deps = [ + target + for attr_name in _MULTIPLE_TARGET_ASPECT_ATTRS + for target in getattr(attr, attr_name, []) + ] + [ + getattr(attr, attr_name) + for attr_name in _SINGLE_TARGET_ASPECT_ATTRS + if hasattr(attr, attr_name) + ], ) ) + precompiled_module = precompile_clang_module( actions = aspect_ctx.actions, cc_compilation_context = compilation_context_to_compile, @@ -536,9 +637,11 @@ def _handle_module( compilation_context = compilation_context, module_map = module_map_file, precompiled_module = precompiled_module, + strict_includes = strict_includes, ), ), ], + direct_swift_infos = direct_swift_infos, swift_infos = swift_infos, ), ] @@ -552,39 +655,124 @@ def _handle_module( return providers -def _compilation_context_for_target(target): - """Gets the compilation context to use when compiling this target's module. - - This function also handles the special case of a target that propagates an - `apple_common.Objc` provider in addition to its `CcInfo` provider, where the - former contains strict include paths that must also be added when compiling - the module. +def _collect_swift_infos_from_deps(aspect_ctx): + """Collect `SwiftInfo` providers from dependencies. Args: - target: The target to which the aspect is being applied. + aspect_ctx: The aspect's context. Returns: - A `CcCompilationContext` that contains the headers of the target being - compiled. + A tuple of lists of `SwiftInfo` providers from dependencies of the target to which + the aspect was applied. The first list contains those from attributes that should be treated + as direct, while the second list contains those from all other attributes. """ - if CcInfo not in target: - return None + direct_swift_infos = [] + swift_infos = [] - compilation_context = target[CcInfo].compilation_context + attr = aspect_ctx.rule.attr + for attr_name in _MULTIPLE_TARGET_ASPECT_ATTRS: + infos = [ + dep[SwiftInfo] + for dep in getattr(attr, attr_name, []) + if SwiftInfo in dep + ] + + if attr_name in _DIRECT_ASPECT_ATTRS: + direct_swift_infos.extend(infos) + else: + swift_infos.extend(infos) - if apple_common.Objc in target: - strict_includes = target[apple_common.Objc].strict_include - if strict_includes: - compilation_context = cc_common.merge_compilation_contexts( - compilation_contexts = [ - compilation_context, - cc_common.create_compilation_context( - includes = strict_includes, - ), - ], - ) + for attr_name in _SINGLE_TARGET_ASPECT_ATTRS: + dep = getattr(attr, attr_name, None) + if dep and SwiftInfo in dep: + if attr_name in _DIRECT_ASPECT_ATTRS: + direct_swift_infos.append(dep[SwiftInfo]) + else: + swift_infos.append(dep[SwiftInfo]) + + # TODO(b/151667396): Remove j2objc-specific knowledge. + if str(aspect_ctx.label) in ("@bazel_tools//tools/j2objc:j2objc_proto_toolchain", "//third_party/java/j2objc:proto_runtime"): + # The J2ObjC proto runtime headers are implicit dependencies of the generated J2ObjC code + # by being transitively reachable via the toolchain and runtime targets. The `SwiftInfos` of + # these targets need to be propagated as direct so that J2ObjC code using the runtime + # headers appears to have a direct dependency on them. + direct_swift_infos.extend(swift_infos) + swift_infos = [] + + return direct_swift_infos, swift_infos + +def _find_swift_interop_info(target, aspect_ctx): + """Finds a `_SwiftInteropInfo` provider associated with the target. + + This function first looks at the target itself to determine if it propagated + a `_SwiftInteropInfo` provider directly (that is, its rule implementation + function called `swift_common.create_swift_interop_info`). If it did not, + then the target's `aspect_hints` attribute is checked for a reference to a + target that propagates `_SwiftInteropInfo` (such as `swift_interop_hint`). + + It is an error if `aspect_hints` contains two or more targets that propagate + `_SwiftInteropInfo`, or if the target directly propagates the provider and + there is also any target in `aspect_hints` that propagates it. + + Args: + target: The target to which the aspect is currently being applied. + aspect_ctx: The aspect's context. - return compilation_context + Returns: + A tuple containing two elements: + + - The `_SwiftInteropInfo` associated with the target, if found; + otherwise, None. + - A list of additional `SwiftInfo` providers that are treated as + direct dependencies of the target, determined by reading attributes + from the target if it did not provide `_SwiftInteropInfo` directly. + """ + if _SwiftInteropInfo in target: + # If the target's rule implementation directly provides + # `_SwiftInteropInfo`, then it is that rule's responsibility to collect + # and merge `SwiftInfo` providers from relevant dependencies. + interop_target = target + interop_from_rule = True + default_direct_swift_infos = [] + default_swift_infos = [] + else: + # If the target's rule implementation does not directly provide + # `_SwiftInteropInfo`, then we need to collect the `SwiftInfo` providers + # from the default dependencies and returns those. Note that if a custom + # rule is used as a hint and returns a `_SwiftInteropInfo` that contains + # `SwiftInfo` providers, then we would consider the providers from the + # default dependencies and the providers from the hint; they are merged + # after the call site of this function. + interop_target = None + interop_from_rule = False + default_direct_swift_infos, default_swift_infos = _collect_swift_infos_from_deps(aspect_ctx) + + # We don't break this loop early when we find a matching hint, because we + # want to give an error message if there are two aspect hints that provide + # `_SwiftInteropInfo` (or if both the rule and an aspect hint do). + for hint in aspect_ctx.rule.attr.aspect_hints: + if _SwiftInteropInfo in hint: + if interop_target: + if interop_from_rule: + fail(("Conflicting Swift interop info from the target " + + "'{target}' ({rule} rule) and the aspect hint " + + "'{hint}'. Only one is allowed.").format( + hint = str(hint.label), + target = str(target.label), + rule = aspect_ctx.rule.kind, + )) + else: + fail(("Conflicting Swift interop info from aspect hints " + + "'{hint1}' and '{hint2}'. Only one is " + + "allowed.").format( + hint1 = str(interop_target.label), + hint2 = str(hint.label), + )) + interop_target = hint + + if interop_target: + return interop_target[_SwiftInteropInfo], default_direct_swift_infos, default_swift_infos + return None, default_direct_swift_infos, default_swift_infos def _swift_clang_module_aspect_impl(target, aspect_ctx): # Do nothing if the target already propagates `SwiftInfo`. @@ -594,30 +782,35 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx): requested_features = aspect_ctx.features unsupported_features = aspect_ctx.disabled_features - if _SwiftInteropInfo in target: - interop_info = target[_SwiftInteropInfo] + interop_info, direct_swift_infos, swift_infos = _find_swift_interop_info(target, aspect_ctx) + if interop_info: + # If the module should be suppressed, return immediately and propagate + # nothing (not even transitive dependencies). + 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) ) - swift_infos = interop_info.swift_infos + swift_infos.extend(interop_info.swift_infos) requested_features.extend(interop_info.requested_features) unsupported_features.extend(interop_info.unsupported_features) else: + exclude_headers = [] module_map_file = None module_name = None - # Collect `SwiftInfo` providers from dependencies, based on the - # attributes that this aspect traverses. - deps = [] - attr = aspect_ctx.rule.attr - for attr_name in _MULTIPLE_TARGET_ASPECT_ATTRS: - deps.extend(getattr(attr, attr_name, [])) - for attr_name in _SINGLE_TARGET_ASPECT_ATTRS: - dep = getattr(attr, attr_name, None) - if dep: - deps.append(dep) - swift_infos = get_providers(deps, SwiftInfo) + if hasattr(aspect_ctx.rule.attr, "_jre_lib"): + # TODO(b/151667396): Remove j2objc-specific knowledge. + # Force explicit modules on for targets processed by `j2objc_library`. + requested_features.extend([ + SWIFT_FEATURE_EMIT_C_MODULE, + SWIFT_FEATURE_USE_C_MODULES, + SWIFT_FEATURE_LAYERING_CHECK, + ]) + unsupported_features.append(SWIFT_FEATURE_MODULE_MAP_NO_PRIVATE_HEADERS) swift_toolchain = aspect_ctx.attr._toolchain_for_aspect[SwiftToolchainInfo] feature_configuration = configure_features( @@ -627,17 +820,14 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx): unsupported_features = unsupported_features, ) - if ( - _SwiftInteropInfo in target or - apple_common.Objc in target or - CcInfo in target - ): + if interop_info or apple_common.Objc in target or CcInfo in target: 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, + direct_swift_infos = direct_swift_infos, swift_infos = swift_infos, swift_toolchain = swift_toolchain, target = target, @@ -645,8 +835,11 @@ def _swift_clang_module_aspect_impl(target, aspect_ctx): # If it's any other rule, just merge the `SwiftInfo` providers from its # deps. - if swift_infos: - return [create_swift_info(swift_infos = swift_infos)] + if direct_swift_infos or swift_infos: + return [create_swift_info( + direct_swift_infos = direct_swift_infos, + swift_infos = swift_infos, + )] return [] @@ -665,16 +858,12 @@ artifacts, and so that Swift module artifacts are not lost when passing through a non-Swift target in the build graph (for example, a `swift_library` that depends on an `objc_library` that depends on a `swift_library`). -It also manages module map generation for `cc_library` targets that have the -`swift_module` tag. This tag may take one of two forms: - -* `swift_module`: By itself, this indicates that the target is compatible - with Swift and should be given a module name that is derived from its - target label. -* `swift_module=name`: The module should be given the name `name`. - -Note that the public headers of such `cc_library` targets must be parsable as C, -since Swift does not support C++ interop at this time. +It also manages module map generation for targets that call +`swift_common.create_swift_interop_info` and do not provide their own module +map, and for targets that use the `swift_interop_hint` aspect hint. Note that if +one of these approaches is used to interop with a target such as a `cc_library`, +the headers must be parsable as C, since Swift does not support C++ interop at +this time. Most users will not need to interact directly with this aspect, since it is automatically applied to the `deps` attribute of all `swift_binary`, diff --git a/swift/internal/swift_interop_hint.bzl b/swift/internal/swift_interop_hint.bzl new file mode 100644 index 000000000..87571f221 --- /dev/null +++ b/swift/internal/swift_interop_hint.bzl @@ -0,0 +1,204 @@ +# Copyright 2021 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 `swift_interop_hint` rule.""" + +load(":swift_common.bzl", "swift_common") + +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 = """\ +An optional custom `.modulemap` file that defines the Clang module for the +headers in the target to which this hint is applied. + +If this attribute is omitted, a module map will be automatically generated based +on the headers in the hinted target. + +If this attribute is provided, then `module_name` must also be provided and +match the name of the desired top-level module in the `.modulemap` file. (A +single `.modulemap` file may define multiple top-level modules.) +""", + mandatory = False, + ), + "module_name": attr.string( + doc = """\ +The name that will be used to import the hinted module into Swift. + +If left unspecified, the module name will be computed based on the hinted +target's build label, by stripping the leading `//` and replacing `/`, `:`, and +other non-identifier characters with underscores. +""", + mandatory = False, + ), + "suppressed": attr.bool( + default = False, + doc = """\ +If `True`, the hinted target should suppress any module that it would otherwise +generate. +""", + mandatory = False, + ), + }, + doc = """\ +Defines an aspect hint that associates non-Swift BUILD targets with additional +information required for them to be imported by Swift. + +> [!NOTE] +> Bazel 6 users must set the `--experimental_enable_aspect_hints` flag to utilize +> this rule. In addition, downstream consumers of rules that utilize this rule +> must also set the flag. The flag is enabled by default in Bazel 7. + +Some build rules, such as `objc_library`, support interoperability with Swift +simply by depending on them; a module map is generated automatically. This is +for convenience, because the common case is that most `objc_library` targets +contain code that is compatible (i.e., capable of being imported) by Swift. + +For other rules, like `cc_library`, additional information must be provided to +indicate that a particular target is compatible with Swift. This is done using +the `aspect_hints` attribute and the `swift_interop_hint` rule. + +#### Using the automatically derived module name (recommended) + +If you want to import a non-Swift, non-Objective-C target into Swift using the +module name that is automatically derived from the BUILD label, there is no need +to declare an instance of `swift_interop_hint`. A canonical one that requests +module name derivation has been provided in +`@build_bazel_rules_swift//swift:auto_module`. Simply add it to the `aspect_hints` of +the target you wish to import: + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = ["@build_bazel_rules_swift//swift:auto_module"], +) +``` + +When this `cc_library` is a dependency of a Swift target, a module map will be +generated for it. In this case, the module's name would be `my_project_somelib`. + +#### Using an explicit module name + +If you need to provide an explicit name for the module (for example, if it is +part of a third-party library that expects to be imported with a specific name), +then you can declare your own `swift_interop_hint` target to define the name: + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = [":somelib_swift_interop"], +) + +swift_interop_hint( + name = "somelib_swift_interop", + module_name = "CSomeLib", +) +``` + +When this `cc_library` is a dependency of a Swift target, a module map will be +generated for it with the module name `CSomeLib`. + +#### Using a custom module map + +In rare cases, the automatically generated module map may not be suitable. For +example, a Swift module may depend on a C module that defines specific +submodules, and this is not handled by the Swift build rules. In this case, you +can provide the module map file using the `module_map` attribute. + +When setting the `module_map` attribute, `module_name` must also be set to the +name of the desired top-level module; it cannot be omitted. + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = [":somelib_swift_interop"], +) + +swift_interop_hint( + name = "somelib_swift_interop", + module_map = "module.modulemap", + module_name = "CSomeLib", +) +``` + +#### Suppressing a module + +As mentioned above, `objc_library` and other Objective-C targets generate +modules by default, without an explicit hint, for convenience. In some +situations, this behavior may not be desirable. For example, an `objc_library` +might contain only Objective-C++ code in its headers that would not be possible +to import into Swift at all. + +When building with implicit modules, this is not typically an issue because the +module map would only be used if Swift code tried to import it (although it does +create useless actions and compiler inputs during the build). When building with +explicit modules, however, Bazel needs to know which targets represent modules +that it can compile and which do not. + +In these cases, there is no need to declare an instance of `swift_interop_hint`. +A canonical one that suppresses module generation has been provided in +`@build_bazel_rules_swift//swift:no_module`. Simply add it to the `aspect_hints` of +the target whose module you wish to suppress: + +```build +# //my/project/BUILD +objc_library( + name = "somelib", + srcs = ["somelib.mm"], + hdrs = ["somelib.h"], + aspect_hints = ["@build_bazel_rules_swift//swift:no_module"], +) +``` + +When this `objc_library` is a dependency of a Swift target, no module map or +explicit module will be generated for it, nor will any Swift information from +its transitive dependencies be propagated. +""", + implementation = _swift_interop_hint_impl, +) diff --git a/swift/internal/utils.bzl b/swift/internal/utils.bzl index 6e1f9c14a..b01cab3a7 100644 --- a/swift/internal/utils.bzl +++ b/swift/internal/utils.bzl @@ -91,12 +91,25 @@ def compilation_context_for_explicit_module_compilation( for dep in deps: if CcInfo in dep: all_compilation_contexts.append(dep[CcInfo].compilation_context) - if apple_common.Objc in dep: - all_compilation_contexts.append( - cc_common.create_compilation_context( - includes = dep[apple_common.Objc].strict_include, - ), - ) + elif SwiftInfo in dep: + # TODO(b/151667396): Remove j2objc-specific knowledge. + # J2ObjC doesn't expose `CcInfo` directly on the `java_library` + # targets it processes, but we can find the compilation context that + # was synthesized by `swift_clang_module_aspect` within the + # `SwiftInfo` provider. + for module in dep[SwiftInfo].direct_modules: + clang = module.clang + if not clang: + continue + + if clang.compilation_context: + all_compilation_contexts.append(clang.compilation_context) + if clang.strict_includes: + all_compilation_contexts.append( + cc_common.create_compilation_context( + includes = clang.strict_includes, + ), + ) return cc_common.merge_compilation_contexts( compilation_contexts = all_compilation_contexts, diff --git a/swift/swift.bzl b/swift/swift.bzl index 2bab57cfd..b392cd190 100644 --- a/swift/swift.bzl +++ b/swift/swift.bzl @@ -54,10 +54,6 @@ load( _swift_binary = "swift_binary", _swift_test = "swift_test", ) -load( - "@build_bazel_rules_swift//swift/internal:swift_c_module.bzl", - _swift_c_module = "swift_c_module", -) load( "@build_bazel_rules_swift//swift/internal:swift_clang_module_aspect.bzl", _swift_clang_module_aspect = "swift_clang_module_aspect", @@ -74,6 +70,10 @@ load( "@build_bazel_rules_swift//swift/internal:swift_import.bzl", _swift_import = "swift_import", ) +load( + "@build_bazel_rules_swift//swift/internal:swift_interop_hint.bzl", + _swift_interop_hint = "swift_interop_hint", +) load( "@build_bazel_rules_swift//swift/internal:swift_library.bzl", _swift_library = "swift_library", @@ -107,11 +107,11 @@ swift_common = _swift_common # Re-export rules. swift_binary = _swift_binary -swift_c_module = _swift_c_module swift_compiler_plugin = _swift_compiler_plugin universal_swift_compiler_plugin = _universal_swift_compiler_plugin swift_feature_allowlist = _swift_feature_allowlist swift_import = _swift_import +swift_interop_hint = _swift_interop_hint swift_library = _swift_library swift_library_group = _swift_library_group swift_module_alias = _swift_module_alias diff --git a/test/BUILD b/test/BUILD index 5b5dc2638..3097529be 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,13 +1,14 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load(":ast_file_tests.bzl", "ast_file_test_suite") load(":bzl_test.bzl", "bzl_test") +load(":cc_library_tests.bzl", "cc_library_test_suite") load(":compiler_arguments_tests.bzl", "compiler_arguments_test_suite") load(":coverage_settings_tests.bzl", "coverage_settings_test_suite") load(":debug_settings_tests.bzl", "debug_settings_test_suite") -load(":explicit_modules_test.bzl", "explicit_modules_test_suite") load(":features_tests.bzl", "features_test_suite") load(":generated_header_tests.bzl", "generated_header_test_suite") load(":imported_framework_tests.bzl", "imported_framework_test_suite") +load(":interop_hints_tests.bzl", "interop_hints_test_suite") load(":mainattr_tests.bzl", "mainattr_test_suite") load(":module_cache_settings_tests.bzl", "module_cache_settings_test_suite") load(":module_interface_tests.bzl", "module_interface_test_suite") @@ -26,6 +27,8 @@ ast_file_test_suite(name = "ast_file") compiler_arguments_test_suite(name = "compiler_arguments") +cc_library_test_suite(name = "cc_library") + coverage_settings_test_suite(name = "coverage_settings") debug_settings_test_suite(name = "debug_settings") @@ -44,6 +47,8 @@ output_file_map_test_suite(name = "output_file_map") module_interface_test_suite(name = "module_interface") +interop_hints_test_suite() + private_deps_test_suite(name = "private_deps") swift_through_non_swift_test_suite(name = "swift_through_non_swift") @@ -54,8 +59,6 @@ pch_output_dir_test_suite(name = "pch_output_dir_settings") private_swiftinterface_test_suite(name = "private_swiftinterface") -explicit_modules_test_suite(name = "explicit_modules") - xctest_runner_test_suite(name = "xctest_runner") utils_test_suite(name = "utils") diff --git a/test/cc_library_tests.bzl b/test/cc_library_tests.bzl new file mode 100644 index 000000000..2ec50a33e --- /dev/null +++ b/test/cc_library_tests.bzl @@ -0,0 +1,52 @@ +# Copyright 2021 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. + +"""Tests for interoperability with `cc_library`-specific features.""" + +load( + "@bazel_skylib//rules:build_test.bzl", + "build_test", +) + +def cc_library_test_suite(name): + """Test suite for interoperability with `cc_library`-specific features.""" + + # Verify that Swift can import a `cc_library` that uses `include_prefix`, + # `strip_include_prefix`, or both. + build_test( + name = "{}_swift_imports_cc_library_with_include_prefix_manipulation".format(name), + targets = [ + "@build_bazel_rules_swift//test/fixtures/cc_library:import_prefix_and_strip_prefix", + "@build_bazel_rules_swift//test/fixtures/cc_library:import_prefix_only", + "@build_bazel_rules_swift//test/fixtures/cc_library:import_strip_prefix_only", + ], + tags = [name], + ) + + # Verify that `swift_interop_hint.exclude_hdrs` correctly excludes headers + # from a `cc_library` that uses `include_prefix` and/or + # `strip_include_prefix` (i.e., both the real header and the virtual header + # are excluded). + build_test( + name = "{}_swift_interop_hint_excludes_headers_with_include_prefix_manipulation".format(name), + targets = [ + "@build_bazel_rules_swift//test/fixtures/cc_library:import_prefix_and_strip_prefix_with_exclusion", + ], + tags = [name], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/explicit_modules_test.bzl b/test/explicit_modules_test.bzl deleted file mode 100644 index ba5770565..000000000 --- a/test/explicit_modules_test.bzl +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2022 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. - -"""Tests for explicit module compilation command line flags""" - -load( - "@build_bazel_rules_swift//test/rules:action_command_line_test.bzl", - "make_action_command_line_test_rule", -) - -explicit_modules_action_command_line_test = make_action_command_line_test_rule( - config_settings = { - "//command_line_option:features": [ - "swift.use_c_modules", - "swift.emit_c_module", - "swift.supports_system_module_flag", - ], - }, -) - -implicit_modules_action_command_line_test = make_action_command_line_test_rule() - -def explicit_modules_test_suite(name): - """Test suite for explicit clang module compilation. - - Args: - name: the base name to be used in things created by this macro - """ - - # Verify that swift libraries compile with the specified module file from deps. - explicit_modules_action_command_line_test( - name = "{}_enabled_swift_side_test".format(name), - expected_argv = [ - "-fmodule-file=SwiftShims", - "-fno-implicit-module-maps", - "-fno-implicit-modules", - ], - mnemonic = "SwiftCompile", - tags = [name], - target_under_test = "@build_bazel_rules_swift//test/fixtures/explicit_modules:simple", - target_compatible_with = ["@platforms//os:macos"], - ) - - # Verify that the swift_c_module precompiles. - explicit_modules_action_command_line_test( - name = "{}_enabled_c_module_side".format(name), - expected_argv = [ - "-fsystem-module", - "-module-name SwiftShims", - "-emit-pcm", - "-fno-implicit-module-maps", - "-fno-implicit-modules", - ], - mnemonic = "SwiftPrecompileCModule", - tags = [name], - target_under_test = "@build_bazel_rules_swift//test/fixtures/explicit_modules:shims", - target_compatible_with = ["@platforms//os:macos"], - ) - - # Verify that a swift_c_module with dependencies precompiles. - explicit_modules_action_command_line_test( - name = "{}_enabled_c_module_deps".format(name), - expected_argv = [ - "-fsystem-module", - "-fmodule-file=_Builtin_stddef_max_align_t", - "-fmodule-map-file=__BAZEL_XCODE_DEVELOPER_DIR__/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include/module.modulemap", - "-module-name Darwin", - "-emit-pcm", - "-fno-implicit-module-maps", - "-fno-implicit-modules", - ], - mnemonic = "SwiftPrecompileCModule", - tags = [name], - target_under_test = "@build_bazel_rules_swift//test/fixtures/explicit_modules:Darwin", - target_compatible_with = ["@platforms//os:macos"], - ) - - # Verify that the default behavior isn't impacted. - implicit_modules_action_command_line_test( - name = "{}_disabled_test".format(name), - not_expected_argv = [ - "-fmodule-file=SwiftShims", - "-fno-implicit-module-maps", - "-fno-implicit-modules", - ], - mnemonic = "SwiftCompile", - tags = [name], - target_under_test = "@build_bazel_rules_swift//test/fixtures/explicit_modules:simple", - ) - - native.test_suite( - name = name, - tags = [name], - ) diff --git a/test/fixtures/BUILD b/test/fixtures/BUILD index ae789254e..6c4e8d7e8 100644 --- a/test/fixtures/BUILD +++ b/test/fixtures/BUILD @@ -2,6 +2,12 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") package(default_visibility = ["//test:__subpackages__"]) +bzl_library( + name = "starlark_tests_bzls", + srcs = glob(["*.bzl"]), + deps = ["//swift"], +) + bzl_library( name = "common", srcs = ["common.bzl"], diff --git a/test/fixtures/cc_library/BUILD b/test/fixtures/cc_library/BUILD new file mode 100644 index 000000000..7e473a8e3 --- /dev/null +++ b/test/fixtures/cc_library/BUILD @@ -0,0 +1,95 @@ +load( + "//swift:swift.bzl", + "swift_interop_hint", + "swift_library", +) +load("//test/fixtures:common.bzl", "FIXTURE_TAGS") + +package( + default_visibility = ["//test:__subpackages__"], +) + +licenses(["notice"]) + +############################################################################### +# Fixtures for testing complex `cc_library` interop cases. + +cc_library( + name = "prefix_only", + hdrs = [ + "header.h", + "header_prefix_only.h", + ], + aspect_hints = ["//swift:auto_module"], + include_prefix = "some/prefix", + tags = FIXTURE_TAGS, +) + +cc_library( + name = "strip_prefix_only", + hdrs = [ + "header.h", + "header_strip_prefix_only.h", + ], + aspect_hints = ["//swift:auto_module"], + strip_include_prefix = "//test", + tags = FIXTURE_TAGS, +) + +cc_library( + name = "prefix_and_strip_prefix", + hdrs = [ + "header.h", + "header_prefix_and_strip_prefix.h", + ], + aspect_hints = ["//swift:auto_module"], + include_prefix = "some/prefix", + strip_include_prefix = "//test", + tags = FIXTURE_TAGS, +) + +cc_library( + name = "prefix_and_strip_prefix_with_exclusion", + hdrs = [ + "header.h", + "header_with_error.h", + ], + aspect_hints = [":prefix_and_strip_prefix_with_exclusion_swift_hint"], + include_prefix = "some/prefix", + strip_include_prefix = "//test", + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "prefix_and_strip_prefix_with_exclusion_swift_hint", + exclude_hdrs = ["header_with_error.h"], + tags = FIXTURE_TAGS, +) + +swift_library( + name = "import_prefix_only", + srcs = ["ImportPrefixOnly.swift"], + tags = FIXTURE_TAGS, + deps = [":prefix_only"], +) + +swift_library( + name = "import_strip_prefix_only", + srcs = ["ImportStripPrefixOnly.swift"], + tags = FIXTURE_TAGS, + deps = [":strip_prefix_only"], +) + +swift_library( + name = "import_prefix_and_strip_prefix", + srcs = ["ImportPrefixAndStripPrefix.swift"], + tags = FIXTURE_TAGS, + deps = [":prefix_and_strip_prefix"], +) + +swift_library( + name = "import_prefix_and_strip_prefix_with_exclusion", + srcs = ["ImportPrefixAndStripPrefixWithExclusion.swift"], + tags = FIXTURE_TAGS, + deps = [":prefix_and_strip_prefix_with_exclusion"], +) diff --git a/test/fixtures/cc_library/ImportPrefixAndStripPrefix.swift b/test/fixtures/cc_library/ImportPrefixAndStripPrefix.swift new file mode 100644 index 000000000..fa63322ff --- /dev/null +++ b/test/fixtures/cc_library/ImportPrefixAndStripPrefix.swift @@ -0,0 +1,3 @@ +import test_fixtures_cc_library_prefix_and_strip_prefix + +func f(_ x: some_structure) {} diff --git a/test/fixtures/cc_library/ImportPrefixAndStripPrefixWithExclusion.swift b/test/fixtures/cc_library/ImportPrefixAndStripPrefixWithExclusion.swift new file mode 100644 index 000000000..83e47256d --- /dev/null +++ b/test/fixtures/cc_library/ImportPrefixAndStripPrefixWithExclusion.swift @@ -0,0 +1,3 @@ +import test_fixtures_cc_library_prefix_and_strip_prefix_with_exclusion + +func f(_ x: some_structure) {} diff --git a/test/fixtures/cc_library/ImportPrefixOnly.swift b/test/fixtures/cc_library/ImportPrefixOnly.swift new file mode 100644 index 000000000..75b9bf61e --- /dev/null +++ b/test/fixtures/cc_library/ImportPrefixOnly.swift @@ -0,0 +1,3 @@ +import test_fixtures_cc_library_prefix_only + +func f(_ x: some_structure) {} diff --git a/test/fixtures/cc_library/ImportStripPrefixOnly.swift b/test/fixtures/cc_library/ImportStripPrefixOnly.swift new file mode 100644 index 000000000..5a9910a7f --- /dev/null +++ b/test/fixtures/cc_library/ImportStripPrefixOnly.swift @@ -0,0 +1,3 @@ +import test_fixtures_cc_library_strip_prefix_only + +func f(_ x: some_structure) {} diff --git a/test/fixtures/cc_library/header.h b/test/fixtures/cc_library/header.h new file mode 100644 index 000000000..0ce38ca4e --- /dev/null +++ b/test/fixtures/cc_library/header.h @@ -0,0 +1,8 @@ +#ifndef RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_H_ +#define RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_H_ + +typedef struct { + int field; +} some_structure; + +#endif // RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_H_ diff --git a/test/fixtures/cc_library/header_prefix_and_strip_prefix.h b/test/fixtures/cc_library/header_prefix_and_strip_prefix.h new file mode 100644 index 000000000..4b8f42c46 --- /dev/null +++ b/test/fixtures/cc_library/header_prefix_and_strip_prefix.h @@ -0,0 +1,6 @@ +#ifndef RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_AND_STRIP_PREFIX_H_ +#define RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_AND_STRIP_PREFIX_H_ + +#include "some/prefix/fixtures/cc_library/header.h" + +#endif // RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_AND_STRIP_PREFIX_H_ diff --git a/test/fixtures/cc_library/header_prefix_only.h b/test/fixtures/cc_library/header_prefix_only.h new file mode 100644 index 000000000..137e8038a --- /dev/null +++ b/test/fixtures/cc_library/header_prefix_only.h @@ -0,0 +1,6 @@ +#ifndef RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_ONLY_H_ +#define RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_ONLY_H_ + +#include "some/prefix/header.h" + +#endif // RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_PREFIX_ONLY_H_ diff --git a/test/fixtures/cc_library/header_strip_prefix_only.h b/test/fixtures/cc_library/header_strip_prefix_only.h new file mode 100644 index 000000000..2f307994c --- /dev/null +++ b/test/fixtures/cc_library/header_strip_prefix_only.h @@ -0,0 +1,6 @@ +#ifndef RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_STRIP_PREFIX_ONLY_H_ +#define RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_STRIP_PREFIX_ONLY_H_ + +#include "fixtures/cc_library/header.h" + +#endif // RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_STRIP_PREFIX_ONLY_H_ diff --git a/test/fixtures/cc_library/header_with_error.h b/test/fixtures/cc_library/header_with_error.h new file mode 100644 index 000000000..6a89e9677 --- /dev/null +++ b/test/fixtures/cc_library/header_with_error.h @@ -0,0 +1,6 @@ +#ifndef RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_WITH_ERROR_H_ +#define RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_WITH_ERROR_H_ + +#error This should never get processed. + +#endif // RULES_SWIFT_TEST_FIXTURES_CC_LIBRARY_HEADER_WITH_ERROR_H_ diff --git a/test/fixtures/common.bzl b/test/fixtures/common.bzl index 7fdf657fa..472ada8ea 100644 --- a/test/fixtures/common.bzl +++ b/test/fixtures/common.bzl @@ -14,9 +14,34 @@ """Common build definitions used by test fixtures.""" +load( + "@build_bazel_rules_swift//swift:swift.bzl", + "SwiftInfo", + "swift_clang_module_aspect", +) + # Common tags that prevent the test fixtures from actually being built (i.e., # their actions executed) when running `bazel test` to do analysis testing. FIXTURE_TAGS = [ "manual", "notap", ] + +def _forward_swift_info_from_swift_clang_module_aspect_impl(ctx): + if SwiftInfo in ctx.attr.target: + return [ctx.attr.target[SwiftInfo]] + return [] + +forward_swift_info_from_swift_clang_module_aspect = rule( + attrs = { + "target": attr.label( + aspects = [swift_clang_module_aspect], + mandatory = True, + ), + }, + doc = """\ +Applies `swift_clang_module_aspect` to the given target and forwards the +`SwiftInfo` provider that it attaches to the target, if any. +""", + implementation = _forward_swift_info_from_swift_clang_module_aspect_impl, +) diff --git a/test/fixtures/explicit_modules/BUILD b/test/fixtures/explicit_modules/BUILD deleted file mode 100644 index 5ae032abf..000000000 --- a/test/fixtures/explicit_modules/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -load("//swift:swift.bzl", "swift_c_module", "swift_library") -load("//test/fixtures:common.bzl", "FIXTURE_TAGS") - -package( - default_visibility = ["//test:__subpackages__"], -) - -licenses(["notice"]) - -############################################################################### -# Fixtures for testing explicit clang module compilation. - -swift_library( - name = "simple", - srcs = ["Empty.swift"], - tags = FIXTURE_TAGS, - deps = [":shims"], -) - -swift_c_module( - name = "shims", - module_name = "SwiftShims", - system_module_map = "__BAZEL_XCODE_SDKROOT__/usr/lib/swift/shims/module.modulemap", - tags = FIXTURE_TAGS, -) - -swift_c_module( - name = "Darwin", - module_name = "Darwin", - system_module_map = "__BAZEL_XCODE_SDKROOT__/usr/include/module.modulemap", - tags = FIXTURE_TAGS, - deps = [ - ":_Builtin_stddef_max_align_t", - ], -) - -swift_c_module( - name = "_Builtin_stddef_max_align_t", - module_name = "_Builtin_stddef_max_align_t", - system_module_map = "__BAZEL_XCODE_DEVELOPER_DIR__/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include/module.modulemap", - tags = FIXTURE_TAGS, -) diff --git a/test/fixtures/interop_hints/BUILD b/test/fixtures/interop_hints/BUILD new file mode 100644 index 000000000..0f1bab1fa --- /dev/null +++ b/test/fixtures/interop_hints/BUILD @@ -0,0 +1,123 @@ +load( + "//swift:swift.bzl", + "swift_interop_hint", + "swift_library", +) +load( + "//test/fixtures:common.bzl", + "FIXTURE_TAGS", + "forward_swift_info_from_swift_clang_module_aspect", +) + +package( + default_visibility = ["//test:__subpackages__"], +) + +licenses(["notice"]) + +swift_library( + name = "import_module_name_swift", + srcs = ["ImportModuleName.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_custom_module_name"], +) + +cc_library( + name = "cc_lib_custom_module_name", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_custom_module_name_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_custom_module_name_hint", + module_name = "ModuleName", + tags = FIXTURE_TAGS, +) + +swift_library( + name = "import_submodule_swift", + srcs = ["ImportSubmodule.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_submodule"], +) + +cc_library( + name = "cc_lib_submodule", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_submodule_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_submodule_hint", + module_map = "module.modulemap", + module_name = "TopModule", + tags = FIXTURE_TAGS, +) + +swift_library( + name = "invalid_swift", + srcs = ["ImportSubmodule.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_invalid"], +) + +cc_library( + name = "cc_lib_invalid", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_invalid_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_invalid_hint", + module_map = "module.modulemap", + tags = FIXTURE_TAGS, +) + +forward_swift_info_from_swift_clang_module_aspect( + name = "objc_library_suppressed", + tags = FIXTURE_TAGS, + target = ":objc_library_suppressed_lib", + target_compatible_with = ["@platforms//os:macos"], +) + +objc_library( + name = "objc_library_suppressed_lib", + hdrs = ["header1.h"], + aspect_hints = ["//swift:no_module"], + tags = FIXTURE_TAGS, + target_compatible_with = ["@platforms//os:macos"], +) + +forward_swift_info_from_swift_clang_module_aspect( + name = "objc_library_with_swift_dep_suppressed", + tags = FIXTURE_TAGS, + target = ":objc_library_with_swift_dep_suppressed_lib", + target_compatible_with = ["@platforms//os:macos"], +) + +objc_library( + name = "objc_library_with_swift_dep_suppressed_lib", + hdrs = ["header1.h"], + aspect_hints = ["//swift:no_module"], + tags = FIXTURE_TAGS, + target_compatible_with = ["@platforms//os:macos"], + deps = [":empty_lib"], +) + +swift_library( + name = "empty_lib", + srcs = ["Empty.swift"], + tags = FIXTURE_TAGS, +) diff --git a/test/fixtures/explicit_modules/Empty.swift b/test/fixtures/interop_hints/Empty.swift similarity index 100% rename from test/fixtures/explicit_modules/Empty.swift rename to test/fixtures/interop_hints/Empty.swift diff --git a/test/fixtures/interop_hints/ImportModuleName.swift b/test/fixtures/interop_hints/ImportModuleName.swift new file mode 100644 index 000000000..f5e291cdd --- /dev/null +++ b/test/fixtures/interop_hints/ImportModuleName.swift @@ -0,0 +1 @@ +import ModuleName diff --git a/test/fixtures/interop_hints/ImportSubmodule.swift b/test/fixtures/interop_hints/ImportSubmodule.swift new file mode 100644 index 000000000..856f8cbb2 --- /dev/null +++ b/test/fixtures/interop_hints/ImportSubmodule.swift @@ -0,0 +1 @@ +import TopModule.Submodule diff --git a/test/fixtures/interop_hints/header1.h b/test/fixtures/interop_hints/header1.h new file mode 100644 index 000000000..bd9ec079d --- /dev/null +++ b/test/fixtures/interop_hints/header1.h @@ -0,0 +1 @@ +// Intentionally empty. diff --git a/test/fixtures/interop_hints/header2.h b/test/fixtures/interop_hints/header2.h new file mode 100644 index 000000000..bd9ec079d --- /dev/null +++ b/test/fixtures/interop_hints/header2.h @@ -0,0 +1 @@ +// Intentionally empty. diff --git a/test/fixtures/interop_hints/module.modulemap b/test/fixtures/interop_hints/module.modulemap new file mode 100644 index 000000000..5338c14d8 --- /dev/null +++ b/test/fixtures/interop_hints/module.modulemap @@ -0,0 +1,9 @@ +module TopModule { + header "header1.h" + export * + + module Submodule { + header "header2.h" + export * + } +} diff --git a/test/fixtures/private_deps/BUILD b/test/fixtures/private_deps/BUILD index 1f07ad986..a8bb980ba 100644 --- a/test/fixtures/private_deps/BUILD +++ b/test/fixtures/private_deps/BUILD @@ -44,6 +44,7 @@ cc_library( name = "private_cc", srcs = ["private.c"], hdrs = ["private.h"], + aspect_hints = ["//swift:auto_module"], features = [ # A bit hacky, but by claiming we don't support PIC, we can get the # output libraries in `libraries_to_link.static_library` instead of @@ -52,19 +53,20 @@ cc_library( "-pic", "-supports_pic", ], - tags = FIXTURE_TAGS + ["swift_module"], + tags = FIXTURE_TAGS, ) cc_library( name = "public_cc", srcs = ["public.c"], hdrs = ["public.h"], + aspect_hints = ["//swift:auto_module"], features = [ # See the comment in the target above. "-pic", "-supports_pic", ], - tags = FIXTURE_TAGS + ["swift_module"], + tags = FIXTURE_TAGS, ) swift_library( diff --git a/test/interop_hints_tests.bzl b/test/interop_hints_tests.bzl new file mode 100644 index 000000000..cf8f31dc4 --- /dev/null +++ b/test/interop_hints_tests.bzl @@ -0,0 +1,92 @@ +# Copyright 2021 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. + +"""Tests for `swift_interop_hint`.""" + +load( + "@build_bazel_rules_swift//test/rules:analysis_failure_test.bzl", + "analysis_failure_test", +) +load( + "@build_bazel_rules_swift//test/rules:provider_test.bzl", + "provider_test", +) + +def interop_hints_test_suite(name = "interop_hints"): + """Test suite for `swift_interop_hint`. + + Args: + name: The name prefix for all the nested tests + """ + + # Verify that a hint with only a custom module name causes the `cc_library` + # to propagate a `SwiftInfo` info with the expected auto-generated module + # map. + provider_test( + name = "{}_hint_with_custom_module_name_builds".format(name), + expected_files = [ + "test/fixtures/interop_hints/cc_lib_custom_module_name.swift.modulemap", + ], + field = "transitive_modules.clang.module_map!", + provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:import_module_name_swift", + ) + + # Verify that a hint with a custom module map file causes the `cc_library` + # to propagate a `SwiftInfo` info with that file. + provider_test( + name = "{}_hint_with_custom_module_map_builds".format(name), + expected_files = [ + "test/fixtures/interop_hints/module.modulemap", + ], + field = "transitive_modules.clang.module_map!", + provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:import_submodule_swift", + ) + + # Verify that the build fails if a hint provides `module_map` without + # `module_name`. + analysis_failure_test( + name = "{}_fails_when_module_map_provided_without_module_name".format(name), + expected_message = "'module_name' must be specified when 'module_map' is specified.", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:invalid_swift", + ) + + # Verify that an `objc_library` hinted to suppress its module does not + # propagate a `SwiftInfo` provider at all. + provider_test( + name = "{}_objc_library_module_suppressed".format(name), + does_not_propagate_provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:objc_library_suppressed", + target_compatible_with = ["@platforms//os:macos"], + ) + + # Verify that an `objc_library` hinted to suppress its module does not + # propagate a `SwiftInfo` provider even if it has Swift dependencies. + provider_test( + name = "{}_objc_library_module_with_swift_dep_suppressed".format(name), + does_not_propagate_provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:objc_library_with_swift_dep_suppressed", + target_compatible_with = ["@platforms//os:macos"], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/rules/provider_test.bzl b/test/rules/provider_test.bzl index c34b54546..5ce1d0d72 100644 --- a/test/rules/provider_test.bzl +++ b/test/rules/provider_test.bzl @@ -165,11 +165,6 @@ def _lookup_provider_by_name(env, target, provider_name): if provider in target: return target[provider] - - unittest.fail( - env, - "Target '{}' did not provide '{}'.".format(target.label, provider_name), - ) return None def _field_access_description(target, provider, field): @@ -330,12 +325,41 @@ def _provider_test_impl(ctx): if types.is_list(target_under_test): target_under_test = target_under_test[0] + provider_name = ctx.attr.does_not_propagate_provider + if provider_name: + provider = _lookup_provider_by_name( + env, + target_under_test, + provider_name, + ) + if provider: + unittest.fail( + env, + "Expected {} to not propagate '{}', but it did: {}".format( + target_under_test.label, + provider_name, + provider, + ), + ) + return analysistest.end(env) + provider_name = ctx.attr.provider + field = ctx.attr.field + if not provider_name or not field: + fail("Either 'does_not_propagate_provider' must be specified, or " + + "both 'provider' and 'field' must be specified.") + provider = _lookup_provider_by_name(env, target_under_test, provider_name) if not provider: + unittest.fail( + env, + "Target '{}' did not provide '{}'.".format( + target_under_test.label, + provider_name, + ), + ) return analysistest.end(env) - field = ctx.attr.field actual = _evaluate_field(env, provider, field) if actual == _EVALUATE_FIELD_FAILED: return analysistest.end(env) @@ -371,6 +395,20 @@ def make_provider_test_rule(config_settings = {}): return analysistest.make( _provider_test_impl, attrs = { + "does_not_propagate_provider": attr.string( + mandatory = False, + doc = """\ +The name of a provider that is expected to not be propagated by the target under +test. + +Currently, only the following providers are recognized: + +* `CcInfo` +* `DefaultInfo` +* `SwiftInfo` +* `apple_common.Objc` +""", + ), "expected_files": attr.string_list( mandatory = False, doc = """\ @@ -392,7 +430,7 @@ configuration details, such as output directories for generated files. """, ), "field": attr.string( - mandatory = True, + mandatory = False, doc = """\ The field name or dotted field path of the provider that should be tested. @@ -406,7 +444,7 @@ the next component. """, ), "provider": attr.string( - mandatory = True, + mandatory = False, doc = """\ The name of the provider expected to be propagated by the target under test, and on which the field will be checked. diff --git a/third_party/com_github_apple_swift_atomics/BUILD.overlay b/third_party/com_github_apple_swift_atomics/BUILD.overlay index 8a4c7f946..daf04e60d 100644 --- a/third_party/com_github_apple_swift_atomics/BUILD.overlay +++ b/third_party/com_github_apple_swift_atomics/BUILD.overlay @@ -1,5 +1,6 @@ load( "@build_bazel_rules_swift//swift:swift.bzl", + "swift_interop_hint", "swift_library", ) @@ -24,8 +25,13 @@ cc_library( hdrs = glob([ "Sources/_AtomicsShims/**/*.h", ]), + aspect_hints = [":shims_interop"], copts = [], includes = ["Sources/_AtomicsShims/include"], - tags = ["swift_module=_AtomicsShims"], visibility = ["//visibility:private"], ) + +swift_interop_hint( + name = "shims_interop", + module_name = "_AtomicsShims", +) diff --git a/third_party/com_github_apple_swift_nio/BUILD.overlay b/third_party/com_github_apple_swift_nio/BUILD.overlay index d45fbe275..ebd14f7a6 100644 --- a/third_party/com_github_apple_swift_nio/BUILD.overlay +++ b/third_party/com_github_apple_swift_nio/BUILD.overlay @@ -1,5 +1,6 @@ load( "@build_bazel_rules_swift//swift:swift.bzl", + "swift_interop_hint", "swift_library", ) @@ -11,9 +12,14 @@ cc_library( hdrs = glob([ "Sources/CNIOAtomics/**/*.h", ]), + aspect_hints = [":CNIOAtomics_interop"], copts = [], includes = ["Sources/CNIOAtomics/include"], - tags = ["swift_module=CNIOAtomics"], +) + +swift_interop_hint( + name = "CNIOAtomics_interop", + module_name = "CNIOAtomics", ) cc_library( @@ -24,11 +30,16 @@ cc_library( hdrs = glob([ "Sources/CNIODarwin/**/*.h", ]), + aspect_hints = [":CNIODarwin_interop"], defines = [ "__APPLE_USE_RFC_3542", ], includes = ["Sources/CNIODarwin/include"], - tags = ["swift_module=CNIODarwin"], +) + +swift_interop_hint( + name = "CNIODarwin_interop", + module_name = "CNIODarwin", ) cc_library( @@ -39,12 +50,17 @@ cc_library( hdrs = glob([ "Sources/CNIOLLHTTP/**/*.h", ]), + aspect_hints = [":CNIOLLHTTP_interop"], copts = [], defines = [ "LLHTTP_STRICT_MODE", ], includes = ["Sources/CNIOLLHTTP/include"], - tags = ["swift_module=CNIOLLHTTP"], +) + +swift_interop_hint( + name = "CNIOLLHTTP_interop", + module_name = "CNIOLLHTTP", ) cc_library( @@ -55,9 +71,14 @@ cc_library( hdrs = glob([ "Sources/CNIOLinux/**/*.h", ]), + aspect_hints = [":CNIOLinux_interop"], copts = [], includes = ["Sources/CNIOLinux/include"], - tags = ["swift_module=CNIOLinux"], +) + +swift_interop_hint( + name = "CNIOLinux_interop", + module_name = "CNIOLinux", ) cc_library( @@ -68,9 +89,14 @@ cc_library( hdrs = [ "Sources/CNIOSHA1/**/*.h", ], + aspect_hints = [":CNIOSHA1_interop"], copts = [], includes = ["Sources/CNIOSHA1/include"], - tags = ["swift_module=CNIOSHA1"], +) + +swift_interop_hint( + name = "CNIOSHA1_interop", + module_name = "CNIOSHA1", ) cc_library( @@ -81,9 +107,14 @@ cc_library( hdrs = glob([ "Sources/CNIOWindows/**/*.h", ]), + aspect_hints = [":CNIOWindows_interop"], copts = [], includes = ["Sources/CNIOWindows/include"], - tags = ["swift_module=CNIOWindows"], +) + +swift_interop_hint( + name = "CNIOWindows_interop", + module_name = "CNIOWindows", ) swift_library( diff --git a/third_party/com_github_apple_swift_nio_ssl/BUILD.overlay b/third_party/com_github_apple_swift_nio_ssl/BUILD.overlay index e29e8ca9a..2ac93ab88 100644 --- a/third_party/com_github_apple_swift_nio_ssl/BUILD.overlay +++ b/third_party/com_github_apple_swift_nio_ssl/BUILD.overlay @@ -1,5 +1,6 @@ load( "@build_bazel_rules_swift//swift:swift.bzl", + "swift_interop_hint", "swift_library", ) @@ -28,13 +29,18 @@ cc_library( hdrs = glob([ "Sources/CNIOBoringSSLShims/include/**/*.h", ]), + aspect_hints = [":CNIOBoringSSLShims_interop"], copts = [], includes = ["Sources/CNIOBoringSSLShims/include"], - tags = ["swift_module=CNIOBoringSSLShims"], visibility = ["//visibility:public"], deps = [":CNIOBoringSSL"], ) +swift_interop_hint( + name = "CNIOBoringSSLShims_interop", + module_name = "CNIOBoringSSLShims", +) + cc_library( name = "CNIOBoringSSL", srcs = glob([ @@ -47,9 +53,14 @@ cc_library( "Sources/CNIOBoringSSL/include/**/*.h", "Sources/CNIOBoringSSL/include/**/*.inc", ]), + aspect_hints = [":CNIOBoringSSL_interop"], copts = [], includes = ["Sources/CNIOBoringSSL/include"], - tags = ["swift_module=CNIOBoringSSL"], visibility = ["//visibility:public"], deps = [], ) + +swift_interop_hint( + name = "CNIOBoringSSL_interop", + module_name = "CNIOBoringSSL", +) diff --git a/third_party/com_github_grpc_grpc_swift/BUILD.overlay b/third_party/com_github_grpc_grpc_swift/BUILD.overlay index f59a5cc1a..38228b04f 100644 --- a/third_party/com_github_grpc_grpc_swift/BUILD.overlay +++ b/third_party/com_github_grpc_grpc_swift/BUILD.overlay @@ -1,6 +1,7 @@ load( "@build_bazel_rules_swift//swift:swift.bzl", "swift_binary", + "swift_interop_hint", "swift_library", ) @@ -12,9 +13,14 @@ cc_library( hdrs = glob([ "Sources/CGRPCZlib/**/*.h", ]), + aspect_hints = [":CGRPCZLIB_interop"], includes = ["Sources/CGRPCZlib/include"], linkopts = ["-lz"], - tags = ["swift_module=CGRPCZlib"], +) + +swift_interop_hint( + name = "CGRPCZLIB_interop", + module_name = "CGRPCZlib", ) swift_library(