Skip to content

Commit

Permalink
feat: add always_include_developer_search_paths attribute to `swift…
Browse files Browse the repository at this point in the history
…_library` (#1162)
  • Loading branch information
cgrindel committed Feb 3, 2024
1 parent c5c8efe commit f8a0428
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 63 deletions.
18 changes: 12 additions & 6 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ A C++ `FeatureConfiguration` value (see
## swift_common.compilation_attrs

<pre>
swift_common.compilation_attrs(<a href="#swift_common.compilation_attrs-additional_deps_aspects">additional_deps_aspects</a>, <a href="#swift_common.compilation_attrs-additional_deps_providers">additional_deps_providers</a>, <a href="#swift_common.compilation_attrs-requires_srcs">requires_srcs</a>)
swift_common.compilation_attrs(<a href="#swift_common.compilation_attrs-additional_deps_aspects">additional_deps_aspects</a>, <a href="#swift_common.compilation_attrs-additional_deps_providers">additional_deps_providers</a>,
<a href="#swift_common.compilation_attrs-include_dev_srch_paths_attrib">include_dev_srch_paths_attrib</a>, <a href="#swift_common.compilation_attrs-requires_srcs">requires_srcs</a>)
</pre>

Returns an attribute dictionary for rules that compile Swift code.
Expand Down Expand Up @@ -70,6 +71,7 @@ attributes from the earlier items in the list.
| :------------- | :------------- | :------------- |
| <a id="swift_common.compilation_attrs-additional_deps_aspects"></a>additional_deps_aspects | A list of additional aspects that should be applied to `deps`. Defaults to the empty list. These must be passed by the individual rules to avoid potential circular dependencies between the API and the aspects; the API loaded the aspects directly, then those aspects would not be able to load the API. | `[]` |
| <a id="swift_common.compilation_attrs-additional_deps_providers"></a>additional_deps_providers | A list of lists representing additional providers that should be allowed by the `deps` attribute of the rule. | `[]` |
| <a id="swift_common.compilation_attrs-include_dev_srch_paths_attrib"></a>include_dev_srch_paths_attrib | A `bool` that indicates whether to include the `always_include_developer_search_paths` attribute. | `False` |
| <a id="swift_common.compilation_attrs-requires_srcs"></a>requires_srcs | Indicates whether the `srcs` attribute should be marked as mandatory and non-empty. Defaults to `True`. | `True` |

**RETURNS**
Expand All @@ -85,8 +87,9 @@ A new attribute dictionary that can be added to the attributes of a

<pre>
swift_common.compile(<a href="#swift_common.compile-actions">actions</a>, <a href="#swift_common.compile-additional_inputs">additional_inputs</a>, <a href="#swift_common.compile-copts">copts</a>, <a href="#swift_common.compile-defines">defines</a>, <a href="#swift_common.compile-deps">deps</a>, <a href="#swift_common.compile-extra_swift_infos">extra_swift_infos</a>,
<a href="#swift_common.compile-feature_configuration">feature_configuration</a>, <a href="#swift_common.compile-generated_header_name">generated_header_name</a>, <a href="#swift_common.compile-is_test">is_test</a>, <a href="#swift_common.compile-module_name">module_name</a>, <a href="#swift_common.compile-package_name">package_name</a>,
<a href="#swift_common.compile-plugins">plugins</a>, <a href="#swift_common.compile-private_deps">private_deps</a>, <a href="#swift_common.compile-srcs">srcs</a>, <a href="#swift_common.compile-swift_toolchain">swift_toolchain</a>, <a href="#swift_common.compile-target_name">target_name</a>, <a href="#swift_common.compile-workspace_name">workspace_name</a>)
<a href="#swift_common.compile-feature_configuration">feature_configuration</a>, <a href="#swift_common.compile-generated_header_name">generated_header_name</a>, <a href="#swift_common.compile-is_test">is_test</a>, <a href="#swift_common.compile-include_dev_srch_paths">include_dev_srch_paths</a>,
<a href="#swift_common.compile-module_name">module_name</a>, <a href="#swift_common.compile-package_name">package_name</a>, <a href="#swift_common.compile-plugins">plugins</a>, <a href="#swift_common.compile-private_deps">private_deps</a>, <a href="#swift_common.compile-srcs">srcs</a>, <a href="#swift_common.compile-swift_toolchain">swift_toolchain</a>,
<a href="#swift_common.compile-target_name">target_name</a>, <a href="#swift_common.compile-workspace_name">workspace_name</a>)
</pre>

Compiles a Swift module.
Expand All @@ -104,7 +107,8 @@ Compiles a Swift module.
| <a id="swift_common.compile-extra_swift_infos"></a>extra_swift_infos | Extra `SwiftInfo` providers that aren't contained by the `deps` of the target being compiled but are required for compilation. | `[]` |
| <a id="swift_common.compile-feature_configuration"></a>feature_configuration | A feature configuration obtained from `swift_common.configure_features`. | none |
| <a id="swift_common.compile-generated_header_name"></a>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` |
| <a id="swift_common.compile-is_test"></a>is_test | Represents if the `testonly` value of the context. | none |
| <a id="swift_common.compile-is_test"></a>is_test | Deprecated. This argument will be removed in the next major release. Use the `include_dev_srch_paths` attribute instead. Represents if the `testonly` value of the context. | `None` |
| <a id="swift_common.compile-include_dev_srch_paths"></a>include_dev_srch_paths | A `bool` that indicates whether the developer framework search paths will be added to the compilation command. | `None` |
| <a id="swift_common.compile-module_name"></a>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 |
| <a id="swift_common.compile-package_name"></a>package_name | The semantic package of the name of the Swift module being compiled. | none |
| <a id="swift_common.compile-plugins"></a>plugins | A list of `SwiftCompilerPluginInfo` providers that represent plugins that should be loaded by the compiler. | `[]` |
Expand Down Expand Up @@ -291,7 +295,8 @@ A `struct` containing four fields:
<pre>
swift_common.create_linking_context_from_compilation_outputs(<a href="#swift_common.create_linking_context_from_compilation_outputs-actions">actions</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-additional_inputs">additional_inputs</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-alwayslink">alwayslink</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-compilation_outputs">compilation_outputs</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-feature_configuration">feature_configuration</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-is_test">is_test</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-label">label</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-feature_configuration">feature_configuration</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-is_test">is_test</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-include_dev_srch_paths">include_dev_srch_paths</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-label">label</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-linking_contexts">linking_contexts</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-module_context">module_context</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-name">name</a>,
<a href="#swift_common.create_linking_context_from_compilation_outputs-swift_toolchain">swift_toolchain</a>, <a href="#swift_common.create_linking_context_from_compilation_outputs-user_link_flags">user_link_flags</a>)
</pre>
Expand All @@ -316,7 +321,8 @@ command line parameters file, those actions will be created here.
| <a id="swift_common.create_linking_context_from_compilation_outputs-alwayslink"></a>alwayslink | If True, any binary that depends on the providers returned by this function will link in all of the library's object files, even if some contain no symbols referenced by the binary. | `False` |
| <a id="swift_common.create_linking_context_from_compilation_outputs-compilation_outputs"></a>compilation_outputs | A `CcCompilationOutputs` value containing the object files to link. Typically, this is the second tuple element in the value returned by `swift_common.compile`. | none |
| <a id="swift_common.create_linking_context_from_compilation_outputs-feature_configuration"></a>feature_configuration | A feature configuration obtained from `swift_common.configure_features`. | none |
| <a id="swift_common.create_linking_context_from_compilation_outputs-is_test"></a>is_test | Represents if the `testonly` value of the context. | none |
| <a id="swift_common.create_linking_context_from_compilation_outputs-is_test"></a>is_test | Deprecated. This argument will be removed in the next major release. Use the `include_dev_srch_paths` attribute instead. Represents if the `testonly` value of the context. | `None` |
| <a id="swift_common.create_linking_context_from_compilation_outputs-include_dev_srch_paths"></a>include_dev_srch_paths | A `bool` that indicates whether the developer framework search paths will be added to the compilation command. | `None` |
| <a id="swift_common.create_linking_context_from_compilation_outputs-label"></a>label | The `Label` of the target being built. This is used as the owner of the linker inputs created for post-compile actions (if any), and the label's name component also determines the name of the artifact unless it is overridden by the `name` argument. | none |
| <a id="swift_common.create_linking_context_from_compilation_outputs-linking_contexts"></a>linking_contexts | A `list` of `CcLinkingContext`s containing libraries from dependencies. | `[]` |
| <a id="swift_common.create_linking_context_from_compilation_outputs-module_context"></a>module_context | The module context returned by `swift_common.compile` containing information about the Swift module that was compiled. Typically, this is the first tuple element in the value returned by `swift_common.compile`. | none |
Expand Down
7 changes: 4 additions & 3 deletions doc/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,9 @@ Allows for the use of Swift textual module interfaces and/or precompiled Swift m
## swift_library

<pre>
swift_library(<a href="#swift_library-name">name</a>, <a href="#swift_library-deps">deps</a>, <a href="#swift_library-srcs">srcs</a>, <a href="#swift_library-data">data</a>, <a href="#swift_library-alwayslink">alwayslink</a>, <a href="#swift_library-copts">copts</a>, <a href="#swift_library-defines">defines</a>, <a href="#swift_library-generated_header_name">generated_header_name</a>,
<a href="#swift_library-generates_header">generates_header</a>, <a href="#swift_library-linkopts">linkopts</a>, <a href="#swift_library-linkstatic">linkstatic</a>, <a href="#swift_library-module_name">module_name</a>, <a href="#swift_library-package_name">package_name</a>, <a href="#swift_library-plugins">plugins</a>,
<a href="#swift_library-private_deps">private_deps</a>, <a href="#swift_library-swiftc_inputs">swiftc_inputs</a>)
swift_library(<a href="#swift_library-name">name</a>, <a href="#swift_library-deps">deps</a>, <a href="#swift_library-srcs">srcs</a>, <a href="#swift_library-data">data</a>, <a href="#swift_library-always_include_developer_search_paths">always_include_developer_search_paths</a>, <a href="#swift_library-alwayslink">alwayslink</a>, <a href="#swift_library-copts">copts</a>,
<a href="#swift_library-defines">defines</a>, <a href="#swift_library-generated_header_name">generated_header_name</a>, <a href="#swift_library-generates_header">generates_header</a>, <a href="#swift_library-linkopts">linkopts</a>, <a href="#swift_library-linkstatic">linkstatic</a>, <a href="#swift_library-module_name">module_name</a>,
<a href="#swift_library-package_name">package_name</a>, <a href="#swift_library-plugins">plugins</a>, <a href="#swift_library-private_deps">private_deps</a>, <a href="#swift_library-swiftc_inputs">swiftc_inputs</a>)
</pre>

Compiles and links Swift code into a static library and Swift module.
Expand All @@ -371,6 +371,7 @@ Compiles and links Swift code into a static library and Swift module.
| <a id="swift_library-deps"></a>deps | A list of targets that are dependencies of the target being built, which will be linked into that target.<br><br>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.<br><br>Allowed kinds of dependencies are:<br><br>* `swift_c_module`, `swift_import` and `swift_library` (or anything propagating `SwiftInfo`)<br><br>* `cc_library` (or anything propagating `CcInfo`)<br><br>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.** | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="swift_library-srcs"></a>srcs | A list of `.swift` source files that will be compiled into the library. | <a href="https://bazel.build/concepts/labels">List of labels</a> | required | |
| <a id="swift_library-data"></a>data | The list of files needed by this target at runtime.<br><br>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. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="swift_library-always_include_developer_search_paths"></a>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` |
| <a id="swift_library-alwayslink"></a>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` |
| <a id="swift_library-copts"></a>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 | `[]` |
| <a id="swift_library-defines"></a>defines | A list of defines to add to the compilation command line.<br><br>Note that unlike C-family languages, Swift defines do not have values; they are simply identifiers that are either defined or undefined. So strings in this list should be simple identifiers, **not** `name=value` pairs.<br><br>Each string is prepended with `-D` and added to the command line. Unlike `copts`, these flags are added for the target and every target that depends on it, so use this attribute with caution. It is preferred that you add defines directly to `copts`, only using this feature in the rare case that a library needs to propagate a symbol up to those that depend on it. | List of strings | optional | `[]` |
Expand Down
36 changes: 36 additions & 0 deletions examples/xplatform/include_dev_srch_paths/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("//swift:swift.bzl", "swift_library", "swift_library_group", "swift_test")

swift_library(
name = "TestHelpers",
srcs = ["TestHelper.swift"],
always_include_developer_search_paths = True,
module_name = "TestHelpers",
tags = ["manual"],
)

swift_library(
name = "StringHelpers",
srcs = ["String+RandomExtensions.swift"],
module_name = "StringHelpers",
tags = ["manual"],
visibility = ["//:__subpackages__"],
)

swift_library_group(
name = "Helpers",
tags = ["manual"],
deps = [
":StringHelpers",
":TestHelpers",
],
)

swift_test(
name = "DemoHelpersTest",
srcs = [
"DemoTestHelperTest.swift",
"String+RandomExtensionsTests.swift",
"main.swift",
],
deps = [":Helpers"],
)
13 changes: 13 additions & 0 deletions examples/xplatform/include_dev_srch_paths/DemoTestHelperTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@testable import TestHelpers
import XCTest

class DemoTestHelperTest: XCTestCase {
func test_assertThat_isEqualTo() {
// To demonstrate a failure, change the expected value to "goodbye".
assertThat("hello").isEqualTo("hello")
}

static var allTests = [
("test_assertThat_isEqualTo", test_assertThat_isEqualTo),
]
}
46 changes: 46 additions & 0 deletions examples/xplatform/include_dev_srch_paths/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Demonstrate `swift_library_group` and `always_include_developer_search_paths`

This example demonstrates the use of the `swift_library_group` rule and the
`always_include_developer_search_paths` attribute for `swift_library`.

## Scenario

The scenario is a developer provides a suite of utility modules that they would like to provide as a
single Bazel dependency. One module, `TestHelpers`, provides a custom assertion syntax for `XCTest`
tests. The other module, `StringHelpers`, provides functions for generating string values. They
should be made available to clients using a single target, `Helpers`.

## Combine Swift modules using `swift_library`

To provide multiple Swift modules as a single target (i.e., this is anlagous to Swift Package
Manager products), one can combine the Swift module Bazel targets using the `swift_library_group`
rule. The Swift modules provided as `deps` to the `swift_library_group` are forwarded to any target
that depends on it.

## Include developer search paths using `always_include_developer_search_paths`

The `TestHelpers` module can only provide the custom assertions if `XCTest` is available. The
`XCTest` module is special in that it is only available if the developer search paths are visible
during compilation. Historically, this would require that the `swift_library` be marked
`testonly = True`. However, because the author wants to provide their utilities as a single
dependency, marking `TestHelpers` as `testonly` would mandate that the `Helpers` target be marked as
`testonly`. This would prevent non-test targets from using the `StringHelpers` module via the
`Helpers` target.

The `always_include_developer_search_paths` attribute on the `swift_library` rule was introduced to
allow the author of the library to dictate whether the developer search paths are added during
compilation. By default, the attribute is `False`. An author must explicitly set
`always_include_developer_search_paths = True` or `testonly = True` for the developer search paths
to be availble during compilation.

### When should I use `testonly` vs `always_include_developer_search_paths`?

In short, prefer marking your target as `testonly`, if you import `XCTest`. The
`always_include_developer_search_paths` attribute was added to support Swift packages
that provide test and non-test dependencies in a single Swift product or Swift target. Marking the
corresponding Bazel targets `testonly` makes them unusable in non-test scenarios.

### Should I combine test and non-test Swift modules?

Typically, no. Prefer keeping test-specific code separate from non-test code. As The Offspring told
us ["You gotta keep 'em separated"](https://youtu.be/1jOk8dk-qaU?si=G73P7X7hf6HDluVG).
Loading

0 comments on commit f8a0428

Please sign in to comment.