diff --git a/doc/BUILD.bazel b/doc/BUILD.bazel index edbad25a0..72eaced5e 100644 --- a/doc/BUILD.bazel +++ b/doc/BUILD.bazel @@ -18,6 +18,7 @@ _DOC_SRCS = { "rules": [ "swift_binary", "swift_c_module", + "swift_feature_allowlist", "swift_grpc_library", "swift_import", "swift_library", diff --git a/doc/providers.md b/doc/providers.md index 8965560ac..fa06673b6 100644 --- a/doc/providers.md +++ b/doc/providers.md @@ -61,7 +61,7 @@ Propagates Swift-specific information about a `proto_library`. ## SwiftToolchainInfo
-SwiftToolchainInfo(action_configs, all_files, cc_toolchain_info, cpu,
+SwiftToolchainInfo(action_configs, all_files, cc_toolchain_info, cpu, feature_allowlists,
                    generated_header_module_implicit_deps_providers, implicit_deps_providers,
                    linker_opts_producer, linker_supports_filelist, object_format, requested_features,
                    root_dir, supports_objc_interop, swift_worker, system_name, test_configuration,
@@ -82,6 +82,7 @@ that use the toolchain.
 | all_files |  A depset of Files containing all the Swift toolchain files (tools, libraries, and other resource files) so they can be passed as tools to actions using this toolchain.    |
 | cc_toolchain_info |  The cc_common.CcToolchainInfo provider from the Bazel C++ toolchain that this 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 should be treated as compile-time inputs to actions that precompile the explicit module for the generated Objective-C header of a Swift module:

* cc_infos: A list of CcInfo providers from targets specified as the toolchain's implicit dependencies. * objc_infos: A list of apple_common.Objc providers from targets specified as the toolchain's implicit dependencies. * swift_infos: A list of SwiftInfo providers from targets specified as the toolchain's implicit dependencies.

This is used to provide modular dependencies for the fixed inclusions (Darwin, Foundation) that are unconditionally emitted in those files.

For ease of use, this field is never None; it will always be a valid struct containing the fields described above, even if those lists are empty. | | implicit_deps_providers | A struct 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):

* cc_infos: A list of CcInfo providers from targets specified as the toolchain's implicit dependencies. * objc_infos: A list of apple_common.Objc providers from targets specified as the toolchain's implicit dependencies. * swift_infos: A list of SwiftInfo providers from targets specified as the toolchain's implicit dependencies.

For ease of use, this field is never None; it will always be a valid struct containing the fields described above, even if those lists are empty. | | linker_opts_producer | Skylib partial. 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.

The partial should be called with two arguments:

* is_static: A Boolean value indicating whether to link against the static or dynamic runtime libraries.

* is_test: A Boolean value indicating whether the target being linked is a test target. | diff --git a/doc/rules.md b/doc/rules.md index cfdd59204..7117649fa 100644 --- a/doc/rules.md +++ b/doc/rules.md @@ -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) @@ -117,6 +118,37 @@ any C++ declarations. | 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 | | + + +## swift_feature_allowlist + +
+swift_feature_allowlist(name, managed_features, packages)
+
+ +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 | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| managed_features | A list of feature strings that are permitted to be specified by the targets in the packages matched by the packages attribute. This list may include both feature names and/or negations (a name with a leading -); 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.

For example, managed_features = ["foo", "-bar"] means that targets in the allowlist's packages may request that feature "foo" be enabled and that feature "bar" be disabled. | List of strings | optional | [] | +| packages | A list of strings representing packages (possibly recursive) whose targets are allowed to enable/disable the features in managed_features. Each package pattern is written in the syntax used by the package_group function:

* //foo/bar: Targets in the package //foo/bar but not in subpackages. * //foo/bar/...: Targets in the package //foo/bar and any of its subpackages. * A leading - excludes packages that would otherwise have been included by the patterns in the list.

Exclusions always take priority over inclusions; order in the list is irrelevant. | List of strings | required | | + + ## swift_grpc_library diff --git a/swift/BUILD b/swift/BUILD index d48b57c47..03429e5fe 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -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", diff --git a/swift/internal/BUILD b/swift/internal/BUILD index 5a83d02a1..f63b536e6 100644 --- a/swift/internal/BUILD +++ b/swift/internal/BUILD @@ -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"], diff --git a/swift/internal/features.bzl b/swift/internal/features.bzl index 51b20c287..2cd6cfc4b 100644 --- a/swift/internal/features.bzl +++ b/swift/internal/features.bzl @@ -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* @@ -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 diff --git a/swift/internal/providers.bzl b/swift/internal/providers.bzl index 5af061c1a..5131b102e 100644 --- a/swift/internal/providers.bzl +++ b/swift/internal/providers.bzl @@ -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. @@ -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 diff --git a/swift/internal/swift_feature_allowlist.bzl b/swift/internal/swift_feature_allowlist.bzl new file mode 100644 index 000000000..fc31a778f --- /dev/null +++ b/swift/internal/swift_feature_allowlist.bzl @@ -0,0 +1,109 @@ +# 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. + +"""Support for restricting access to features based on an allowlist.""" + +load(":providers.bzl", "SwiftAllowlistPackageInfo", "SwiftFeatureAllowlistInfo") + +def _parse_allowlist_package(package_spec): + """Parses an allowlist package specification from a string. + + Args: + package_spec: A string that represents a possibly recursive package + specification, with an optional exclusion marker in front. + + Returns: + An instance of `SwiftAllowlistPackageInfo` containing the parsed + information from the package specification. + """ + if package_spec.startswith("-"): + excluded = True + package_spec = package_spec[1:] + else: + excluded = False + + if package_spec.endswith("/..."): + match_subpackages = True + package_spec = package_spec[:-4] + else: + match_subpackages = False + + return SwiftAllowlistPackageInfo( + excluded = excluded, + match_subpackages = match_subpackages, + package = package_spec, + ) + +def _swift_feature_allowlist_impl(ctx): + return [SwiftFeatureAllowlistInfo( + allowlist_label = str(ctx.label), + managed_features = ctx.attr.managed_features, + packages = [ + _parse_allowlist_package(package_spec) + for package_spec in ctx.attr.packages + ], + )] + +swift_feature_allowlist = rule( + attrs = { + "managed_features": attr.string_list( + allow_empty = True, + doc = """\ +A list of feature strings that are permitted to be specified by the targets in +the packages matched by the `packages` attribute. This list may include both +feature names and/or negations (a name with a leading `-`); 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. + +For example, `managed_features = ["foo", "-bar"]` means that targets in the +allowlist's packages may request that feature `"foo"` be enabled and that +feature `"bar"` be disabled. +""", + mandatory = False, + ), + "packages": attr.string_list( + allow_empty = True, + doc = """\ +A list of strings representing packages (possibly recursive) whose targets are +allowed to enable/disable the features in `managed_features`. Each package +pattern is written in the syntax used by the `package_group` function: + +* `//foo/bar`: Targets in the package `//foo/bar` but not in subpackages. +* `//foo/bar/...`: Targets in the package `//foo/bar` and any of its + subpackages. +* A leading `-` excludes packages that would otherwise have been included by + the patterns in the list. + +Exclusions always take priority over inclusions; order in the list is +irrelevant. +""", + mandatory = True, + ), + }, + doc = """\ +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. +""", + implementation = _swift_feature_allowlist_impl, +) diff --git a/swift/internal/swift_toolchain.bzl b/swift/internal/swift_toolchain.bzl index 29b68e2a1..05ec28e24 100644 --- a/swift/internal/swift_toolchain.bzl +++ b/swift/internal/swift_toolchain.bzl @@ -34,7 +34,7 @@ load( "SWIFT_FEATURE_USE_RESPONSE_FILES", ) load(":features.bzl", "features_for_build_modes") -load(":providers.bzl", "SwiftToolchainInfo") +load(":providers.bzl", "SwiftFeatureAllowlistInfo", "SwiftToolchainInfo") load(":toolchain_config.bzl", "swift_toolchain_config") load( ":utils.bzl", @@ -216,6 +216,10 @@ def _swift_toolchain_impl(ctx): all_files = depset(all_files), cc_toolchain_info = cc_toolchain, cpu = ctx.attr.arch, + feature_allowlists = [ + target[SwiftFeatureAllowlistInfo] + for target in ctx.attr.feature_allowlists + ], generated_header_module_implicit_deps_providers = ( collect_implicit_deps_providers([]) ), @@ -254,6 +258,13 @@ architecture-specific content, such as "x86_64" in "lib/swift/linux/x86_64". """, mandatory = True, ), + "feature_allowlists": attr.label_list( + doc = """\ +A list of `swift_feature_allowlist` targets that allow or prohibit packages from +requesting or disabling features. +""", + providers = [[SwiftFeatureAllowlistInfo]], + ), "os": attr.string( doc = """\ The name of the operating system that this toolchain targets. diff --git a/swift/internal/xcode_swift_toolchain.bzl b/swift/internal/xcode_swift_toolchain.bzl index a2cb1e8a0..bb65cf3de 100644 --- a/swift/internal/xcode_swift_toolchain.bzl +++ b/swift/internal/xcode_swift_toolchain.bzl @@ -45,7 +45,12 @@ load( ) load(":features.bzl", "features_for_build_modes") load(":toolchain_config.bzl", "swift_toolchain_config") -load(":providers.bzl", "SwiftInfo", "SwiftToolchainInfo") +load( + ":providers.bzl", + "SwiftFeatureAllowlistInfo", + "SwiftInfo", + "SwiftToolchainInfo", +) load( ":utils.bzl", "collect_implicit_deps_providers", @@ -772,6 +777,10 @@ def _xcode_swift_toolchain_impl(ctx): all_files = depset(all_files), cc_toolchain_info = cc_toolchain, cpu = cpu, + feature_allowlists = [ + target[SwiftFeatureAllowlistInfo] + for target in ctx.attr.feature_allowlists + ], generated_header_module_implicit_deps_providers = ( collect_implicit_deps_providers( ctx.attr.generated_header_module_implicit_deps, @@ -802,6 +811,13 @@ xcode_swift_toolchain = rule( attrs = dicts.add( swift_toolchain_driver_attrs(), { + "feature_allowlists": attr.label_list( + doc = """\ +A list of `swift_feature_allowlist` targets that allow or prohibit packages from +requesting or disabling features. +""", + providers = [[SwiftFeatureAllowlistInfo]], + ), "generated_header_module_implicit_deps": attr.label_list( doc = """\ Targets whose `SwiftInfo` providers should be treated as compile-time inputs to diff --git a/swift/swift.bzl b/swift/swift.bzl index 835051d92..e8e2e4397 100644 --- a/swift/swift.bzl +++ b/swift/swift.bzl @@ -51,6 +51,10 @@ load( "@build_bazel_rules_swift//swift/internal:swift_common.bzl", _swift_common = "swift_common", ) +load( + "@build_bazel_rules_swift//swift/internal:swift_feature_allowlist.bzl", + _swift_feature_allowlist = "swift_feature_allowlist", +) load( "@build_bazel_rules_swift//swift/internal:swift_grpc_library.bzl", _swift_grpc_library = "swift_grpc_library", @@ -88,12 +92,13 @@ swift_common = _swift_common # Re-export rules. 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 -swift_test = _swift_test swift_module_alias = _swift_module_alias swift_proto_library = _swift_proto_library +swift_test = _swift_test # Re-export public aspects. swift_clang_module_aspect = _swift_clang_module_aspect