Do not propagate aspect to own attributes when using '*'.
RELNOTES: Do not propagate aspect to its own attributes when using '*'.
--
MOS_MIGRATED_REVID=138194456
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
index 32b7354..00e7693 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -238,7 +238,7 @@
|| attribute.isLateBound()) {
return;
}
- depResolver.resolveDep(attribute, label);
+ depResolver.resolveDep(new AttributeAndOwner(attribute), label);
}
});
}
@@ -253,7 +253,8 @@
Label ruleLabel = rule.getLabel();
ConfiguredAttributeMapper attributeMap = depResolver.attributeMap;
ImmutableSet<String> mappedAttributes = ImmutableSet.copyOf(attributeMap.getAttributeNames());
- for (Attribute attribute : depResolver.attributes) {
+ for (AttributeAndOwner attributeAndOwner : depResolver.attributes) {
+ Attribute attribute = attributeAndOwner.attribute;
if (!attribute.isImplicit() || !attribute.getCondition().apply(attributeMap)) {
continue;
}
@@ -265,7 +266,7 @@
if (label != null) {
label = ruleLabel.resolveRepositoryRelative(label);
- depResolver.resolveDep(attribute, label);
+ depResolver.resolveDep(attributeAndOwner, label);
}
} else if (attribute.getType() == BuildType.LABEL_LIST) {
List<Label> labelList;
@@ -278,7 +279,7 @@
labelList = BuildType.LABEL_LIST.cast(attribute.getDefaultValue(rule));
}
for (Label label : labelList) {
- depResolver.resolveDep(attribute, ruleLabel.resolveRepositoryRelative(label));
+ depResolver.resolveDep(attributeAndOwner, ruleLabel.resolveRepositoryRelative(label));
}
}
}
@@ -315,7 +316,8 @@
BuildConfiguration hostConfig)
throws EvalException, InvalidConfigurationException, InterruptedException {
ConfiguredAttributeMapper attributeMap = depResolver.attributeMap;
- for (Attribute attribute : depResolver.attributes) {
+ for (AttributeAndOwner attributeAndOwner : depResolver.attributes) {
+ Attribute attribute = attributeAndOwner.attribute;
if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) {
continue;
}
@@ -353,7 +355,7 @@
// is because the split already had to be applied to determine the attribute's value.
// This makes the split logic in the normal pipeline redundant and potentially
// incorrect.
- depResolver.resolveDep(attribute, dep, splitConfig);
+ depResolver.resolveDep(attributeAndOwner, dep, splitConfig);
}
}
} else {
@@ -361,7 +363,7 @@
for (Label dep : resolveLateBoundAttribute(depResolver.rule, attribute,
lateBoundDefault.useHostConfiguration() ? hostConfig : ruleConfig, attributeMap)) {
// Process this dep like a normal attribute.
- depResolver.resolveDep(attribute, dep);
+ depResolver.resolveDep(attributeAndOwner, dep);
}
}
}
@@ -459,7 +461,7 @@
}
Attribute attribute = rule.getRuleClassObject().getAttributeByName(attrName);
for (Label label : labels) {
- depResolver.resolveDep(attribute, label);
+ depResolver.resolveDep(new AttributeAndOwner(attribute), label);
}
}
@@ -482,7 +484,7 @@
Map<Attribute, Collection<Label>> m = depLabels.asMap();
for (Map.Entry<Attribute, Collection<Label>> entry : depLabels.asMap().entrySet()) {
for (Label depLabel : entry.getValue()) {
- depResolver.resolveDep(entry.getKey(), depLabel);
+ depResolver.resolveDep(new AttributeAndOwner(entry.getKey()), depLabel);
}
}
return outgoingEdges.values();
@@ -512,12 +514,16 @@
}
private static ImmutableSet<AspectDescriptor> requiredAspects(
- @Nullable Aspect aspect, Attribute attribute, final Target target, Rule originalRule) {
+ @Nullable Aspect aspect,
+ AttributeAndOwner attributeAndOwner,
+ final Target target,
+ Rule originalRule) {
if (!(target instanceof Rule)) {
return ImmutableSet.of();
}
- Iterable<Aspect> aspectCandidates = extractAspectCandidates(aspect, attribute, originalRule);
+ Iterable<Aspect> aspectCandidates =
+ extractAspectCandidates(aspect, attributeAndOwner, originalRule);
RuleClass ruleClass = ((Rule) target).getRuleClassObject();
ImmutableSet.Builder<AspectDescriptor> result = ImmutableSet.builder();
for (Aspect aspectCandidate : aspectCandidates) {
@@ -535,14 +541,15 @@
}
private static Iterable<Aspect> extractAspectCandidates(
- @Nullable Aspect aspect, Attribute attribute, Rule originalRule) {
+ @Nullable Aspect aspect, AttributeAndOwner attributeAndOwner, Rule originalRule) {
// The order of this set will be deterministic. This is necessary because this order eventually
// influences the order in which aspects are merged into the main configured target, which in
// turn influences which aspect takes precedence if two emit the same provider (maybe this
// should be an error)
+ Attribute attribute = attributeAndOwner.attribute;
Set<Aspect> aspectCandidates = new LinkedHashSet<>();
aspectCandidates.addAll(attribute.getAspects(originalRule));
- if (aspect != null) {
+ if (aspect != null && !aspect.getAspectClass().equals(attributeAndOwner.ownerAspect)) {
for (AspectClass aspectClass :
aspect.getDefinition().getAttributeAspects(attribute)) {
if (aspectClass.equals(aspect.getAspectClass())) {
@@ -562,6 +569,25 @@
}
/**
+ * Pair of (attribute, owner aspect if attribute is from an aspect).
+ *
+ * <p>For "plain" rule attributes, this wrapper class will have value (attribute, null).
+ */
+ final class AttributeAndOwner {
+ final Attribute attribute;
+ final @Nullable AspectClass ownerAspect;
+
+ AttributeAndOwner(Attribute attribute) {
+ this(attribute, null);
+ }
+
+ AttributeAndOwner(Attribute attribute, @Nullable AspectClass ownerAspect) {
+ this.attribute = attribute;
+ this.ownerAspect = ownerAspect;
+ }
+ }
+
+ /**
* Supplies the logic for translating <Attribute, Label> pairs for a rule into the
* <Attribute, Dependency> pairs DependencyResolver ultimately returns.
*
@@ -576,7 +602,7 @@
private final ConfiguredAttributeMapper attributeMap;
private final NestedSetBuilder<Label> rootCauses;
private final OrderedSetMultimap<Attribute, Dependency> outgoingEdges;
- private final List<Attribute> attributes;
+ private final List<AttributeAndOwner> attributes;
/**
* Constructs a new dependency resolver for the specified rule context.
@@ -600,36 +626,38 @@
this.attributes = getAttributes(rule, aspect);
}
- /**
- * Returns the attributes that should be visited for this rule/aspect combination.
- */
- private List<Attribute> getAttributes(Rule rule, @Nullable Aspect aspect) {
+ /** Returns the attributes that should be visited for this rule/aspect combination. */
+ private List<AttributeAndOwner> getAttributes(Rule rule, @Nullable Aspect aspect) {
+ ImmutableList.Builder<AttributeAndOwner> result = ImmutableList.builder();
List<Attribute> ruleDefs = rule.getRuleClassObject().getAttributes();
- if (aspect == null) {
- return ruleDefs;
- } else {
- return ImmutableList.<Attribute>builder()
- .addAll(ruleDefs)
- .addAll(aspect.getDefinition().getAttributes().values())
- .build();
+ for (Attribute attribute : ruleDefs) {
+ result.add(new AttributeAndOwner(attribute));
}
+ if (aspect != null) {
+ for (Attribute attribute : aspect.getDefinition().getAttributes().values()) {
+ result.add(new AttributeAndOwner(attribute, aspect.getAspectClass()));
+ }
+ }
+ return result.build();
}
/**
* Resolves the given dep for the given attribute, including determining which configurations to
* apply to it.
*/
- void resolveDep(Attribute attribute, Label depLabel) throws InterruptedException {
+ void resolveDep(AttributeAndOwner attributeAndOwner, Label depLabel)
+ throws InterruptedException {
Target toTarget = getTarget(rule, depLabel, rootCauses);
if (toTarget == null) {
return; // Skip this round: we still need to Skyframe-evaluate the dep's target.
}
BuildConfiguration.TransitionApplier resolver = ruleConfig.getTransitionApplier();
- ruleConfig.evaluateTransition(rule, attribute, toTarget, resolver);
+ ruleConfig.evaluateTransition(rule, attributeAndOwner.attribute, toTarget, resolver);
// An <Attribute, Label> pair can resolve to multiple deps because of split transitions.
for (Dependency dependency :
- resolver.getDependencies(depLabel, requiredAspects(aspect, attribute, toTarget, rule))) {
- outgoingEdges.put(attribute, dependency);
+ resolver.getDependencies(
+ depLabel, requiredAspects(aspect, attributeAndOwner, toTarget, rule))) {
+ outgoingEdges.put(attributeAndOwner.attribute, dependency);
}
}
@@ -640,9 +668,9 @@
* BuildConfiguration#evaluateTransition}). That means attributes passed through here won't obey
* standard rules on which configurations apply to their deps. This should only be done for
* special circumstances that really justify the difference. When in doubt, use {@link
- * #resolveDep(Attribute, Label)}.
+ * #resolveDep(AttributeAndOwner, Label)}.
*/
- void resolveDep(Attribute attribute, Label depLabel, BuildConfiguration config)
+ void resolveDep(AttributeAndOwner attributeAndOwner, Label depLabel, BuildConfiguration config)
throws InterruptedException {
Target toTarget = getTarget(rule, depLabel, rootCauses);
if (toTarget == null) {
@@ -655,7 +683,8 @@
applyNullTransition = true;
}
- ImmutableSet<AspectDescriptor> aspects = requiredAspects(aspect, attribute, toTarget, rule);
+ ImmutableSet<AspectDescriptor> aspects =
+ requiredAspects(aspect, attributeAndOwner, toTarget, rule);
Dependency dep;
if (config.useDynamicConfigurations() && !applyNullTransition) {
// Pass a transition rather than directly feeding the configuration so deps get trimmed.
@@ -665,7 +694,7 @@
dep = Iterables.getOnlyElement(transitionApplier.getDependencies(depLabel, aspects));
}
- outgoingEdges.put(attribute, dep);
+ outgoingEdges.put(attributeAndOwner.attribute, dep);
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
index a2e9d23..acae678 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
@@ -501,4 +501,48 @@
"rule //a:x");
}
+ /**
+ * Ensures an aspect with attr = '*' doesn't try to propagate to its own implicit attributes.
+ * Doing so leads to a dependency cycle.
+ */
+ @Test
+ public void aspectWithAllAttributesDoesNotPropagateToOwnImplicitAttributes() throws Exception {
+ setRulesAvailableInTests(
+ new TestAspects.BaseRule(),
+ new TestAspects.SimpleRule(),
+ new TestAspects.AllAttributesWithToolAspectRule());
+ pkg(
+ "a",
+ "simple(name='tool')",
+ "simple(name='a')",
+ "all_attributes_with_tool_aspect(name='x', foo=[':a'])");
+
+ ConfiguredTarget a = getConfiguredTarget("//a:x");
+ assertThat(a.getProvider(RuleInfo.class).getData())
+ .containsExactly("aspect //a:a", "rule //a:x");
+ }
+
+ /**
+ * Makes sure the aspect *will* propagate to its implicit attributes if there is a "regular"
+ * dependency path to it (i.e. not through its own implicit attributes).
+ */
+ @Test
+ public void aspectWithAllAttributesPropagatesToItsToolIfThereIsPath() throws Exception {
+ setRulesAvailableInTests(
+ new TestAspects.BaseRule(),
+ new TestAspects.SimpleRule(),
+ new TestAspects.AllAttributesWithToolAspectRule());
+ pkg(
+ "a",
+ "simple(name='tool')",
+ "simple(name='a', foo=[':b'], foo1=':c', txt='some text')",
+ "simple(name='b', foo=[], txt='some text')",
+ "simple(name='c', foo=[':tool'], txt='more text')",
+ "all_attributes_with_tool_aspect(name='x', foo=[':a'])");
+
+ ConfiguredTarget a = getConfiguredTarget("//a:x");
+ assertThat(a.getProvider(RuleInfo.class).getData())
+ .containsExactly(
+ "aspect //a:a", "aspect //a:b", "aspect //a:c", "aspect //a:tool", "rule //a:x");
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
index 263a795..f6f693e 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
@@ -50,6 +50,7 @@
import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
import com.google.devtools.build.lib.packages.Attribute.LateBoundLabelList;
import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
@@ -237,7 +238,25 @@
.allAttributesAspect(ALL_ATTRIBUTES_ASPECT)
.build();
+ /** An aspect that propagates along all attributes and has a tool dependency. */
+ public static class AllAttributesWithToolAspect extends BaseAspect {
+ @Override
+ public AspectDefinition getDefinition(AspectParameters aspectParameters) {
+ return ALL_ATTRIBUTES_WITH_TOOL_ASPECT_DEFINITION;
+ }
+ }
+
+ public static final NativeAspectClass ALL_ATTRIBUTES_WITH_TOOL_ASPECT =
+ new AllAttributesWithToolAspect();
+ private static final AspectDefinition ALL_ATTRIBUTES_WITH_TOOL_ASPECT_DEFINITION =
+ new AspectDefinition.Builder("all_attributes_with_tool_aspect")
+ .allAttributesAspect(ALL_ATTRIBUTES_WITH_TOOL_ASPECT)
+ .add(
+ attr("$tool", BuildType.LABEL)
+ .allowedFileTypes(FileTypeSet.ANY_FILE)
+ .value(Label.parseAbsoluteUnchecked("//a:tool")))
+ .build();
/**
* An aspect that requires aspects on the attributes of rules it attaches to.
@@ -575,6 +594,29 @@
}
}
+ /** A rule that defines an {@link AllAttributesWithToolAspect} on one of its attributes. */
+ public static class AllAttributesWithToolAspectRule implements RuleDefinition {
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+ return builder
+ .add(
+ attr("foo", LABEL_LIST)
+ .allowedFileTypes(FileTypeSet.ANY_FILE)
+ .aspect(ALL_ATTRIBUTES_WITH_TOOL_ASPECT))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("all_attributes_with_tool_aspect")
+ .factoryClass(DummyRuleFactory.class)
+ .ancestors(BaseRule.class)
+ .build();
+ }
+ }
+
/**
* A rule that defines a {@link WarningAspect} on one of its attributes.
*/
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
index e0eb465..cad9704 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
@@ -1174,6 +1174,57 @@
})).contains("file.xa");
}
+ @Test
+ public void aspectsPropagatingToAllAttributes() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _impl(target, ctx):",
+ " s = set([target.label])",
+ " if hasattr(ctx.rule.attr, 'runtime_deps'):",
+ " for i in ctx.rule.attr.runtime_deps:",
+ " s += i.target_labels",
+ " return struct(target_labels = s)",
+ "",
+ "MyAspect = aspect(",
+ " implementation=_impl,",
+ " attrs = { '_tool' : attr.label(default = Label('//test:tool')) },",
+ " attr_aspects=['*'],",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "java_library(",
+ " name = 'tool',",
+ ")",
+ "java_library(",
+ " name = 'bar',",
+ " runtime_deps = [':tool'],",
+ ")",
+ "java_library(",
+ " name = 'foo',",
+ " runtime_deps = [':bar'],",
+ ")");
+ AnalysisResult analysisResult =
+ update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:foo");
+ AspectValue aspectValue = analysisResult.getAspects().iterator().next();
+ SkylarkProviders skylarkProviders =
+ aspectValue.getConfiguredAspect().getProvider(SkylarkProviders.class);
+ assertThat(skylarkProviders).isNotNull();
+ Object names = skylarkProviders.getValue("target_labels");
+ assertThat(names).isInstanceOf(SkylarkNestedSet.class);
+ assertThat(
+ transform(
+ (SkylarkNestedSet) names,
+ new Function<Object, String>() {
+ @Nullable
+ @Override
+ public String apply(Object o) {
+ assertThat(o).isInstanceOf(Label.class);
+ return ((Label) o).getName();
+ }
+ }))
+ .containsExactly("foo", "bar", "tool");
+ }
+
@RunWith(JUnit4.class)
public static final class WithKeepGoing extends SkylarkAspectsTest {
@Override