diff --git a/doc/rules.md b/doc/rules.md
index 4e85ccadd..baab8cb0d 100644
--- a/doc/rules.md
+++ b/doc/rules.md
@@ -212,7 +212,7 @@ the future, this rule is not recommended for other widespread use.
## swift_feature_allowlist
-swift_feature_allowlist(name, managed_features, packages)
+swift_feature_allowlist(name, aspect_ids, managed_features, packages)
Limits the ability to request or disable certain features to a set of packages
@@ -233,7 +233,8 @@ package.
| 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 | `[]` |
+| aspect_ids | A list of strings representing the identifiers of aspects that are allowed to enable/disable the features in `managed_features`, even when the aspect is applied to packages not covered by the `packages` attribute.
Aspect identifiers are each expected to be of the form `<.bzl file label>%` (i.e., the form one would use if invoking it from the command line, as described at https://bazel.build/extending/aspects#invoking_the_aspect_using_the_command_line). | List of strings | optional | `[]` |
+| managed_features | A list of feature strings that are permitted to be specified by the targets in the packages matched by the `packages` attribute *or* an aspect whose name matches the `aspect_ids` attribute (in any package). This list may include both feature names and/or negations (a name with a leading `-`); a regular feature name means that the matching targets/aspects 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/aspects 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 | |
diff --git a/swift/internal/features.bzl b/swift/internal/features.bzl
index 441e693e9..758dc4f4d 100644
--- a/swift/internal/features.bzl
+++ b/swift/internal/features.bzl
@@ -87,9 +87,25 @@ def configure_features(
"parse_headers",
])
+ # HACK: This is the only way today to check whether the caller is inside an
+ # aspect. We have to do this because accessing `ctx.aspect_ids` halts the
+ # build if called from outside an aspect, but we can't use `hasattr` to
+ # check if it's safe because the attribute is always present on both rule
+ # and aspect contexts.
+ # TODO: b/319132714 - Replace this with a real API.
+ is_aspect = repr(ctx).startswith("%` (i.e., the form one would use if
+invoking it from the command line, as described at
+https://bazel.build/extending/aspects#invoking_the_aspect_using_the_command_line).
+""",
+ ),
"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
+the packages matched by the `packages` attribute *or* an aspect whose name
+matches the `aspect_ids` attribute (in any package). 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.
+name means that the matching targets/aspects 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.
+allowlist's packages/aspects may request that feature `"foo"` be enabled and
+that feature `"bar"` be disabled.
""",
mandatory = False,
),