Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the swift_feature_allowlist rule that lets toolchains control which packages are allowed to enable/disable specific features. #627

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ _DOC_SRCS = {
"rules": [
"swift_binary",
"swift_c_module",
"swift_feature_allowlist",
"swift_grpc_library",
"swift_import",
"swift_library",
Expand Down
3 changes: 2 additions & 1 deletion doc/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Propagates Swift-specific information about a `proto_library`.
## SwiftToolchainInfo

<pre>
SwiftToolchainInfo(<a href="#SwiftToolchainInfo-action_configs">action_configs</a>, <a href="#SwiftToolchainInfo-all_files">all_files</a>, <a href="#SwiftToolchainInfo-cc_toolchain_info">cc_toolchain_info</a>, <a href="#SwiftToolchainInfo-cpu">cpu</a>,
SwiftToolchainInfo(<a href="#SwiftToolchainInfo-action_configs">action_configs</a>, <a href="#SwiftToolchainInfo-all_files">all_files</a>, <a href="#SwiftToolchainInfo-cc_toolchain_info">cc_toolchain_info</a>, <a href="#SwiftToolchainInfo-cpu">cpu</a>, <a href="#SwiftToolchainInfo-feature_allowlists">feature_allowlists</a>,
<a href="#SwiftToolchainInfo-generated_header_module_implicit_deps_providers">generated_header_module_implicit_deps_providers</a>, <a href="#SwiftToolchainInfo-implicit_deps_providers">implicit_deps_providers</a>,
<a href="#SwiftToolchainInfo-linker_opts_producer">linker_opts_producer</a>, <a href="#SwiftToolchainInfo-linker_supports_filelist">linker_supports_filelist</a>, <a href="#SwiftToolchainInfo-object_format">object_format</a>, <a href="#SwiftToolchainInfo-requested_features">requested_features</a>,
<a href="#SwiftToolchainInfo-root_dir">root_dir</a>, <a href="#SwiftToolchainInfo-supports_objc_interop">supports_objc_interop</a>, <a href="#SwiftToolchainInfo-swift_worker">swift_worker</a>, <a href="#SwiftToolchainInfo-system_name">system_name</a>, <a href="#SwiftToolchainInfo-test_configuration">test_configuration</a>,
Expand All @@ -82,6 +82,7 @@ that use the toolchain.
| <a id="SwiftToolchainInfo-all_files"></a>all_files | A <code>depset</code> of <code>File</code>s containing all the Swift toolchain files (tools, libraries, and other resource files) so they can be passed as <code>tools</code> to actions using this toolchain. |
| <a id="SwiftToolchainInfo-cc_toolchain_info"></a>cc_toolchain_info | The <code>cc_common.CcToolchainInfo</code> provider from the Bazel C++ toolchain that this Swift toolchain depends on. |
| <a id="SwiftToolchainInfo-cpu"></a>cpu | <code>String</code>. The CPU architecture that the toolchain is targeting. |
| <a id="SwiftToolchainInfo-feature_allowlists"></a>feature_allowlists | A list of <code>SwiftFeatureAllowlistInfo</code> providers that allow or prohibit packages from requesting or disabling features. |
| <a id="SwiftToolchainInfo-generated_header_module_implicit_deps_providers"></a>generated_header_module_implicit_deps_providers | A <code>struct</code> with the following fields, which are providers from targets that should be treated as compile-time inputs to actions that precompile the explicit module for the generated Objective-C header of a Swift module:<br><br>* <code>cc_infos</code>: A list of <code>CcInfo</code> providers from targets specified as the toolchain's implicit dependencies. * <code>objc_infos</code>: A list of <code>apple_common.Objc</code> providers from targets specified as the toolchain's implicit dependencies. * <code>swift_infos</code>: A list of <code>SwiftInfo</code> providers from targets specified as the toolchain's implicit dependencies.<br><br>This is used to provide modular dependencies for the fixed inclusions (Darwin, Foundation) that are unconditionally emitted in those files.<br><br>For ease of use, this field is never <code>None</code>; it will always be a valid <code>struct</code> containing the fields described above, even if those lists are empty. |
| <a id="SwiftToolchainInfo-implicit_deps_providers"></a>implicit_deps_providers | A <code>struct</code> with the following fields, which represent providers from targets that should be added as implicit dependencies of any Swift compilation or linking target (but not to precompiled explicit C/Objective-C modules):<br><br>* <code>cc_infos</code>: A list of <code>CcInfo</code> providers from targets specified as the toolchain's implicit dependencies. * <code>objc_infos</code>: A list of <code>apple_common.Objc</code> providers from targets specified as the toolchain's implicit dependencies. * <code>swift_infos</code>: A list of <code>SwiftInfo</code> providers from targets specified as the toolchain's implicit dependencies.<br><br>For ease of use, this field is never <code>None</code>; it will always be a valid <code>struct</code> containing the fields described above, even if those lists are empty. |
| <a id="SwiftToolchainInfo-linker_opts_producer"></a>linker_opts_producer | Skylib <code>partial</code>. A partial function that returns the flags that should be passed to Clang to link a binary or test target with the Swift runtime libraries.<br><br>The partial should be called with two arguments:<br><br>* <code>is_static</code>: A <code>Boolean</code> value indicating whether to link against the static or dynamic runtime libraries.<br><br>* <code>is_test</code>: A <code>Boolean</code> value indicating whether the target being linked is a test target. |
Expand Down
32 changes: 32 additions & 0 deletions doc/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ On this page:

* [swift_binary](#swift_binary)
* [swift_c_module](#swift_c_module)
* [swift_feature_allowlist](#swift_feature_allowlist)
* [swift_grpc_library](#swift_grpc_library)
* [swift_import](#swift_import)
* [swift_library](#swift_library)
Expand Down Expand Up @@ -117,6 +118,37 @@ any C++ declarations.
| <a id="swift_c_module-module_name"></a>module_name | The name of the top-level module in the module map that this target represents.<br><br>A single <code>module.modulemap</code> 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 <code>swift_c_module</code> targets that reference the same <code>module.modulemap</code> file but with different module names and headers. | String | required | |


<a id="#swift_feature_allowlist"></a>

## swift_feature_allowlist

<pre>
swift_feature_allowlist(<a href="#swift_feature_allowlist-name">name</a>, <a href="#swift_feature_allowlist-managed_features">managed_features</a>, <a href="#swift_feature_allowlist-packages">packages</a>)
</pre>

Limits the ability to request or disable certain features to a set of packages
(and possibly subpackages) in the workspace.

A Swift toolchain target can reference any number (zero or more) of
`swift_feature_allowlist` targets. The features managed by these allowlists may
overlap. For some package _P_, a feature is allowed to be used by targets in
that package if _P_ matches the `packages` patterns in *all* of the allowlists
that manage that feature.

A feature that is not managed by any allowlist is allowed to be used by any
package.


**ATTRIBUTES**


| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="swift_feature_allowlist-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
| <a id="swift_feature_allowlist-managed_features"></a>managed_features | A list of feature strings that are permitted to be specified by the targets in the packages matched by the <code>packages</code> attribute. This list may include both feature names and/or negations (a name with a leading <code>-</code>); a regular feature name means that the targets in the matching packages may explicitly request that the feature be enabled, and a negated feature means that the target may explicitly request that the feature be disabled.<br><br>For example, <code>managed_features = ["foo", "-bar"]</code> means that targets in the allowlist's packages may request that feature <code>"foo"</code> be enabled and that feature <code>"bar"</code> be disabled. | List of strings | optional | [] |
| <a id="swift_feature_allowlist-packages"></a>packages | A list of strings representing packages (possibly recursive) whose targets are allowed to enable/disable the features in <code>managed_features</code>. Each package pattern is written in the syntax used by the <code>package_group</code> function:<br><br>* <code>//foo/bar</code>: Targets in the package <code>//foo/bar</code> but not in subpackages. * <code>//foo/bar/...</code>: Targets in the package <code>//foo/bar</code> and any of its subpackages. * A leading <code>-</code> excludes packages that would otherwise have been included by the patterns in the list.<br><br>Exclusions always take priority over inclusions; order in the list is irrelevant. | List of strings | required | |


<a id="#swift_grpc_library"></a>

## swift_grpc_library
Expand Down
1 change: 1 addition & 0 deletions swift/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bzl_library(
"//swift/internal:swift_binary_test",
"//swift/internal:swift_c_module",
"//swift/internal:swift_common",
"//swift/internal:swift_feature_allowlist",
"//swift/internal:swift_grpc_library",
"//swift/internal:swift_import",
"//swift/internal:swift_library",
Expand Down
9 changes: 9 additions & 0 deletions swift/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ bzl_library(
],
)

bzl_library(
name = "swift_feature_allowlist",
srcs = ["swift_feature_allowlist.bzl"],
visibility = ["//swift:__subpackages__"],
deps = [
":providers",
],
)

bzl_library(
name = "swift_binary_test",
srcs = ["swift_binary_test.bzl"],
Expand Down
102 changes: 102 additions & 0 deletions swift/internal/features.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ def configure_features(
passed to other `swift_common` functions. Note that the structure of
this value should otherwise not be relied on or inspected directly.
"""
if swift_toolchain.feature_allowlists:
_check_allowlists(
allowlists = swift_toolchain.feature_allowlists,
label = ctx.label,
requested_features = requested_features,
unsupported_features = unsupported_features,
)

# The features to enable for a particular rule/target are the ones requested
# by the toolchain, plus the ones requested by the target itself, *minus*
Expand Down Expand Up @@ -175,3 +182,98 @@ def is_feature_enabled(feature_configuration, feature_name):
),
feature_name = feature_name,
)

def _check_allowlists(
*,
allowlists,
label,
requested_features,
unsupported_features):
"""Checks the toolchain's allowlists to verify the requested features.

If any of the features requested to be enabled or disabled is not allowed in
the target's package by one of the allowlists, the build will fail with an
error message indicating the feature and the allowlist that denied it.

Args:
allowlists: A list of `SwiftFeatureAllowlistInfo` providers that will be
checked.
label: The label of the target being checked against the allowlist.
requested_features: The list of features to be enabled. This is
typically obtained using the `ctx.features` field in a rule
implementation function.
unsupported_features: The list of features that are unsupported by the
current rule. This is typically obtained using the
`ctx.disabled_features` field in a rule implementation function.
"""
features_to_check = list(requested_features)
features_to_check.extend(
["-{}".format(feature) for feature in unsupported_features],
)

for allowlist in allowlists:
for feature_string in features_to_check:
if not _is_feature_allowed_in_package(
allowlist = allowlist,
feature = feature_string,
package = label.package,
workspace_name = label.workspace_name,
):
fail((
"Feature '{feature}' is not allowed to be set by the " +
"target '{target}'; see the allowlist at '{allowlist}' " +
"for more information."
).format(
allowlist = allowlist.allowlist_label,
feature = feature_string,
target = str(label),
))

def _is_feature_allowed_in_package(
allowlist,
feature,
package,
workspace_name = None):
"""Returns a value indicating whether a feature is allowed in a package.

Args:
allowlist: The `SwiftFeatureAllowlistInfo` provider that contains the
allowlist.
feature: The name of the feature (or its negation) being checked.
package: The package part of the label being checked for access (e.g.,
the value of `ctx.label.package`).
workspace_name: The workspace name part of the label being checked for
access (e.g., the value of `ctx.label.workspace_name`).

Returns:
True if the feature is allowed to be used in the package, or False if it
is not.
"""

# Any feature not managed by the allowlist is allowed by default.
if feature not in allowlist.managed_features:
return True

if workspace_name:
package_spec = "@{}//{}".format(workspace_name, package)
else:
package_spec = "//{}".format(package)

is_allowed = False
for package_info in allowlist.packages:
if package_info.match_subpackages:
is_match = (
package_spec == package_info.package or
package_spec.startswith(package_info.package + "/")
)
else:
is_match = package_spec == package_info.package

# Package exclusions always take precedence over package inclusions, so
# if we have an exclusion match, return false immediately.
if package_info.excluded and is_match:
return False
else:
is_allowed = True

return is_allowed
45 changes: 45 additions & 0 deletions swift/internal/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@

"""Defines Starlark providers that propagated by the Swift BUILD rules."""

SwiftAllowlistPackageInfo = provider(
doc = "Describes a package match in an allowlist.",
fields = {
"excluded": """\
A Boolean value indicating whether the packages described by this value are
exclusions rather than inclusions.
""",
"match_subpackages": """\
A Boolean value indicating whether subpackages of `package` should also be
matched.
""",
"package": """\
A string indicating the name of the package to match, in the form
`//path/to/package`, or `@repository//path/to/package` if an explicit repository
name was given.
""",
},
)

SwiftFeatureAllowlistInfo = provider(
doc = """\
Describes a set of features and the packages that are allowed to request or
disable them.
""",
fields = {
"allowlist_label": """\
A string containing the label of the `swift_feature_allowlist` target that
created this provider.
""",
"managed_features": """\
A list of strings representing feature names or their negations that packages in
the `packages` list are allowed to explicitly request or disable.
""",
"packages": """\
A list of `SwiftAllowlistPackageInfo` values describing packages (possibly
recursive) whose targets are allowed to request or disable a feature managed by
this allowlist.
""",
},
)

SwiftInfo = provider(
doc = """\
Contains information about the compiled artifacts of a Swift module.
Expand Down Expand Up @@ -72,6 +113,10 @@ Swift toolchain depends on.
""",
"cpu": """\
`String`. The CPU architecture that the toolchain is targeting.
""",
"feature_allowlists": """\
A list of `SwiftFeatureAllowlistInfo` providers that allow or prohibit packages
from requesting or disabling features.
""",
"generated_header_module_implicit_deps_providers": """\
A `struct` with the following fields, which are providers from targets that
Expand Down
Loading