Enable having aspect parameters of type `boolean`
This CL enables using `attr.bool` as a type of aspects parameters.
PiperOrigin-RevId: 417803129
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
index 5fbb8b2..3eb5c85 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
@@ -639,9 +639,12 @@
} else if (attribute.getType() == Type.INTEGER) {
// isValueSet() is always true for attr.int as default value is 0 by default.
hasDefault = !Objects.equals(attribute.getDefaultValue(null), StarlarkInt.of(0));
+ } else if (attribute.getType() == Type.BOOLEAN) {
+ hasDefault = !Objects.equals(attribute.getDefaultValue(null), false);
} else {
throw Starlark.errorf(
- "Aspect parameter attribute '%s' must have type 'int' or 'string'.", nativeName);
+ "Aspect parameter attribute '%s' must have type 'bool', 'int' or 'string'.",
+ nativeName);
}
if (hasDefault && attribute.checkAllowedValues()) {
@@ -799,14 +802,12 @@
String aspectAttrName = aspectAttribute.getPublicName();
Type<?> aspectAttrType = aspectAttribute.getType();
- // When propagated from a rule, explicit aspect attributes must be of type int or string
- // and they must have `values` restriction.
+ // When propagated from a rule, explicit aspect attributes must be of type boolean, int
+ // or string. Integer and string attributes must have the `values` restriction.
if (!aspectAttribute.isImplicit() && !aspectAttribute.isLateBound()) {
- if ((aspectAttrType != Type.STRING && aspectAttrType != Type.INTEGER)
- || !aspectAttribute.checkAllowedValues()) {
+ if (aspectAttrType != Type.BOOLEAN && !aspectAttribute.checkAllowedValues()) {
throw Starlark.errorf(
- "Aspect %s: Aspect parameter attribute '%s' must have type 'int' or 'string'"
- + " and use the 'values' restriction.",
+ "Aspect %s: Aspect parameter attribute '%s' must use the 'values' restriction.",
aspect.getName(), aspectAttrName);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
index 0f8772c..556b498 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java
@@ -460,7 +460,8 @@
attribute.isImplicit()
|| attribute.isLateBound()
|| (attribute.getType() == Type.STRING && attribute.checkAllowedValues())
- || (attribute.getType() == Type.INTEGER && attribute.checkAllowedValues()),
+ || (attribute.getType() == Type.INTEGER && attribute.checkAllowedValues())
+ || attribute.getType() == Type.BOOLEAN,
"%s: Invalid attribute '%s' (%s)",
aspectClass.getName(),
attribute.getName(),
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
index 6a66447..14f3e3c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.packages;
+import com.google.common.base.Ascii;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
@@ -51,6 +52,12 @@
private StarlarkAspectClass aspectClass;
+ private static final ImmutableSet<String> TRUE_REPS =
+ ImmutableSet.of("true", "1", "yes", "t", "y");
+
+ private static final ImmutableSet<String> FALSE_REPS =
+ ImmutableSet.of("false", "0", "no", "f", "n");
+
public StarlarkDefinedAspect(
StarlarkCallable implementation,
ImmutableList<String> attributeAspects,
@@ -147,7 +154,8 @@
String attrName = attr.getName();
String attrValue = aspectParams.getOnlyValueOfAttribute(attrName);
Preconditions.checkState(!Attribute.isImplicit(attrName));
- Preconditions.checkState(attrType == Type.STRING || attrType == Type.INTEGER);
+ Preconditions.checkState(
+ attrType == Type.STRING || attrType == Type.INTEGER || attrType == Type.BOOLEAN);
Preconditions.checkArgument(
aspectParams.getAttribute(attrName).size() == 1,
"Aspect %s parameter %s has %s values (must have exactly 1).",
@@ -182,11 +190,15 @@
private static Attribute addAttrValue(Attribute attr, String attrValue) {
Attribute.Builder<?> attrBuilder;
+ Type<?> attrType = attr.getType();
Object castedValue = attrValue;
- if (attr.getType() == Type.INTEGER) {
+ if (attrType == Type.INTEGER) {
castedValue = StarlarkInt.parse(attrValue, /*base=*/ 0);
attrBuilder = attr.cloneBuilder(Type.INTEGER).value((StarlarkInt) castedValue);
+ } else if (attrType == Type.BOOLEAN) {
+ castedValue = Boolean.parseBoolean(attrValue);
+ attrBuilder = attr.cloneBuilder(Type.BOOLEAN).value((Boolean) castedValue);
} else {
attrBuilder = attr.cloneBuilder(Type.STRING).value((String) castedValue);
}
@@ -197,7 +209,7 @@
// values in all aspects string attributes for both native and starlark aspects.
// Therefore, allowedValues list is added here with only the current value of the attribute.
return attrBuilder
- .allowedValues(new Attribute.AllowedValueSet(attr.getType().cast(castedValue)))
+ .allowedValues(new Attribute.AllowedValueSet(attrType.cast(castedValue)))
.build(attr.getName());
} else {
return attrBuilder.build(attr.getName());
@@ -232,8 +244,11 @@
rule.getTargetKind(),
param);
Preconditions.checkArgument(
- ruleAttr.getType() == Type.STRING || ruleAttr.getType() == Type.INTEGER,
- "Cannot apply aspect %s to %s since attribute '%s' is not string and not integer.",
+ ruleAttr.getType() == Type.STRING
+ || ruleAttr.getType() == Type.INTEGER
+ || ruleAttr.getType() == Type.BOOLEAN,
+ "Cannot apply aspect %s to %s since attribute '%s' is not boolean, integer, nor"
+ + " string.",
getName(),
rule.getTargetKind(),
param);
@@ -264,9 +279,11 @@
}
Preconditions.checkArgument(
- parameterType == Type.STRING || parameterType == Type.INTEGER,
- "Aspect %s: Cannot pass value of attribute '%s' of type %s, only 'int' and 'string'"
- + " attributes are allowed.",
+ parameterType == Type.STRING
+ || parameterType == Type.INTEGER
+ || parameterType == Type.BOOLEAN,
+ "Aspect %s: Cannot pass value of attribute '%s' of type %s, only 'boolean', 'int' and"
+ + " 'string' attributes are allowed.",
getName(),
parameterName,
parameterType);
@@ -275,36 +292,52 @@
parametersValues.getOrDefault(
parameterName, parameterType.cast(aspectParameter.getDefaultValue(null)).toString());
- // Validate integer values for integer attributes
Object castedParameterValue = parameterValue;
+ // Validate integer and boolean parameters values
if (parameterType == Type.INTEGER) {
- try {
- castedParameterValue = StarlarkInt.parse(parameterValue, /*base=*/ 0);
- } catch (NumberFormatException e) {
- throw new EvalException(
- String.format(
- "%s: expected value of type 'int' for attribute '%s' but got '%s'",
- getName(), parameterName, parameterValue),
- e);
- }
+ castedParameterValue = parseIntParameter(parameterName, parameterValue);
+ } else if (parameterType == Type.BOOLEAN) {
+ castedParameterValue = parseBooleanParameter(parameterName, parameterValue);
}
if (aspectParameter.checkAllowedValues()) {
PredicateWithMessage<Object> allowedValues = aspectParameter.getAllowedValues();
- if (parameterType == Type.INTEGER) {
- castedParameterValue = StarlarkInt.parse(parameterValue, /*base=*/ 0);
- }
if (!allowedValues.apply(castedParameterValue)) {
throw Starlark.errorf(
"%s: invalid value in '%s' attribute: %s",
- getName(), parameterName, allowedValues.getErrorReason(parameterValue));
+ getName(), parameterName, allowedValues.getErrorReason(castedParameterValue));
}
}
- builder.addAttribute(parameterName, parameterValue);
+ builder.addAttribute(parameterName, castedParameterValue.toString());
}
return builder.build();
}
+ private StarlarkInt parseIntParameter(String name, String value) throws EvalException {
+ try {
+ return StarlarkInt.parse(value, /*base=*/ 0);
+ } catch (NumberFormatException e) {
+ throw new EvalException(
+ String.format(
+ "%s: expected value of type 'int' for attribute '%s' but got '%s'",
+ getName(), name, value),
+ e);
+ }
+ }
+
+ private Boolean parseBooleanParameter(String name, String value) throws EvalException {
+ value = Ascii.toLowerCase(value);
+ if (TRUE_REPS.contains(value)) {
+ return true;
+ }
+ if (FALSE_REPS.contains(value)) {
+ return false;
+ }
+ throw Starlark.errorf(
+ "%s: expected value of type 'bool' for attribute '%s' but got '%s'",
+ getName(), name, value);
+ }
+
public ImmutableList<Label> getRequiredToolchains() {
return requiredToolchains;
}
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
index 8800035..0ff29ab 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
@@ -7299,8 +7299,8 @@
assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
}
assertContainsEvent(
- "Aspect //test:defs.bzl%my_aspect: Aspect parameter attribute 'param' must have type"
- + " 'int' or 'string' and use the 'values' restriction.");
+ "Aspect //test:defs.bzl%my_aspect: Aspect parameter attribute 'param' must use the 'values'"
+ + " restriction.");
}
/**
@@ -7334,8 +7334,8 @@
assertThrows(TargetParsingException.class, () -> update("//test:main_target"));
}
assertContainsEvent(
- "Aspect //test:defs.bzl%required_aspect: Aspect parameter attribute 'p1' must have type"
- + " 'int' or 'string' and use the 'values' restriction.");
+ "Aspect //test:defs.bzl%required_aspect: Aspect parameter attribute 'p1' must use the"
+ + " 'values' restriction.");
}
@Test
@@ -7502,8 +7502,8 @@
}
assertContainsEvent(
- "Aspect //test:aspect.bzl%MyAspectUncovered: Aspect parameter attribute 'my_attr' must have"
- + " type 'int' or 'string' and use the 'values' restriction.");
+ "Aspect //test:aspect.bzl%MyAspectUncovered: Aspect parameter attribute 'my_attr' must use"
+ + " the 'values' restriction.");
}
@Test
@@ -7841,6 +7841,440 @@
}
@Test
+ public void booleanAspectParameter_mandatoryAttrNotCoveredByRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.bool(default = True, mandatory = True) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
+ reporter.removeHandler(failFastHandler);
+
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a TargetParsingException.
+ if (keepGoing()) {
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
+ assertThat(analysisResult.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
+ }
+
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type boolean.");
+ }
+
+ @Test
+ public void booleanAspectParameter_mandatoryAttrWithWrongTypeInRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.bool(default = True, mandatory = True) },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
+ " 'my_attr': attr.string() },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
+ reporter.removeHandler(failFastHandler);
+
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a TargetParsingException.
+ if (keepGoing()) {
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
+ assertThat(analysisResult.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
+ }
+
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type boolean.");
+ }
+
+ @Test
+ public void booleanAspectParameter_attrWithoutDefaultNotCoveredByRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.bool() },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]) },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
+ reporter.removeHandler(failFastHandler);
+
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a TargetParsingException.
+ if (keepGoing()) {
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
+ assertThat(analysisResult.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
+ }
+
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type boolean.");
+ }
+
+ @Test
+ public void booleanAspectParameter_attrWithoutDefaultWrongTypeInRule() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " return struct()",
+ "def _rule_impl(ctx):",
+ " return struct()",
+ "MyAspectUncovered = aspect(",
+ " implementation=_impl,",
+ " attrs = { 'my_attr' : attr.bool() },",
+ ")",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'deps' : attr.label_list(aspects=[MyAspectUncovered]),",
+ " 'my_attr': attr.string() },",
+ ")");
+ scratch.file("test/BUILD", "load('//test:aspect.bzl', 'my_rule')", "my_rule(name ='main')");
+ reporter.removeHandler(failFastHandler);
+
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a TargetParsingException.
+ if (keepGoing()) {
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main");
+ assertThat(analysisResult.hasError()).isTrue();
+ } else {
+ assertThrows(TargetParsingException.class, () -> update(ImmutableList.of(), "//test:main"));
+ }
+
+ assertContainsEvent(
+ "Aspect //test:aspect.bzl%MyAspectUncovered requires rule my_rule to specify attribute "
+ + "'my_attr' with type boolean.");
+ }
+
+ @Test
+ public void aspectBooleanParameter_withDefaultValue() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " result = ['my_aspect on target {}, my_attr = {}'.",
+ " format(target.label, ctx.attr.my_attr)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.my_aspect_result",
+ " return struct(my_aspect_result = result)",
+ "",
+ "def _rule_impl(ctx):",
+ " if ctx.attr.dep:",
+ " return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
+ " pass",
+ "",
+ "MyAspect = aspect(",
+ " implementation = _aspect_impl,",
+ " attrs = { 'my_attr' : attr.bool(default = True) },",
+ ")",
+ "",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'dep' : attr.label(aspects=[MyAspect]) }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'main_target',",
+ " dep = ':dep_target',",
+ ")",
+ "my_rule(name = 'dep_target')");
+
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
+ assertThat(Starlark.toIterable(ruleResult))
+ .containsExactly("my_aspect on target //test:dep_target, my_attr = True");
+ }
+
+ @Test
+ public void aspectBooleanParameter_valueOverwrittenByRuleDefault() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " result = ['my_aspect on target {}, my_attr = {}'.",
+ " format(target.label, ctx.attr.my_attr)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.my_aspect_result",
+ " return struct(my_aspect_result = result)",
+ "",
+ "def _rule_impl(ctx):",
+ " if ctx.attr.dep:",
+ " return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
+ " pass",
+ "",
+ "MyAspect = aspect(",
+ " implementation = _aspect_impl,",
+ " attrs = { 'my_attr' : attr.bool(default = True) },",
+ ")",
+ "",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
+ " 'my_attr': attr.bool(default = False) }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'main_target',",
+ " dep = ':dep_target',",
+ ")",
+ "my_rule(name = 'dep_target')");
+
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
+ assertThat(Starlark.toIterable(ruleResult))
+ .containsExactly("my_aspect on target //test:dep_target, my_attr = False");
+ }
+
+ @Test
+ public void aspectBooleanParameter_valueOverwrittenByTargetValue() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target, ctx):",
+ " result = ['my_aspect on target {}, my_attr = {}'.",
+ " format(target.label, ctx.attr.my_attr)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.my_aspect_result",
+ " return struct(my_aspect_result = result)",
+ "",
+ "def _rule_impl(ctx):",
+ " if ctx.attr.dep:",
+ " return struct(my_rule_result = ctx.attr.dep.my_aspect_result)",
+ " pass",
+ "",
+ "MyAspect = aspect(",
+ " implementation = _aspect_impl,",
+ " attrs = { 'my_attr' : attr.bool(default = True) },",
+ ")",
+ "",
+ "my_rule = rule(",
+ " implementation=_rule_impl,",
+ " attrs = { 'dep' : attr.label(aspects=[MyAspect]),",
+ " 'my_attr': attr.bool(default = True) }",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:aspect.bzl', 'my_rule')",
+ "my_rule(name = 'main_target',",
+ " dep = ':dep_target',",
+ " my_attr = False,",
+ ")",
+ "my_rule(name = 'dep_target')");
+
+ AnalysisResult analysisResult = update(ImmutableList.of(), "//test:main_target");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkList<?> ruleResult = (StarlarkList) configuredTarget.get("my_rule_result");
+ assertThat(Starlark.toIterable(ruleResult))
+ .containsExactly("my_aspect on target //test:dep_target, my_attr = False");
+ }
+
+ @Test
+ public void testTopLevelAspectsWithBooleanParameter() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "def _aspect_a_impl(target, ctx):",
+ " result = ['aspect_a on target {}, p = {}'.",
+ " format(target.label, ctx.attr.p)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.aspect_a_result",
+ " return struct(aspect_a_result = result)",
+ "",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " attr_aspects = ['dep'],",
+ " attrs = { 'p' : attr.bool() },",
+ ")",
+ "",
+ "def _my_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = { 'dep' : attr.label() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'my_rule')",
+ "my_rule(",
+ " name = 'main_target',",
+ " dep = ':dep_target_1',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_1',",
+ " dep = ':dep_target_2',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_2',",
+ ")");
+ useConfiguration("--experimental_allow_top_level_aspects_parameters");
+
+ AnalysisResult analysisResult =
+ update(
+ ImmutableList.of("//test:defs.bzl%aspect_a"),
+ ImmutableMap.of("p", "y"),
+ "//test:main_target");
+
+ ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+ ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
+ assertThat(Starlark.toIterable(aspectAResult))
+ .containsExactly(
+ "aspect_a on target //test:main_target, p = True",
+ "aspect_a on target //test:dep_target_1, p = True",
+ "aspect_a on target //test:dep_target_2, p = True");
+ }
+
+ @Test
+ public void testTopLevelAspectsWithBooleanParameter_useDefaultValue() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "def _aspect_a_impl(target, ctx):",
+ " result = ['aspect_a on target {}, p = {}'.",
+ " format(target.label, ctx.attr.p)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.aspect_a_result",
+ " return struct(aspect_a_result = result)",
+ "",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " attr_aspects = ['dep'],",
+ " attrs = { 'p' : attr.bool(default = False) },",
+ ")",
+ "",
+ "def _my_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = { 'dep' : attr.label() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'my_rule')",
+ "my_rule(",
+ " name = 'main_target',",
+ " dep = ':dep_target_1',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_1',",
+ " dep = ':dep_target_2',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_2',",
+ ")");
+ useConfiguration("--experimental_allow_top_level_aspects_parameters");
+
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("//test:defs.bzl%aspect_a"), "//test:main_target");
+
+ ImmutableMap<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+ ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkList<?> aspectAResult = (StarlarkList) aspectA.get("aspect_a_result");
+ assertThat(Starlark.toIterable(aspectAResult))
+ .containsExactly(
+ "aspect_a on target //test:main_target, p = False",
+ "aspect_a on target //test:dep_target_1, p = False",
+ "aspect_a on target //test:dep_target_2, p = False");
+ }
+
+ @Test
+ public void testTopLevelAspectsWithBooleanParameter_invalidValue() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "def _aspect_a_impl(target, ctx):",
+ " result = ['aspect_a on target {}, p = {}'.",
+ " format(target.label, ctx.attr.p)]",
+ " if ctx.rule.attr.dep:",
+ " result += ctx.rule.attr.dep.aspect_a_result",
+ " return struct(aspect_a_result = result)",
+ "",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " attr_aspects = ['dep'],",
+ " attrs = { 'p' : attr.bool() },",
+ ")",
+ "",
+ "def _my_rule_impl(ctx):",
+ " pass",
+ "my_rule = rule(",
+ " implementation = _my_rule_impl,",
+ " attrs = { 'dep' : attr.label() },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'my_rule')",
+ "my_rule(",
+ " name = 'main_target',",
+ " dep = ':dep_target_1',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_1',",
+ " dep = ':dep_target_2',",
+ ")",
+ "my_rule(",
+ " name = 'dep_target_2',",
+ ")");
+ useConfiguration("--experimental_allow_top_level_aspects_parameters");
+ reporter.removeHandler(failFastHandler);
+
+ // This call succeeds if "--keep_going" was passed, which it does in the WithKeepGoing test
+ // suite. Otherwise, it fails and throws a ViewCreationFailedException.
+ if (keepGoing()) {
+ AnalysisResult analysisResult =
+ update(
+ ImmutableList.of("//test:defs.bzl%aspect_a"),
+ ImmutableMap.of("p", "x"),
+ "//test:main_target");
+ assertThat(analysisResult.hasError()).isTrue();
+ } else {
+ assertThrows(
+ ViewCreationFailedException.class,
+ () ->
+ update(
+ ImmutableList.of("//test:defs.bzl%aspect_a"),
+ ImmutableMap.of("p", "x"),
+ "//test:main_target"));
+ }
+ assertContainsEvent(
+ "//test:defs.bzl%aspect_a: expected value of type 'bool' for attribute 'p' but got 'x'");
+ }
+
+ @Test
public void testRuleAspectWithMandatoryParameterNotProvided() throws Exception {
scratch.file(
"test/defs.bzl",
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
index 3a6120d..5336000 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -615,7 +615,7 @@
@Test
public void testAspectParameterBadType() throws Exception {
ev.checkEvalErrorContains(
- "Aspect parameter attribute 'param' must have type 'int' or 'string'.",
+ "Aspect parameter attribute 'param' must have type 'bool', 'int' or 'string'.",
"def _impl(target, ctx):",
" pass",
"my_aspect = aspect(_impl,",